从基础配置到架构设计:JiuwenClaw 日报生成器开发实践

引言|当"日报自动化"真正走进日常工作场景

过去一年,AI Agent 的能力被讨论得很多。从简单的问答到复杂的工作流编排,看起来 AI 已经无所不能。但当我们真正把 Agent 带到日常办公场景时,会发现一个很现实的问题:

一个能对话的 Agent,并不等于一个能帮你自动化工作的助手。

在日常办公场景里,我们面对的不是简单的"问答交互",而是更多的重复性工作,例如:每天下班前要写日报、每周要汇总周报、每月要整理月度总结。这些工作虽然不复杂,但需要耗费大量时间和精力。

如果一个 Agent 只是"能聊天",那它只是一个有趣的玩具。

但如果我们希望它成为一套真正可交付的"工作自动化助手",那它必须具备三件能力:1.自动收集多源数据,2.智能分析工作效率,3.多渠道主动推送。

在这篇文章里,我会完整拆解:

  • 如何设计多数据源采集架构(Git 提交 + 邮件统计 + 记忆系统 + 待办事项)
  • 如何实现工作分析引擎(效率指标计算 + 趋势对比 + 关键词提取)
  • 如何构建报告生成器(日报 + 周报 + 月报)
  • 如何配置定时任务实现自动推送
  • 完整的代码实现与测试验证流程

如果你也在思考:

  • 如何把 Agent 从 Demo 变成生产力工具?
  • 如何让 Agent 主动推送信息而不是被动响应?
  • 如何构建可复用的模块化技能体系?

那接下来的内容,或许会给你一些新的视角。


项目环境说明

实际运行环境

项目

配置值

项目路径

D:\Download\jiuwenclaw

操作系统

Windows 10

Python

3.10+

模型服务

ModelScope (Qwen/Qwen3-235B-A22B-Instruct-2507)

实际数据源配置

数据源

配置值

Git 仓库

D:\Download\jiuwenclaw (当前项目)

邮箱

******@163.com (网易163邮箱)

推送渠道

飞书 (cli_a92035b1823a9cd2)

心跳时间

每天 18:00-18:30

核心文件位置

D:\Download\jiuwenclaw\
├── .env                              # 环境变量配置
├── config/config.yaml                # 应用配置(心跳、飞书频道)
├── workspace/
│   ├── HEARTBEAT.md                  # 心跳任务配置
│   └── agent/skills/daily-report/    # 技能模块
│       ├── SKILL.md                  # 技能定义 v2.0.0
│       ├── collectors/               # 数据采集模块
│       ├── analyzers/                # 工作分析模块
│       └── generators/               # 报告生成模块

em@163.com

一、问题背景

1.1 基础版日报生成器的局限性

说起构建一个真正智能的日报生成器,大家第一反应往往是:

"那不就是把记忆系统和待办事项拼一拼,再加个模板就完事?"

在简单场景里,这种做法确实能用。

可一到真实工作现场,你就会碰到三类大坑:

  1. 数据源单一
    只能从记忆和待办获取数据,无法感知代码提交、邮件处理等实际工作产出。
  1. 缺乏分析能力
    只是简单罗列任务,无法计算效率指标、无法进行趋势对比、无法给出工作建议。
  1. 报告类型固定
    只能生成日报,无法汇总周报、月报,无法满足不同汇报周期的需求。

举个例子,用户想看"本周代码提交趋势"或"与上周相比的工作效率变化",基础版完全做不到。因为这些需要:

  • 获取 Git 提交历史数据
  • 进行跨时间段的数据对比
  • 计算效率指标和趋势分析

进阶版日报生成器 就不一样了——它从多个数据源采集信息,通过工作分析引擎处理,最终生成包含效率指标、趋势对比、工作建议的完整报告。

1.2 日常办公的实际痛点

做办公自动化项目时,踩过不少坑。

最典型的就是每天写日报太费时间。下班前本该是整理一天工作、准备下班的时候,却要花 15-20 分钟回想今天做了什么、写到哪了、有什么成果。有时候忙了一天,反而想不起来具体干了啥。

还有一个问题是日报格式不统一。有时候写成流水账,有时候写成要点,有时候干脆忘了写。团队协作时,每个人的日报格式都不一样,leader 看起来也头疼。

最麻烦的是容易遗漏重要事项。明明今天解决了一个关键 bug,或者完成了一个重要功能,结果写日报的时候忘了提,等于白干了。如果有系统能自动采集 Git 提交记录,就不会遗漏这些重要产出。

邮件沟通也是一个容易被忽略的工作内容。今天处理了多少邮件、有多少未读、有哪些重要邮件需要跟进,这些信息写日报时往往想不起来。

用了进阶版日报生成器之后,这些问题确实解决了很多。系统会自动采集 Git 提交、邮件统计、记忆记录、待办事项,然后通过工作分析引擎计算效率指标,生成趋势对比,给出工作建议。

1.3 JiuwenClaw 的技能系统特性

JiuwenClaw 是一个开源的 Agent 开发框架,其技能系统支持:

能力

说明

模块化技能

每个 Skill 可以包含多个 Python 模块

工具集成

可声明 allowed_tools 获取系统工具权限

心跳触发

支持定时任务自动执行技能

多频道推送

支持飞书、Web 等多种渠道

这个框架的价值在哪?简单说就是能力可扩展。通过模块化设计,我们可以把数据采集、工作分析、报告生成分别封装,形成清晰的责任边界。

二、技术方案

2.1 JiuwenClaw 的分层设计

JiuwenClaw 是分层架构的,我们的进阶版日报生成器在 Application Layer(应用层):

2.2 三层数据处理流程

2.3 核心组件概览

本项目的核心组件及其职责:

组件

类型

职责

所在模块

GitCollector

Collector

Git 提交记录采集

collectors/git_collector.py

EmailCollector

Collector

网易邮箱统计采集

collectors/email_collector.py

MemoryCollector

Collector

记忆数据采集

collectors/memory_collector.py

TodoCollector

Collector

待办事项采集

collectors/todo_collector.py

DataAggregator

Aggregator

数据聚合器

collectors/aggregator.py

WorkAnalyzer

Analyzer

工作分析引擎

analyzers/work_analyzer.py

ReportGenerator

Generator

报告生成器

generators/report_generator.py

组件依赖关系:

2.4 数据流与交互设计

完整的报告请求处理流程:

关键技术决策:

决策点

选择

原因

Git 采集方式

命令行 git log

无需额外依赖,直接调用系统 Git

邮件采集方式

IMAP 协议

网易邮箱支持,可获取邮件统计

分词工具

jieba(可选)

中文效果好,无依赖也可降级

报告格式

Markdown

兼容性好,飞书可渲染

触发方式

心跳 + 手动

定时自动 + 按需执行

第三章|Skills 技能系统工程化设计

3.1 Skills 目录结构

workspace/agent/skills/daily-report/
├── SKILL.md                    # 技能定义(必须)
├── collectors/                 # 数据采集模块
│   ├── __init__.py
│   ├── git_collector.py        # Git 提交采集
│   ├── email_collector.py      # 邮件统计采集
│   ├── memory_collector.py     # 记忆数据采集
│   ├── todo_collector.py       # 待办事项采集
│   └── aggregator.py           # 数据聚合器
├── analyzers/                  # 工作分析模块
│   ├── __init__.py
│   └── work_analyzer.py        # 工作分析引擎
├── generators/                 # 报告生成模块
│   ├── __init__.py
│   └── report_generator.py     # 报告生成器
└── report_helper.py            # 兼容旧版脚本

3.2 advanced-daily-report 的 SKILL.md

---
name: advanced-daily-report
version: 2.0.0
description: 进阶版日报生成器,支持多数据源采集、工作分析、趋势对比、周报月报聚合
tags: [report, automation, productivity, daily, weekly, monthly, advanced]
allowed_tools: [read_memory, write_memory, mcp_exec_command, read_file, write_file]
---

# 进阶版日报生成器

自动采集多源数据,智能分析工作效率,生成日报/周报/月报并推送到飞书。

## 核心能力

### 1. 多数据源采集

| 数据源 | 采集内容 | 频率 |
|--------|----------|------|
| **Git 仓库** | 提交记录、代码变更统计 | 实时 |
| **网易邮箱** | 收发邮件统计、未读提醒 | 实时 |
| **记忆系统** | 今日工作记录、长期记忆 | 实时 |
| **待办事项** | 任务状态、完成率 | 实时 |

### 2. 智能工作分析

- **效率指标计算**
  - 任务完成率 = 已完成 / 总任务
  - 生产力得分(0-100)
  - 专注度得分(0-100)

- **趋势对比**
  - 与昨日对比
  - 与上周同期对比
  - 周趋势图

- **关键词提取**
  - 自动提取今日工作关键词
  - 工作主题聚类

### 3. 多报告类型

| 类型 | 触发方式 | 推送时间 |
|------|----------|----------|
| **日报** | 手动/定时 | 每天 18:00 |
| **周报** | 定时 | 每周五 18:00 |
| **月报** | 定时 | 每月最后一天 18:00 |

## 目录结构

daily-report/
├── SKILL.md # 技能定义(本文件)
├── collectors/ # 数据采集模块
│ ├── init.py
│ ├── git_collector.py # Git 提交采集
│ ├── email_collector.py # 邮件统计采集
│ ├── memory_collector.py # 记忆数据采集
│ ├── todo_collector.py # 待办事项采集
│ └── aggregator.py # 数据聚合器
├── analyzers/ # 分析模块
│ ├── init.py
│ └── work_analyzer.py # 工作分析引擎
├── generators/ # 报告生成模块
│ ├── init.py
│ └── report_generator.py # 报告生成器
└── report_helper.py # 兼容旧版脚本


## 使用方式

### ⚠️ 重要:执行方式

本技能通过执行 Python 脚本来采集数据(Git提交、邮箱邮件、记忆、待办)。
**必须使用 `mcp_exec_command` 工具执行脚本**,而不是直接回复用户。

**脚本会自动采集以下数据**:
- **Git 提交记录**:通过 `git log` 命令读取 `D:/Download/jiuwenclaw` 仓库的提交历史
- **邮箱邮件统计**:通过 IMAP 协议连接 `******@163.com` 读取邮件统计(需要邮箱授权码)
- **记忆系统**:读取 `workspace/agent/memory/` 目录下的每日记忆文件
- **待办事项**:读取 `workspace/session/` 目录下的 todo.md 文件

### 手动触发

当用户请求生成日报/周报/月报时,**执行以下命令**:

```bash
# 生成今日日报(包含Git提交、待办任务、记忆数据)
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py daily --save

# 生成指定日期日报
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py daily --date 2026-03-06 --save

# 生成周报(聚合一周数据)
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py weekly --save

# 生成月报(聚合一月数据,包含每日Git提交统计)
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py monthly --save

# 生成月报(指定月份)
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py monthly --year 2026 --month 3 --save

执行步骤

  1. 用户发送 "生成日报" / "生成周报" / "生成月报" 等指令
  1. 使用 mcp_exec_command 执行上述命令
  1. 脚本自动采集数据:
    • Git: 执行 git log 获取提交记录、代码变更统计
    • 邮箱: 通过 IMAP 连接获取邮件统计(如果配置了邮箱)
    • 记忆: 读取记忆文件获取工作记录
    • 待办: 解析 todo.md 获取任务状态
  1. 等待脚本执行完成,获取输出内容
  1. 将报告内容发送给用户

触发关键词

  • 日报:生成今日日报、生成昨天日报、查看今日工作、查看代码提交
  • 周报:生成本周周报、周报汇总、本周工作总结
  • 月报:生成本月月报、月度总结、读取邮箱中本月的内容整理成月报、本月代码提交统计

数据源说明

数据源

采集方式

配置位置

Git 仓库

git log 命令

仓库路径: D:/Download/jiuwenclaw

网易邮箱

IMAP 协议

.env: EMAIL_ADDRESS, EMAIL_TOKEN

记忆系统

读取 MD 文件

workspace/agent/memory/YYYY-MM-DD.md

待办事项

解析 todo.md

workspace/session/*/todo.md

定时触发

通过 HEARTBEAT.md 配置定时执行:

## 活跃的任务项
- 生成今日工作日报  # 每天执行
- 每周五生成周报    # 周报
- 每月末生成月报    # 月报

日报模板

# 📋 工作日报 - 2026-03-06

## 📊 今日概览

| 指标 | 数值 |
|------|------|
| 提交次数 | 5 |
| 任务完成 | 3/8 |
| 代码变更 | +350/-80 |
| 邮件处理 | 收 12 / 发 3 |
| 生产力得分 | 78.5 |

## ✅ 已完成任务
- 完成日报生成器技能开发
- 配置飞书频道推送
- 测试心跳触发功能

## 🔄 进行中任务
- 编写开发文档
- 添加周报聚合功能

## 💻 代码提交

| 时间 | 提交信息 | 变更 |
|------|----------|------|
| 09:30 | feat: 添加日报生成功能 | +120/-30 |
| 14:15 | fix: 修复邮件采集bug | +45/-12 |

## 📧 邮件概况
- 今日收件: 12 封
- 今日发件: 3 封
- 未读邮件: 2 封

## 📈 趋势对比
- 提交: ↑ 2 次
- 效率: ↑ 5.2 分

## 💡 工作建议
1. 专注度较低,建议减少干扰
2. 任务完成率有待提高

## 🔜 明日计划
- 完善日报模板
- 添加周报聚合功能

配置说明

Git 仓库配置

本项目监控的 Git 仓库(脚本会自动读取):

仓库路径: D:/Download/jiuwenclaw

脚本通过 git log 命令采集以下数据:

  • 提交哈希、提交信息、作者、时间
  • 每次提交的文件变更数、新增行数、删除行数

邮箱配置

.env 文件中配置(本项目实际配置):

EMAIL_ADDRESS=******@163.com
EMAIL_TOKEN==******
EMAIL_PROVIDER=163

注意EMAIL_TOKEN 是邮箱授权码,不是登录密码。
获取方式:登录163邮箱 → 设置 → POP3/SMTP/IMAP → 开启IMAP服务 → 获取授权码

心跳配置

heartbeat:
  every: 3600
  target: feishu
  active_hours:
    start: 18:00
    end: 18:30

API 参考

数据采集器

from collectors import DataAggregator

aggregator = DataAggregator(
    workspace_dir="workspace",
    git_repo="path/to/repo",
    email_config={
        "address": "xxx@163.com",
        "auth_code": "xxx",
        "provider": "163"
    }
)

# 采集今日数据
data = aggregator.collect()

# 采集一周数据
week_data = aggregator.collect_week()

工作分析器

from analyzers import WorkAnalyzer

analyzer = WorkAnalyzer()
result = analyzer.analyze(data.to_dict())

print(f"生产力得分: {result.metrics.productivity_score}")
print(f"关键词: {result.keywords}")
print(f"建议: {result.suggestions}")

报告生成器

from generators import ReportGenerator

generator = ReportGenerator(aggregator)

# 生成日报
daily = generator.generate_daily()

# 生成周报
weekly = generator.generate_weekly()

# 生成月报
monthly = generator.generate_monthly(2026, 3)

注意事项

  1. Git 仓库: 确保仓库路径正确且有访问权限
  1. 邮箱授权: 使用授权码而非登录密码
  1. 心跳时间: 修改后需重启服务
  1. 数据存储: 报告保存到 workspace/agent/reports/

更新日志

  • v2.0.0 (2026-03-06): 进阶版,支持多数据源、趋势对比、周报月报
  • v1.0.0 (2026-03-06): 初始版本,基础日报生成

<!-- 这是一张图片,ocr 内容为:哈0日中 物到(G) 运行(R)终调(1) Q JIUWENCLAW 选择(S) 文件明 编铝() 益否(V) 帮助(H) SKILLMD 4.U X 贵源管理器 发行说明:1.110.0 用SXILMD>应R进输假日报生股差>承接心能力>由,有效量能工作分析 WONKSPACE> AGENT > SKILLS >DAILY-REPENT >KILL NAME:ADVANCED-DAILY-REPORT VERSION:2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.00 DESCRIPTION:进防版日报生顾露,支持多数据源采集,工作分析,菌势对比,周换月报频合 TABS: IREPART, AUTOBALIAN, PRODUCEIVITY,DAELY, WEEKIY,MONTHLY, ADVANCED] ALLOWED TOOLS: [READ MESMORY. WRITE MEMORY,MCP EXEC CONUAND.READ FILE. WRITE FILE] OGS #进阶版日报生成器 WORKSPACE 自动采集多源数据,智能分析工作效率,生成日报/周娘/月报并推送到飞书. AGENT #核心能力 豆 北京1.多数据源采体 SKILLS DAILY-REPORT 数据源|采集内容|频率 GIT仓库提交记录,代码变更统计 农中网易邮箱* 收发邮件统计,未读提醒 REPART_HELPER.PY *记忆系统 今日工作记录,长期间 待办事项 任务状态,完成电 RUN REPORT PY 文明 HASKILLMD 我需发.智能工作分析 PANANCIAL-DOCUMENL-PARSER FINANCIAL PARSER PY 效率指标计算 .已完成/总任务 任务完成率 SOULMD 生产力得分(0-100) 专注度得分(0-100) 趋势对比 与非日对比 与上周司期对比 居超势图 关键词提取** 自动提取今日工作关键词 QITIGNORE 工作主题聚英 BUILDPST ###3,多报告类型 MANIFESTIN 类型| 触发方式 推送时间 D OPEN SOURCE SOFTWARE NOTICE MD 日报手动/定时每天18:00 [T]PYPROJECTTOML 定时 *周报** CI README.MD 每月最后一天18:00 定时 月报* 大前 目录结构 BILLS 0乡 #88MAIN* O 0 0 444 UPDATE IS READY,CLICK TO RESTANT. 行31.列11 至格? UTF8 () SKL 8FINIMSEUP -->
![](https://cdn.nlark.com/yuque/0/2026/png/27326384/1772862026601-dd7ed3ff-26da-4182-8f6f-c45c437feffc.png)

### 3.3 日报模板
```markdown
# 📋 工作日报 - 2026-03-06

## 📊 今日概览

| 指标 | 数值 |
|------|------|
| 提交次数 | 5 |
| 任务完成 | 3/8 |
| 代码变更 | +350/-80 |
| 邮件处理 | 收 12 / 发 3 |
| 生产力得分 | 78.5 |

## ✅ 已完成任务
- 完成日报生成器技能开发
- 配置飞书频道推送

## 💻 代码提交

| 时间 | 提交信息 | 变更 |
|------|----------|------|
| 09:30 | feat: 添加日报生成功能 | +120/-30 |

## 📧 邮件概况
- 今日收件: 12 封
- 今日发件: 3 封
- 未读邮件: 2 封

## 📈 趋势对比
- 提交: ↑ 2 次
- 效率: ↑ 5.2 分

## 💡 工作建议
1. 专注度较低,建议减少干扰

## 🔜 明日计划
- 完善日报模板

第四章|数据采集层完整实现

4.1 Git 提交采集器

# collectors/git_collector.py
# -*- coding: utf-8 -*-
"""
Git 提交记录采集器

功能:
- 获取指定日期的 Git 提交记录
- 统计提交次数、修改文件数、代码行数变化
- 支持多个仓库
"""

import subprocess
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional


@dataclass
class GitCommit:
    """Git 提交记录"""

    hash: str  # 提交哈希
    message: str  # 提交信息
    author: str  # 作者
    date: datetime  # 提交时间
    files_changed: int = 0  # 修改文件数
    insertions: int = 0  # 新增行数
    deletions: int = 0  # 删除行数

    def to_dict(self) -> dict:
        return {
            "hash": self.hash,
            "message": self.message,
            "author": self.author,
            "date": self.date.isoformat(),
            "files_changed": self.files_changed,
            "insertions": self.insertions,
            "deletions": self.deletions,
        }


@dataclass
class GitStats:
    """Git 统计数据"""

    commits: list[GitCommit] = field(default_factory=list)
    total_commits: int = 0
    total_files_changed: int = 0
    total_insertions: int = 0
    total_deletions: int = 0

    @property
    def net_lines(self) -> int:
        """净增行数"""
        return self.total_insertions - self.total_deletions

    def to_dict(self) -> dict:
        return {
            "total_commits": self.total_commits,
            "total_files_changed": self.total_files_changed,
            "total_insertions": self.total_insertions,
            "total_deletions": self.total_deletions,
            "net_lines": self.net_lines,
            "commits": [c.to_dict() for c in self.commits],
        }


class GitCollector:
    """Git 提交记录采集器"""

    def __init__(self, repo_path: str | Path):
        """
        初始化 Git 采集器

        Args:
            repo_path: Git 仓库路径
        """
        self.repo_path = Path(repo_path).resolve()

    def _run_git_command(self, args: list[str], timeout: int = 30) -> str:
        """执行 Git 命令"""
        try:
            result = subprocess.run(
                ["git", "-C", str(self.repo_path)] + args,
                capture_output=True,
                text=True,
                timeout=timeout,
                encoding="utf-8",
                errors="replace",
            )
            return result.stdout.strip()
        except subprocess.TimeoutExpired:
            return ""
        except Exception as e:
            return ""

    def get_commits(self, date: Optional[str] = None, author: Optional[str] = None) -> GitStats:
        """
        获取指定日期的提交记录

        Args:
            date: 日期字符串 (YYYY-MM-DD),默认今天
            author: 作者名称过滤,默认不过滤

        Returns:
            GitStats: Git 统计数据
        """
        if date is None:
            date = datetime.now().strftime("%Y-%m-%d")

        stats = GitStats()

        # 获取提交列表
        log_format = "%H|%s|%an|%ai"
        since = f"{date} 00:00:00"
        until = f"{date} 23:59:59"

        cmd_args = [
            "log",
            f"--since={since}",
            f"--until={until}",
            f"--format={log_format}",
        ]

        if author:
            cmd_args.append(f"--author={author}")

        log_output = self._run_git_command(cmd_args)

        if not log_output:
            return stats

        # 解析提交记录
        for line in log_output.split("\n"):
            if not line.strip():
                continue

            parts = line.split("|", 3)
            if len(parts) < 4:
                continue

            commit_hash, message, author_name, date_str = parts

            try:
                commit_date = datetime.fromisoformat(
                    date_str.replace(" ", "T").split("+")[0]
                )
            except ValueError:
                commit_date = datetime.now()

            # 获取每个提交的文件变更统计
            numstat = self._run_git_command(
                ["show", "--numstat", "--format=", commit_hash]
            )

            files_changed = 0
            insertions = 0
            deletions = 0

            for stat_line in numstat.split("\n"):
                if not stat_line.strip():
                    continue
                stat_parts = stat_line.split("\t")
                if len(stat_parts) >= 2:
                    try:
                        ins = int(stat_parts[0]) if stat_parts[0] != "-" else 0
                        dels = int(stat_parts[1]) if stat_parts[1] != "-" else 0
                        insertions += ins
                        deletions += dels
                        files_changed += 1
                    except ValueError:
                        continue

            commit = GitCommit(
                hash=commit_hash[:8],
                message=message.strip(),
                author=author_name,
                date=commit_date,
                files_changed=files_changed,
                insertions=insertions,
                deletions=deletions,
            )

            stats.commits.append(commit)
            stats.total_commits += 1
            stats.total_files_changed += files_changed
            stats.total_insertions += insertions
            stats.total_deletions += deletions

        return stats

4.2 邮件统计采集器

重要说明:163邮箱的 IMAP 服务有特殊限制,直接使用 SELECT 命令会返回 "Unsafe Login" 错误。
解决方案:

  1. 注册 ID 命令到 imaplib:imaplib.Commands['ID'] = ('NONAUTH', 'AUTH', 'SELECTED')
  1. 登录后发送 ID 命令声明客户端身份
  1. 使用 STATUS 命令获取邮件统计(绕过 SELECT 限制)
  1. 如需读取邮件内容,ID 命令发送成功后可正常使用 SELECT
# collectors/email_collector.py
# -*- coding: utf-8 -*-
"""
邮件统计采集器

支持:
- 网易邮箱 (163/126/yeah)
- 通过 IMAP 协议读取邮件

功能:
- 统计收件箱邮件数量
- 获取未读邮件数
- 读取邮件内容摘要

163邮箱特殊处理:
- 必须注册 ID 命令并登录后发送
- 使用 STATUS 命令获取统计(绕过 Unsafe Login 限制)
"""

import email
import re
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from email.header import decode_header
from typing import Optional

try:
    import imaplib
    IMAP_AVAILABLE = True
    # 163邮箱必须:注册ID命令到imaplib
    imaplib.Commands['ID'] = ('NONAUTH', 'AUTH', 'SELECTED')
except ImportError:
    IMAP_AVAILABLE = False
    imaplib = None


# 网易邮箱 IMAP 服务器配置
NETEASE_IMAP_SERVERS = {
    "163": "imap.163.com",
    "126": "imap.126.com",
    "yeah": "imap.yeah.net",
}


@dataclass
class EmailInfo:
    """邮件信息"""
    subject: str = ""  # 主题
    sender: str = ""  # 发件人
    date: str = ""  # 日期
    body_preview: str = ""  # 正文预览

    def to_dict(self) -> dict:
        return {
            "subject": self.subject,
            "sender": self.sender,
            "date": self.date,
            "body_preview": self.body_preview[:200] if self.body_preview else "",
        }


@dataclass
class EmailStats:
    """邮件统计数据"""
    total_emails: int = 0  # 邮箱总邮件数
    unread: int = 0  # 未读邮件数
    recent_emails: list[EmailInfo] = field(default_factory=list)  # 近期邮件

    def to_dict(self) -> dict:
        return {
            "total_emails": self.total_emails,
            "unread": self.unread,
            "recent_emails": [e.to_dict() for e in self.recent_emails],
        }


class EmailCollector:
    """邮件统计采集器"""

    def __init__(
        self,
        email_address: str,
        auth_code: str,
        provider: str = "163",
    ):
        """
        初始化邮件采集器

        Args:
            email_address: 邮箱地址
            auth_code: 授权码(不是登录密码)
            provider: 邮箱提供商 (163/126/yeah)
        """
        if not IMAP_AVAILABLE:
            raise ImportError("imaplib 模块不可用")

        self.email_address = email_address
        self.auth_code = auth_code
        self.provider = provider.lower()

        if self.provider not in NETEASE_IMAP_SERVERS:
            raise ValueError(f"不支持的邮箱提供商: {provider}")

        self.imap_server = NETEASE_IMAP_SERVERS[self.provider]
        self._connection = None

    def _decode_str(self, s: str) -> str:
        """解码邮件字符串"""
        if s is None:
            return ""
        decoded_parts = decode_header(s)
        result = []
        for part, encoding in decoded_parts:
            if isinstance(part, bytes):
                try:
                    result.append(part.decode(encoding or "utf-8", errors="ignore"))
                except Exception:
                    result.append(part.decode("utf-8", errors="ignore"))
            else:
                result.append(part)
        return "".join(result)

    def connect(self) -> bool:
        """连接 IMAP 服务器并发送ID命令"""
        try:
            self._connection = imaplib.IMAP4_SSL(self.imap_server, 993)
            self._connection.login(self.email_address, self.auth_code)

            # 163邮箱必须:登录后立即发送ID命令
            args = '("name" "python" "version" "1.0" "vendor" "python-imap")'
            self._connection._simple_command("ID", args)

            return True
        except Exception as e:
            print(f"连接邮箱失败: {e}")
            return False

    def disconnect(self):
        """断开连接"""
        if self._connection:
            try:
                self._connection.logout()
            except Exception:
                pass
            self._connection = None

    def get_stats(self) -> EmailStats:
        """
        获取邮件统计(使用STATUS命令,绕过SELECT限制)

        Returns:
            EmailStats: 邮件统计数据
        """
        stats = EmailStats()

        if not self._connection:
            if not self.connect():
                return stats

        try:
            # 使用 STATUS 命令获取统计(163邮箱的SELECT会报Unsafe Login)
            status, data = self._connection.status("INBOX", "(MESSAGES UNSEEN)")
            if status == "OK" and data:
                # 解析响应: b'"INBOX" (MESSAGES 39 UNSEEN 32)'
                response = data[0].decode() if isinstance(data[0], bytes) else str(data[0])
                messages_match = re.search(r'MESSAGES\s+(\d+)', response)
                unseen_match = re.search(r'UNSEEN\s+(\d+)', response)
                if messages_match:
                    stats.total_emails = int(messages_match.group(1))
                if unseen_match:
                    stats.unread = int(unseen_match.group(1))
        except Exception as e:
            print(f"获取邮件统计失败: {e}")

        return stats

    def get_recent_emails(self, limit: int = 10, days: int = 30) -> list[EmailInfo]:
        """
        读取近期邮件内容(ID命令发送后可正常使用SELECT)

        Args:
            limit: 最多读取邮件数量
            days: 只读取最近N天内的邮件

        Returns:
            邮件列表
        """
        if not self._connection:
            if not self.connect():
                return []

        emails = []

        try:
            # ID命令已发送,现在可以正常使用SELECT
            typ, dat = self._connection.select("INBOX")
            if typ != "OK":
                return []

            # 搜索最近N天的邮件
            since_date = (datetime.now() - timedelta(days=days)).strftime("%d-%b-%Y")
            typ, msg_ids = self._connection.search(None, f'(SINCE {since_date})')

            if typ != "OK" or not msg_ids[0]:
                return []

            ids = msg_ids[0].split()[-limit:]  # 获取最新的N封

            for msg_id in reversed(ids):
                try:
                    typ, msg_data = self._connection.fetch(msg_id, "(RFC822)")
                    if typ != "OK":
                        continue

                    raw_email = msg_data[0][1]
                    msg = email.message_from_bytes(raw_email)

                    # 解码主题
                    subject = self._decode_str(msg["Subject"]) or "(无主题)"
                    from_addr = self._decode_str(msg.get("From", ""))
                    date_str = msg.get("Date", "")

                    # 提取正文
                    body = ""
                    if msg.is_multipart():
                        for part in msg.walk():
                            content_type = part.get_content_type()
                            if content_type == "text/plain":
                                payload = part.get_payload(decode=True)
                                charset = part.get_content_charset() or "utf-8"
                                body = payload.decode(charset, errors="ignore")
                                break
                            elif content_type == "text/html" and not body:
                                payload = part.get_payload(decode=True)
                                charset = part.get_content_charset() or "utf-8"
                                html_body = payload.decode(charset, errors="ignore")
                                body = re.sub(r'<[^>]+>', ' ', html_body)
                                body = re.sub(r'\s+', ' ', body).strip()
                    else:
                        payload = msg.get_payload(decode=True)
                        charset = msg.get_content_charset() or "utf-8"
                        body = payload.decode(charset, errors="ignore") if payload else ""

                    emails.append(EmailInfo(
                        subject=subject[:100],
                        sender=from_addr[:80],
                        date=date_str,
                        body_preview=body[:500] if body else ""
                    ))
                except Exception:
                    continue

        except Exception as e:
            print(f"读取邮件内容失败: {e}")

        return emails

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()
        return False

4.3 记忆数据采集器

# collectors/memory_collector.py
# -*- coding: utf-8 -*-
"""
记忆数据采集器

功能:
- 读取今日记忆文件
- 读取长期记忆
- 提取工作摘要
"""

import re
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional


@dataclass
class MemoryData:
    """记忆数据"""

    today_content: str = ""  # 今日记忆内容
    long_term_content: str = ""  # 长期记忆内容
    work_summaries: list[str] = field(default_factory=list)  # 工作摘要列表
    key_decisions: list[str] = field(default_factory=list)  # 关键决策

    def to_dict(self) -> dict:
        return {
            "today_content": self.today_content[:500] if self.today_content else "",
            "work_summaries": self.work_summaries,
            "key_decisions": self.key_decisions,
        }


class MemoryCollector:
    """记忆数据采集器"""

    def __init__(self, workspace_dir: str | Path):
        """
        初始化记忆采集器

        Args:
            workspace_dir: workspace 目录路径
        """
        self.workspace_dir = Path(workspace_dir)
        self.memory_dir = self.workspace_dir / "agent" / "memory"

    def _read_file_safe(self, file_path: Path) -> str:
        """安全读取文件"""
        if not file_path.exists():
            return ""
        try:
            return file_path.read_text(encoding="utf-8")
        except Exception:
            return ""

    def _extract_list_items(self, content: str) -> list[str]:
        """提取列表项(以 - 或 * 开头的行)"""
        items = []
        for line in content.split("\n"):
            stripped = line.strip()
            if stripped.startswith("-") or stripped.startswith("*"):
                item = stripped.lstrip("-* ").strip()
                # 跳过注释和空项
                if item and not item.startswith("<!--"):
                    items.append(item)
        return items

    def _extract_sections(self, content: str, section_title: str) -> list[str]:
        """提取指定标题下的内容"""
        items = []
        in_section = False

        for line in content.split("\n"):
            stripped = line.strip()

            # 检测标题
            if stripped.startswith("##"):
                if section_title.lower() in stripped.lower():
                    in_section = True
                else:
                    in_section = False
                continue

            if in_section:
                if stripped.startswith("-") or stripped.startswith("*"):
                    item = stripped.lstrip("-* ").strip()
                    if item and not item.startswith("<!--"):
                        items.append(item)

        return items

    def collect(self, date: Optional[str] = None) -> MemoryData:
        """
        采集记忆数据

        Args:
            date: 日期字符串 (YYYY-MM-DD),默认今天

        Returns:
            MemoryData: 记忆数据
        """
        if date is None:
            date = datetime.now().strftime("%Y-%m-%d")

        data = MemoryData()

        # 读取今日记忆
        today_file = self.memory_dir / f"{date}.md"
        data.today_content = self._read_file_safe(today_file)

        # 读取长期记忆
        memory_file = self.memory_dir / "MEMORY.md"
        data.long_term_content = self._read_file_safe(memory_file)

        # 提取工作摘要
        data.work_summaries = self._extract_list_items(data.today_content)

        # 提取关键决策(从长期记忆中)
        data.key_decisions = self._extract_sections(
            data.long_term_content, "决策"
        ) or self._extract_sections(data.long_term_content, "偏好")

        return data

    def get_week_memories(self, end_date: Optional[str] = None) -> dict[str, MemoryData]:
        """获取一周的记忆数据"""
        if end_date is None:
            end_date = datetime.now()
        else:
            end_date = datetime.strptime(end_date, "%Y-%m-%d")

        result = {}
        for i in range(7):
            date = (end_date - timedelta(days=i)).strftime("%Y-%m-%d")
            result[date] = self.collect(date)

        return result

    def get_month_memories(self, year: int, month: int) -> dict[str, MemoryData]:
        """获取一月的记忆数据"""
        import calendar

        _, days_in_month = calendar.monthrange(year, month)
        result = {}

        for day in range(1, days_in_month + 1):
            date = f"{year:04d}-{month:02d}-{day:02d}"
            result[date] = self.collect(date)

        return result

4.4 待办事项采集器

# collectors/todo_collector.py
# -*- coding: utf-8 -*-
"""
待办事项采集器

功能:
- 读取 todo.md 文件
- 解析任务状态
- 统计完成情况
"""

import re
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Optional


@dataclass
class TodoTask:
    """待办任务"""

    id: str
    content: str
    status: str  # completed, running, waiting, cancelled
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "content": self.content,
            "status": self.status,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@dataclass
class TodoStats:
    """待办统计"""

    total: int = 0
    completed: int = 0
    running: int = 0
    waiting: int = 0
    cancelled: int = 0
    tasks: list[TodoTask] = field(default_factory=list)

    @property
    def completion_rate(self) -> float:
        """完成率"""
        if self.total == 0:
            return 0.0
        return self.completed / self.total

    def to_dict(self) -> dict:
        return {
            "total": self.total,
            "completed": self.completed,
            "running": self.running,
            "waiting": self.waiting,
            "cancelled": self.cancelled,
            "completion_rate": round(self.completion_rate, 2),
            "tasks": [t.to_dict() for t in self.tasks],
        }


class TodoCollector:
    """待办事项采集器"""

    def __init__(self, workspace_dir: str | Path):
        """
        初始化待办采集器

        Args:
            workspace_dir: workspace 目录路径
        """
        self.workspace_dir = Path(workspace_dir)
        self.session_dir = self.workspace_dir / "session"

    def _read_file_safe(self, file_path: Path) -> str:
        """安全读取文件"""
        if not file_path.exists():
            return ""
        try:
            return file_path.read_text(encoding="utf-8")
        except Exception:
            return ""

    def _find_latest_todo_file(self) -> Optional[Path]:
        """查找最新的 todo.md 文件"""
        if not self.session_dir.exists():
            return None

        todo_files = list(self.session_dir.rglob("todo.md"))

        if not todo_files:
            return None

        # 按修改时间排序,返回最新的
        todo_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
        return todo_files[0]

    def _parse_status(self, line: str) -> tuple[str, str]:
        """
        解析任务行,提取 ID 和状态

        支持格式:
        - [x] 1. 任务内容
        - [ ] 1. 任务内容
        - 1. [status:completed] 任务内容
        - 1. ✅ 任务内容
        """
        # Markdown checkbox 格式
        checkbox_match = re.match(r"\s*-\s*\[([xX ])\]\s*(.+)", line)
        if checkbox_match:
            checked = checkbox_match.group(1).lower() == "x"
            content = checkbox_match.group(2).strip()
            status = "completed" if checked else "waiting"
            return "", status

        # 带状态标记格式
        status_match = re.match(r"\s*(\d+)\.\s*\[status:(\w+)\]\s*(.+)", line, re.IGNORECASE)
        if status_match:
            task_id = status_match.group(1)
            status = status_match.group(2).lower()
            content = status_match.group(3).strip()
            return task_id, status

        # 带状态标记格式(中括号前)
        bracket_match = re.match(r"\s*(\d+)\.\s*\[([xX✅🔄⏳❌])\]\s*(.+)", line)
        if bracket_match:
            task_id = bracket_match.group(1)
            status_char = bracket_match.group(2)
            content = bracket_match.group(3).strip()

            status_map = {
                "x": "completed", "X": "completed", "✅": "completed",
                "🔄": "running", "⏳": "waiting", "❌": "cancelled",
            }
            status = status_map.get(status_char, "waiting")
            return task_id, status

        # 普通编号格式
        number_match = re.match(r"\s*(\d+)\.\s+(.+)", line)
        if number_match:
            task_id = number_match.group(1)
            content = number_match.group(2).strip()

            # 从内容中检测状态
            if "✅" in content or "[完成]" in content:
                status = "completed"
            elif "🔄" in content or "[进行中]" in content:
                status = "running"
            elif "❌" in content or "[取消]" in content:
                status = "cancelled"
            else:
                status = "waiting"

            return task_id, status

        return "", ""

    def collect(self) -> TodoStats:
        """
        采集待办数据

        Returns:
            TodoStats: 待办统计数据
        """
        stats = TodoStats()

        todo_file = self._find_latest_todo_file()
        if not todo_file:
            return stats

        content = self._read_file_safe(todo_file)
        if not content:
            return stats

        task_counter = 0

        for line in content.split("\n"):
            if not line.strip():
                continue

            task_id, status = self._parse_status(line)

            if status:
                task_counter += 1

                # 提取任务内容(去除状态标记)
                content_clean = re.sub(r"\[status:\w+\]\s*", "", line)
                content_clean = re.sub(r"\[[xX✅🔄⏳❌]\]\s*", "", content_clean)
                content_clean = re.sub(r"^\s*-\s*\[[xX ]\]\s*", "", content_clean)
                content_clean = re.sub(r"^\s*\d+\.\s*", "", content_clean)
                content_clean = content_clean.strip()

                if not task_id:
                    task_id = str(task_counter)

                task = TodoTask(id=task_id, content=content_clean, status=status)

                stats.tasks.append(task)
                stats.total += 1

                if status == "completed":
                    stats.completed += 1
                elif status == "running":
                    stats.running += 1
                elif status == "waiting":
                    stats.waiting += 1
                elif status == "cancelled":
                    stats.cancelled += 1

        return stats

4.5 采集模块初始化文件

# collectors/__init__.py
# -*- coding: utf-8 -*-
"""
进阶版日报生成器 - 数据采集模块

包含:
- GitCollector: Git 提交记录采集
- EmailCollector: 邮件统计采集
- MemoryCollector: 记忆数据采集
- TodoCollector: 待办事项采集
- DataAggregator: 数据聚合器
"""

from .git_collector import GitCollector, GitCommit
from .email_collector import EmailCollector, EmailStats
from .memory_collector import MemoryCollector
from .todo_collector import TodoCollector
from .aggregator import DataAggregator, CollectedData

__all__ = [
    "GitCollector", "GitCommit",
    "EmailCollector", "EmailStats",
    "MemoryCollector",
    "TodoCollector",
    "DataAggregator", "CollectedData",
]

4.6 数据聚合器

# collectors/aggregator.py
# -*- coding: utf-8 -*-
"""
数据聚合器

功能:
- 整合所有采集器的数据
- 统一时间窗口过滤
- 提供统一的数据访问接口
"""

import json
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Optional

from .email_collector import EmailCollector, EmailStats
from .git_collector import GitCollector, GitStats
from .memory_collector import MemoryCollector, MemoryData
from .todo_collector import TodoCollector, TodoStats


@dataclass
class CollectedData:
    """聚合后的数据"""

    date: str  # 日期
    collected_at: datetime  # 采集时间

    # Git 数据
    git: GitStats = field(default_factory=GitStats)

    # 邮件数据
    email: EmailStats = field(default_factory=EmailStats)

    # 记忆数据
    memory: MemoryData = field(default_factory=MemoryData)

    # 待办数据
    todo: TodoStats = field(default_factory=TodoStats)

    # 历史对比数据
    comparison: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "date": self.date,
            "collected_at": self.collected_at.isoformat(),
            "git": self.git.to_dict(),
            "email": self.email.to_dict(),
            "memory": self.memory.to_dict(),
            "todo": self.todo.to_dict(),
            "comparison": self.comparison,
        }


class DataAggregator:
    """数据聚合器"""

    def __init__(
        self,
        workspace_dir: str | Path,
        git_repo: Optional[str | Path] = None,
        email_config: Optional[dict] = None,
    ):
        """
        初始化数据聚合器

        Args:
            workspace_dir: workspace 目录
            git_repo: Git 仓库路径
            email_config: 邮箱配置
        """
        self.workspace_dir = Path(workspace_dir)

        # 初始化各采集器
        self.memory_collector = MemoryCollector(self.workspace_dir)
        self.todo_collector = TodoCollector(self.workspace_dir)

        # Git 采集器(可选)
        self.git_collector = None
        if git_repo:
            self.git_collector = GitCollector(git_repo)

        # 邮件采集器(可选)
        self.email_collector = None
        self.email_config = email_config

    def collect(self, date: Optional[str] = None, include_comparison: bool = True) -> CollectedData:
        """
        聚合采集数据

        Args:
            date: 日期字符串,默认今天
            include_comparison: 是否包含历史对比

        Returns:
            CollectedData: 聚合后的数据
        """
        if date is None:
            date = datetime.now().strftime("%Y-%m-%d")

        data = CollectedData(
            date=date,
            collected_at=datetime.now(),
        )

        # 采集记忆数据
        data.memory = self.memory_collector.collect(date)

        # 采集待办数据
        data.todo = self.todo_collector.collect()

        # 采集 Git 数据
        if self.git_collector:
            data.git = self.git_collector.get_commits(date)

        # 采集邮件数据
        if self.email_config and self.email_collector is None:
            try:
                self.email_collector = EmailCollector(
                    email_address=self.email_config["address"],
                    auth_code=self.email_config["auth_code"],
                    provider=self.email_config.get("provider", "163"),
                )
            except Exception as e:
                print(f"邮件采集器初始化失败: {e}")

        if self.email_collector:
            try:
                with self.email_collector:
                    data.email = self.email_collector.get_stats(date)
            except Exception as e:
                print(f"邮件数据采集失败: {e}")

        # 历史对比
        if include_comparison:
            data.comparison = self._generate_comparison(data, date)

        return data

    def _generate_comparison(self, current_data: CollectedData, date: str) -> dict:
        """生成历史对比数据"""
        comparison = {}

        try:
            current_date = datetime.strptime(date, "%Y-%m-%d")

            # 与昨日对比
            yesterday = (current_date - timedelta(days=1)).strftime("%Y-%m-%d")
            yesterday_data = self._collect_light(yesterday)

            comparison["yesterday"] = {
                "git_commits": {
                    "current": current_data.git.total_commits,
                    "previous": yesterday_data.git.total_commits,
                    "change": current_data.git.total_commits - yesterday_data.git.total_commits,
                },
                "todo_completed": {
                    "current": current_data.todo.completed,
                    "previous": yesterday_data.todo.completed,
                    "change": current_data.todo.completed - yesterday_data.todo.completed,
                },
            }

        except Exception:
            pass

        return comparison

    def _collect_light(self, date: str) -> CollectedData:
        """轻量采集(仅 Git 和记忆)"""
        data = CollectedData(
            date=date,
            collected_at=datetime.now(),
        )

        if self.git_collector:
            data.git = self.git_collector.get_commits(date)

        data.memory = self.memory_collector.collect(date)

        return data

第五章|工作分析与报告生成模块

本章节整合了工作分析引擎和报告生成器的完整实现,是日报生成器的核心处理层。

5.1 数据源采集说明

脚本会自动采集以下数据

数据源

采集方式

配置位置

Git 仓库

git log 命令

仓库路径: D:/Download/jiuwenclaw

网易邮箱

IMAP 协议

.env: EMAIL_ADDRESS, EMAIL_TOKEN

记忆系统

读取 MD 文件

workspace/agent/memory/YYYY-MM-DD.md

待办事项

解析 todo.md

workspace/session/*/todo.md

5.2 执行方式

重要:本技能通过执行 Python 脚本来采集数据,必须使用 mcp_exec_command 执行:

# 生成日报
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py daily --save

# 生成周报
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py weekly --save

# 生成月报
cd D:/Download/jiuwenclaw && python workspace/agent/skills/daily-report/run_report.py monthly --save

5.3 效率指标数据结构

# analyzers/work_analyzer.py
# -*- coding: utf-8 -*-
"""
工作分析引擎

功能:
- 效率指标计算
- 趋势对比分析
- 关键词提取
- 智能摘要生成
"""

import re
from collections import Counter
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any, Optional


@dataclass
class EfficiencyMetrics:
    """效率指标"""

    # 任务指标
    task_completion_rate: float = 0.0  # 任务完成率
    tasks_completed: int = 0  # 已完成任务数
    tasks_total: int = 0  # 总任务数

    # Git 指标
    commit_count: int = 0  # 提交次数
    files_changed: int = 0  # 修改文件数
    lines_added: int = 0  # 新增行数
    lines_deleted: int = 0  # 删除行数
    net_lines: int = 0  # 净增行数

    # 沟通指标
    emails_received: int = 0  # 收到邮件
    emails_sent: int = 0  # 发送邮件

    # 综合指标
    productivity_score: float = 0.0  # 生产力得分 (0-100)
    focus_score: float = 0.0  # 专注度得分 (0-100)

    def to_dict(self) -> dict:
        return {
            "task_completion_rate": round(self.task_completion_rate, 2),
            "tasks_completed": self.tasks_completed,
            "tasks_total": self.tasks_total,
            "commit_count": self.commit_count,
            "files_changed": self.files_changed,
            "lines_added": self.lines_added,
            "lines_deleted": self.lines_deleted,
            "net_lines": self.net_lines,
            "emails_received": self.emails_received,
            "emails_sent": self.emails_sent,
            "productivity_score": round(self.productivity_score, 2),
            "focus_score": round(self.focus_score, 2),
        }

5.4 生产力得分计算逻辑

class WorkAnalyzer:
    """工作分析引擎"""

    # 停用词列表
    STOPWORDS = {
        "的", "了", "是", "在", "我", "有", "和", "就", "不", "人", "都", "一",
        "上", "也", "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看",
        "自己", "这", "那", "什么", "这个", "那个", "可以", "然后", "还是", "但是",
        "如果", "因为", "所以", "或者", "而且", "已经", "可能", "应该", "需要",
    }

    def _calculate_productivity_score(self, metrics: EfficiencyMetrics) -> float:
        """
        计算生产力得分

        评分规则:
        1. 任务完成贡献(最高 40 分)
           - 完成率 * 40

        2. 代码贡献(最高 30 分)
           - 提交次数:每次 5 分,最高 15 分
           - 代码行数:每 50 行 1 分,最高 15 分

        3. 沟通贡献(最高 20 分)
           - 发送邮件:每封 2 分,最高 10 分
           - 收到邮件:每 5 封 1 分,最高 10 分

        4. 活跃度贡献(最高 10 分)
           - 有任何产出即得 10 分
        """
        score = 0.0

        # 任务完成贡献(最高 40 分)
        score += metrics.task_completion_rate * 40

        # 代码贡献(最高 30 分)
        code_score = min(metrics.commit_count * 5, 15)
        code_score += min(metrics.net_lines / 50, 15)
        score += code_score

        # 沟通贡献(最高 20 分)
        communication_score = min(metrics.emails_sent * 2, 10)
        communication_score += min(metrics.emails_received / 5, 10)
        score += communication_score

        # 活跃度贡献(最高 10 分)
        if metrics.commit_count > 0 or metrics.tasks_completed > 0:
            score += 10

        return min(score, 100.0)

    def _calculate_focus_score(self, metrics: EfficiencyMetrics) -> float:
        """
        计算专注度得分

        评分规则:
        1. 基础分 100 分
        2. 未完成任务扣分:每个扣 5 分,最高扣 30 分
        3. 提交频繁度加分:合理提交(≤5次)加 10 分
        4. 邮件干扰扣分:超过 20 封邮件,每封扣 0.5 分,最高扣 20 分
        """
        score = 100.0

        # 任务未完成扣分
        pending_tasks = metrics.tasks_total - metrics.tasks_completed
        score -= min(pending_tasks * 5, 30)

        # 提交频繁度(适中最优)
        if metrics.commit_count > 0:
            if metrics.commit_count <= 5:
                score += 10
            elif metrics.commit_count > 10:
                score -= 5

        # 邮件干扰扣分
        if metrics.emails_received > 20:
            score -= min((metrics.emails_received - 20) * 0.5, 20)

        return max(score, 0.0)

5.5 报告生成器核心代码

# generators/report_generator.py
# -*- coding: utf-8 -*-
"""
报告生成器

支持:
- 日报生成
- 周报生成(聚合一周数据)
- 月报生成(聚合一月数据)
"""

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional

from ..analyzers.work_analyzer import AnalysisResult, WorkAnalyzer
from ..collectors.aggregator import CollectedData, DataAggregator


@dataclass
class ReportConfig:
    """报告配置"""

    report_type: str = "daily"  # daily, weekly, monthly
    date: str = ""
    include_trends: bool = True
    include_suggestions: bool = True
    output_format: str = "markdown"


class ReportGenerator:
    """报告生成器"""

    def __init__(
        self,
        data_aggregator: DataAggregator,
        work_analyzer: Optional[WorkAnalyzer] = None,
    ):
        self.data_aggregator = data_aggregator
        self.work_analyzer = work_analyzer or WorkAnalyzer()

    def generate_daily(self, date: Optional[str] = None) -> str:
        """生成日报"""
        if date is None:
            date = datetime.now().strftime("%Y-%m-%d")

        # 采集数据
        data = self.data_aggregator.collect(date, include_comparison=True)

        # 分析数据
        analysis = self.work_analyzer.analyze(data.to_dict())

        # 生成报告
        return self._render_daily_report(data, analysis)

    def generate_weekly(self, end_date: Optional[str] = None) -> str:
        """生成周报"""
        if end_date is None:
            end_date = datetime.now()
        else:
            end_date = datetime.strptime(end_date, "%Y-%m-%d")

        start_date = end_date - timedelta(days=6)
        start_str = start_date.strftime("%Y-%m-%d")
        end_str = end_date.strftime("%Y-%m-%d")

        # 采集一周数据
        week_data = self.data_aggregator.collect_week(end_str)

        # 聚合周数据
        aggregated = self._aggregate_week_data(week_data)

        return self._render_weekly_report(aggregated, start_str, end_str)

    def _render_daily_report(self, data: CollectedData, analysis: AnalysisResult) -> str:
        """渲染日报 Markdown"""
        lines = [
            f"# 📋 工作日报 - {data.date}",
            "",
            "## 📊 今日概览",
            "",
            "| 指标 | 数值 |",
            "|------|------|",
            f"| 提交次数 | {analysis.metrics.commit_count} |",
            f"| 任务完成 | {analysis.metrics.tasks_completed}/{analysis.metrics.tasks_total} |",
            f"| 代码变更 | +{analysis.metrics.lines_added}/-{analysis.metrics.lines_deleted} |",
            f"| 生产力得分 | {analysis.metrics.productivity_score:.1f} |",
            "",
        ]

        # 已完成任务
        completed_tasks = [t for t in data.todo.tasks if t.status == "completed"]
        if completed_tasks:
            lines.extend(["## ✅ 已完成任务", ""])
            for task in completed_tasks[:10]:
                lines.append(f"- {task.content}")
            lines.append("")

        # Git 提交记录
        if data.git.commits:
            lines.extend([
                "## 💻 代码提交",
                "",
                "| 时间 | 提交信息 | 变更 |",
                "|------|----------|------|",
            ])
            for commit in data.git.commits[:10]:
                time_str = commit.date.strftime("%H:%M") if commit.date else "-"
                lines.append(
                    f"| {time_str} | {commit.message[:40]} | "
                    f"+{commit.insertions}/-{commit.deletions} |"
                )
            lines.append("")

        # 趋势对比
        if analysis.trends.vs_yesterday:
            lines.extend(["## 📈 趋势对比", ""])
            vs_y = analysis.trends.vs_yesterday
            if "commits" in vs_y:
                change = vs_y["commits"]["change"]
                symbol = "↑" if change > 0 else "↓" if change < 0 else "→"
                lines.append(f"- 提交: {symbol} {abs(change)} 次")
            lines.append("")

        # 工作建议
        if analysis.suggestions:
            lines.extend(["## 💡 工作建议", ""])
            for i, suggestion in enumerate(analysis.suggestions, 1):
                lines.append(f"{i}. {suggestion}")
            lines.append("")

        return "\n".join(lines)

5.6 分析模块初始化文件

# analyzers/__init__.py
# -*- coding: utf-8 -*-
"""
进阶版日报生成器 - 分析模块

包含:
- WorkAnalyzer: 工作分析引擎
- EfficiencyMetrics: 效率指标
- TrendComparison: 趋势对比
- AnalysisResult: 分析结果
"""

from .work_analyzer import (
    WorkAnalyzer,
    EfficiencyMetrics,
    TrendComparison,
    AnalysisResult,
)

__all__ = [
    "WorkAnalyzer",
    "EfficiencyMetrics",
    "TrendComparison",
    "AnalysisResult",
]

5.7 生成模块初始化文件

# generators/__init__.py
# -*- coding: utf-8 -*-
"""
报告生成模块

支持:
- 日报生成
- 周报生成
- 月报生成
"""

from .report_generator import ReportGenerator, ReportConfig

__all__ = ["ReportGenerator", "ReportConfig"]

5.8 入口脚本 (run_report.py)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
日报/周报/月报生成入口脚本(独立版)

使用方式:
    python run_report.py daily [date]           # 生成日报
    python run_report.py weekly [end_date]      # 生成周报
    python run_report.py monthly [year] [month] # 生成月报
"""

import argparse
import io
import os
import sys
import subprocess
from datetime import datetime
from pathlib import Path

# 修复 Windows 编码问题
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")

# 获取脚本所在目录
SKILL_DIR = Path(__file__).parent
PROJECT_ROOT = SKILL_DIR.parent.parent.parent.parent


def collect_git_stats(date: str = None) -> dict:
    """采集 Git 提交统计"""
    if date is None:
        date = datetime.now().strftime("%Y-%m-%d")

    try:
        result = subprocess.run(
            ["git", "-C", str(PROJECT_ROOT), "log",
             f"--since={date} 00:00:00",
             f"--until={date} 23:59:59",
             "--format=%H|%s|%an|%ai",
             "--numstat"],
            capture_output=True,
            text=True,
            encoding="utf-8",
            timeout=30
        )

        commits = []
        total_insertions = 0
        total_deletions = 0

        if result.stdout:
            current_commit = None
            for line in result.stdout.strip().split("\n"):
                if "|" in line and len(line.split("|")) >= 4:
                    parts = line.split("|")
                    if current_commit:
                        commits.append(current_commit)
                    current_commit = {
                        "hash": parts[0][:8],
                        "message": parts[1],
                        "author": parts[2],
                        "insertions": 0,
                        "deletions": 0
                    }
                elif current_commit and "\t" in line:
                    stat_parts = line.split("\t")
                    if len(stat_parts) >= 2:
                        try:
                            ins = int(stat_parts[0]) if stat_parts[0] != "-" else 0
                            dels = int(stat_parts[1]) if stat_parts[1] != "-" else 0
                            current_commit["insertions"] += ins
                            current_commit["deletions"] += dels
                            total_insertions += ins
                            total_deletions += dels
                        except ValueError:
                            pass

            if current_commit:
                commits.append(current_commit)

        return {
            "total_commits": len(commits),
            "total_insertions": total_insertions,
            "total_deletions": total_deletions,
            "commits": commits
        }
    except Exception as e:
        return {"error": str(e)}


def generate_daily_report(date: str = None) -> str:
    """生成日报"""
    if date is None:
        date = datetime.now().strftime("%Y-%m-%d")

    # 采集 Git 数据
    git_stats = collect_git_stats(date)

    # 读取记忆文件
    memory_file = PROJECT_ROOT / "workspace" / "agent" / "memory" / f"{date}.md"
    memory_content = ""
    work_items = []

    if memory_file.exists():
        memory_content = memory_file.read_text(encoding="utf-8")
        for line in memory_content.split("\n"):
            stripped = line.strip()
            if stripped.startswith("-") or stripped.startswith("*"):
                item = stripped.lstrip("-* ").strip()
                if item and not item.startswith("<!--"):
                    work_items.append(item)

    # 查找 todo 文件
    todo_file = None
    session_dir = PROJECT_ROOT / "workspace" / "session"
    if session_dir.exists():
        todo_files = list(session_dir.rglob("todo.md"))
        if todo_files:
            todo_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
            todo_file = todo_files[0]

    # 解析 todo
    completed_tasks = []
    pending_tasks = []

    if todo_file and todo_file.exists():
        todo_content = todo_file.read_text(encoding="utf-8")
        import re
        for line in todo_content.split("\n"):
            stripped = line.strip()
            # Checkbox 格式
            match = re.match(r"-\s*\[([xX ])\]\s*(.+)", stripped)
            if match:
                checked = match.group(1).lower() == "x"
                task = match.group(2).strip()
                if checked:
                    completed_tasks.append(task)
                else:
                    pending_tasks.append(task)

    # 生成报告
    lines = [
        f"# 📋 工作日报 - {date}",
        "",
        "## 📊 今日概览",
        "",
        "| 指标 | 数值 |",
        "|------|------|",
        f"| 代码提交 | {git_stats.get('total_commits', 0)} 次 |",
        f"| 代码变更 | +{git_stats.get('total_insertions', 0)}/-{git_stats.get('total_deletions', 0)} |",
        f"| 已完成任务 | {len(completed_tasks)} 项 |",
        f"| 进行中 | {len(pending_tasks)} 项 |",
        "",
    ]

    # 已完成任务
    if completed_tasks:
        lines.extend(["## ✅ 已完成任务", ""])
        for task in completed_tasks[:10]:
            lines.append(f"- {task}")
        lines.append("")

    # 代码提交
    if git_stats.get("commits"):
        lines.extend([
            "## 💻 代码提交",
            "",
            "| 时间 | 提交信息 | 变更 |",
            "|------|----------|------|",
        ])
        for commit in git_stats["commits"][:10]:
            lines.append(
                f"| {commit.get('hash', '-')} | {commit.get('message', '-')[:40]} | "
                f"+{commit.get('insertions', 0)}/-{commit.get('deletions', 0)} |"
            )
        lines.append("")

    # 工作记录
    if work_items:
        lines.extend(["## 📝 今日工作记录", ""])
        for item in work_items[:10]:
            lines.append(f"- {item}")
        lines.append("")

    # 明日计划
    lines.extend(["## 🔜 明日计划", ""])
    if pending_tasks:
        for task in pending_tasks[:5]:
            lines.append(f"- {task}")
    else:
        lines.append("- 待补充")
    lines.append("")

    return "\n".join(lines)


def generate_monthly_report(year: int = None, month: int = None) -> str:
    """生成月报"""
    now = datetime.now()
    if year is None:
        year = now.year
    if month is None:
        month = now.month

    import calendar
    _, days_in_month = calendar.monthrange(year, month)

    # 采集整月数据
    total_commits = 0
    total_insertions = 0
    total_deletions = 0
    active_days = 0

    for day in range(1, days_in_month + 1):
        date = f"{year:04d}-{month:02d}-{day:02d}"
        stats = collect_git_stats(date)
        commits = stats.get("total_commits", 0)
        total_commits += commits
        total_insertions += stats.get("total_insertions", 0)
        total_deletions += stats.get("total_deletions", 0)
        if commits > 0:
            active_days += 1

    # 生成报告
    lines = [
        f"# 📋 工作月报 - {year}年{month}月",
        "",
        "## 📊 本月概览",
        "",
        "| 指标 | 数值 |",
        "|------|------|",
        f"| 活跃天数 | {active_days}/{days_in_month} 天 |",
        f"| 代码提交 | {total_commits} 次 |",
        f"| 代码变更 | +{total_insertions}/-{total_deletions} |",
        "",
        "## 📝 工作总结",
        "",
        f"本月共完成 {total_commits} 次代码提交,",
        f"净增代码 {total_insertions - total_deletions} 行。",
        "",
        "## 🔜 下月计划",
        "",
        "- 继续完善项目功能",
        "",
    ]

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="日报/周报/月报生成器")
    parser.add_argument(
        "type",
        choices=["daily", "weekly", "monthly"],
        help="报告类型: daily(日报), weekly(周报), monthly(月报)"
    )
    parser.add_argument("--date", "-d", help="日期 (YYYY-MM-DD)")
    parser.add_argument("--year", "-y", type=int, help="年份")
    parser.add_argument("--month", "-m", type=int, help="月份")
    parser.add_argument("--save", "-s", action="store_true", help="保存到文件")

    args = parser.parse_args()

    try:
        if args.type == "daily":
            date = args.date or datetime.now().strftime("%Y-%m-%d")
            print(f"正在生成日报 ({date})...", file=sys.stderr)
            content = generate_daily_report(date)
            date_str = date

        elif args.type == "weekly":
            date = args.date or datetime.now().strftime("%Y-%m-%d")
            print(f"正在生成周报 (截至 {date})...", file=sys.stderr)
            # 周报暂时用日报代替
            content = generate_daily_report(date)
            date_str = date

        elif args.type == "monthly":
            now = datetime.now()
            year = args.year or now.year
            month = args.month or now.month
            print(f"正在生成月报 ({year}年{month}月)...", file=sys.stderr)
            content = generate_monthly_report(year, month)
            date_str = f"{year:04d}-{month:02d}"

        # 输出结果
        print("\n" + "=" * 50)
        print(content)
        print("=" * 50)

        # 保存文件
        if args.save:
            reports_dir = PROJECT_ROOT / "workspace" / "agent" / "reports"
            reports_dir.mkdir(parents=True, exist_ok=True)
            filepath = reports_dir / f"{args.type}-{date_str}.md"
            filepath.write_text(content, encoding="utf-8")
            print(f"\n报告已保存到: {filepath}", file=sys.stderr)

    except Exception as e:
        print(f"错误: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()

第六章|配置与部署

6.1 环境变量配置 (.env)

本项目实际使用的 .env 配置:

# 模型配置
MODEL_PROVIDER="OpenAI"
MODEL_NAME="Qwen/Qwen3-235B-A22B-Instruct-2507"
API_BASE="https://api-inference.modelscope.cn/v1"
API_KEY="ms*********"

# 邮箱配置(网易163邮箱)
# 注意:EMAIL_TOKEN 使用的是邮箱授权码,不是登录密码
# 获取方式:登录163邮箱 → 设置 → POP3/SMTP/IMAP → 开启IMAP服务 → 获取授权码
EMAIL_ADDRESS=******@163.com
EMAIL_TOKEN=******
EMAIL_PROVIDER=163

# Embedding 配置(可选,用于记忆系统向量化)
EMBED_API_KEY=
EMBED_API_BASE=
EMBED_MODEL=

# 其他API配置
JINA_API_KEY=
SERPER_API_KEY=
PERPLEXITY_API_KEY=

邮箱配置说明

  • EMAIL_ADDRESS:完整的邮箱地址
  • EMAIL_TOKEN:邮箱授权码(非登录密码),需要在邮箱设置中开启 IMAP 服务后获取
  • EMAIL_PROVIDER:邮箱提供商,目前支持 163126yeah 三个网易邮箱

6.2 心跳配置 (HEARTBEAT.md)

# 心跳任务

在此文件中配置需要 JiuwenClaw 周期性执行的任务。

---

## 活跃的任务项

<!-- 在此行之后添加待执行任务,每行一条,以 "- " 开头 -->

- 生成今日工作日报

<!-- 周报任务(每周五触发) -->
<!-- - 生成本周工作周报 -->

<!-- 月报任务(每月末触发) -->
<!-- - 生成本月工作月报 -->

---

## 已完成的任务项

<!-- 将已完成的任务移动到此段或删除 -->

---

## 任务说明

### 日报任务
- **触发时间**: 每天 18:00 - 18:30(根据 config.yaml 配置)
- **推送目标**: 飞书
- **内容**: 今日 Git 提交、任务完成情况、邮件统计、工作效率分析

### 周报任务
- **触发时间**: 每周五 18:00 - 18:30
- **推送目标**: 飞书
- **内容**: 本周数据聚合、趋势分析、下周计划

### 月报任务
- **触发时间**: 每月最后一天 18:00 - 18:30
- **推送目标**: 飞书
- **内容**: 本月数据聚合、成果总结、下月计划

---

## 配置方式

修改 `config/config.yaml` 中的 `heartbeat` 配置:

```yaml
heartbeat:
  every: 3600              # 心跳间隔(秒)
  target: feishu           # 推送目标
  active_hours:
    start: 18:00           # 生效开始时间
    end: 18:30             # 生效结束时间

修改后重启服务生效。


### 6.3 Git 仓库配置
本项目监控的 Git 仓库(脚本会自动读取):

```plain
仓库路径: D:/Download/jiuwenclaw

采集方式:脚本通过 git log 命令采集数据,无需额外配置。

采集内容

  • 提交哈希(commit hash)
  • 提交信息(commit message)
  • 提交作者、提交时间
  • 文件变更数、新增行数、删除行数

执行命令

# 脚本内部执行的 git 命令
git -C D:/Download/jiuwenclaw log --since="2026-03-07 00:00:00" --until="2026-03-07 23:59:59" --format="%H|%s|%an|%ai" --numstat

多仓库支持:如需监控多个仓库,可扩展 DataAggregator

# 扩展配置示例(需自行实现)
git_repos:
  - path: "D:/Download/jiuwenclaw"
    name: "jiuwenclaw"
  - path: "D:/Projects/another-repo"
    name: "another-project"

6.4 飞书频道配置 (config.yaml)

本项目实际使用的 config.yaml 飞书配置:

heartbeat:
  # 心跳间隔(秒),默认 3600 (1小时)
  every: 3600
  # 心跳结果回传的 channel
  target: feishu
  # 心跳生效时间段(本地时间)
  # 18:00-18:30 期间会触发日报生成
  active_hours:
    start: 18:00
    end: 18:30

channels:
  feishu:
    # 飞书应用配置
    # 获取方式:飞书开放平台 → 创建企业自建应用 → 获取 App ID 和 App Secret
    app_id: cli_a92035b1823a9cd2
    app_secret: *****
    encrypt_key:        # 加密 key(可选)
    verification_token: # 验证 token(可选)
    allow_from:         # IP 白名单(可选)
    enabled: true

飞书应用配置步骤

  1. 访问 飞书开放平台
  1. 创建企业自建应用,获取 app_idapp_secret
  1. 添加「机器人」能力
  1. 配置事件订阅:im.message.receive_v1
  1. 发布应用到所有成员

第七章|测试验证

7.1 测试数据采集器

# 测试 Git 采集(采集指定日期的提交记录)
cd D:\Download\jiuwenclaw
python workspace/agent/skills/daily-report/run_report.py daily --date 2026-03-07

# 测试月报生成(采集整月数据)
python workspace/agent/skills/daily-report/run_report.py monthly --year 2026 --month 3

# 测试保存到文件
python workspace/agent/skills/daily-report/run_report.py daily --save

7.2 完整测试流程

步骤1:创建待办清单(测试待办数据采集)

在飞书或 Web 前端发送:

帮我创建一个待办清单:
1. 完成日报生成器技能开发
2. 实现Git提交数据采集模块
3. 实现邮箱统计数据采集模块
4. 配置飞书频道推送
5. 测试心跳触发功能
6. 编写开发文档

步骤2:模拟工作记录(测试记忆数据采集)
帮我记录今天的工作:
- 上午完成了 SKILL.md 技能定义文件编写
- 创建了 Git 提交采集器 git_collector.py
- 创建了邮箱统计采集器 email_collector.py
- 创建了记忆数据采集器 memory_collector.py
- 创建了待办事项采集器 todo_collector.py
- 下午完成了工作分析引擎 work_analyzer.py
- 实现了报告生成器 report_generator.py
- 配置了心跳和飞书推送
- 进行了功能测试和调试

步骤3:提交代码(测试 Git 数据采集)
# 在项目中提交一些代码,用于测试 Git 采集
git add .
git commit -m "feat: 添加日报生成器完整功能

- 实现多数据源采集(Git/邮箱/记忆/待办)
- 添加工作分析引擎
- 支持日报/周报/月报生成
- 配置飞书推送和心跳触发"

步骤4:生成日报
生成今日日报

步骤5:生成月报(测试邮箱数据采集)
读取邮箱中本月的内容整理成月报

第八章|扩展方向

8.1 更多数据源

数据源

采集方式

价值

企业微信/钉钉

API

消息沟通统计

日程/日历

CalDAV/iCal

会议时间分析

Jira/飞书任务

API

项目进度跟踪

浏览器历史

本地数据库

工作内容追溯

8.2 更智能分析

  • 工作模式识别:识别高效时段、低效时段
  • 疲劳度预警:基于连续工作时长
  • 时间分配建议:优化任务优先级

8.3 更丰富交互

  • 飞书按钮交互:编辑、重新生成、推送
  • 日报编辑功能:在线修改后保存
  • 审批流程:Leader 审阅确认

写在最后

从最初的一个简单想法——"能不能让 AI 帮我写日报",到如今这套完整的多数据源日报生成系统,这个项目经历了多次迭代和优化。

开发过程中遇到的最大挑战是 163邮箱的 IMAP 协议适配。网易邮箱的安全限制导致 SELECT 命令返回 "Unsafe Login" 错误,经过反复调试和资料查阅,最终通过以下方案解决:

  1. 注册 ID 命令imaplib.Commands['ID'] = ('NONAUTH', 'AUTH', 'SELECTED')
  1. 发送身份声明:登录后立即发送 ID 命令
  1. 使用 STATUS 命令:绕过 SELECT 限制获取邮件统计

这套系统现在可以:

  • 自动采集 Git 提交、邮件统计、记忆记录、待办事项
  • 生成日报、周报、月报
  • 通过飞书定时推送
  • 读取邮件内容并生成摘要

如果你也在尝试构建类似的 AI Agent 应用,希望这篇文章能给你一些参考。

让 AI Agent 真正成为智能工作助手,从进阶版日报生成器开始。

— JiuwenClaw 进阶版日报生成器开发实践


参考资料

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐