weflow-cli 1.15.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/OPERATIONS.md +16 -1
  3. package/README.en.md +21 -2
  4. package/README.md +34 -6
  5. package/bin/weflow-cli.ts +389 -6
  6. package/dist/bin/weflow-cli.js +422 -8
  7. package/dist/bin/weflow-cli.js.map +1 -1
  8. package/dist/src/core/ntCore.d.ts +47 -0
  9. package/dist/src/core/ntCore.d.ts.map +1 -1
  10. package/dist/src/core/ntCore.js +24 -0
  11. package/dist/src/core/ntCore.js.map +1 -1
  12. package/dist/src/services/assistantService.d.ts +8 -1
  13. package/dist/src/services/assistantService.d.ts.map +1 -1
  14. package/dist/src/services/assistantService.js +47 -8
  15. package/dist/src/services/assistantService.js.map +1 -1
  16. package/dist/src/services/assistantTools.d.ts +8 -0
  17. package/dist/src/services/assistantTools.d.ts.map +1 -1
  18. package/dist/src/services/assistantTools.js +67 -11
  19. package/dist/src/services/assistantTools.js.map +1 -1
  20. package/dist/src/services/configService.d.ts.map +1 -1
  21. package/dist/src/services/configService.js +3 -2
  22. package/dist/src/services/configService.js.map +1 -1
  23. package/dist/src/services/cursorProbe.d.ts +44 -0
  24. package/dist/src/services/cursorProbe.d.ts.map +1 -0
  25. package/dist/src/services/cursorProbe.js +183 -0
  26. package/dist/src/services/cursorProbe.js.map +1 -0
  27. package/dist/src/services/qchatImport.d.ts +89 -0
  28. package/dist/src/services/qchatImport.d.ts.map +1 -0
  29. package/dist/src/services/qchatImport.js +273 -0
  30. package/dist/src/services/qchatImport.js.map +1 -0
  31. package/dist/src/services/setupVenv.d.ts +43 -0
  32. package/dist/src/services/setupVenv.d.ts.map +1 -0
  33. package/dist/src/services/setupVenv.js +198 -0
  34. package/dist/src/services/setupVenv.js.map +1 -0
  35. package/dist/src/utils/python.d.ts +10 -0
  36. package/dist/src/utils/python.d.ts.map +1 -1
  37. package/dist/src/utils/python.js +27 -4
  38. package/dist/src/utils/python.js.map +1 -1
  39. package/docs/DATA_CONTRACT.md +25 -0
  40. package/docs/DECISIONS.md +92 -30
  41. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +365 -365
  42. package/docs/HEALTH-CHECK.md +104 -104
  43. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -310
  44. package/docs/NEXT_ACTIONS.md +236 -236
  45. package/docs/NPM-PUBLISH.md +130 -130
  46. package/docs/POSITIONING.md +68 -68
  47. package/docs/PROJECT_STATE.md +6 -3
  48. package/docs/REPLAY.md +169 -0
  49. package/docs/SYNC_CONTRACT.md +197 -180
  50. package/package.json +89 -88
  51. package/requirements-3x.txt +4 -4
  52. package/requirements-voice.txt +15 -15
  53. package/resources/js/graph3d/NOTICE.txt +35 -35
  54. package/resources/js/graph3d/d3-binarytree.min.js +2 -2
  55. package/resources/js/graph3d/d3-dispatch.min.js +2 -2
  56. package/resources/js/graph3d/d3-force-3d.min.js +2 -2
  57. package/resources/js/graph3d/d3-octree.min.js +2 -2
  58. package/resources/js/graph3d/d3-quadtree.min.js +2 -2
  59. package/resources/js/graph3d/d3-timer.min.js +2 -2
  60. package/resources/js/graph3d/three.min.js +6 -6
  61. package/resources/js/graph3d/viewer2d.js +550 -448
  62. package/resources/panel/quick-menu.cjs +197 -197
  63. package/scripts/_utils.py +1338 -1338
  64. package/scripts/article_notes.py +423 -423
  65. package/scripts/bonds.py +11 -0
  66. package/scripts/chat_notes.py +86 -3
  67. package/scripts/check_readme_commands.py +427 -426
  68. package/scripts/contact_schema.py +339 -339
  69. package/scripts/create_reading_notes.py +11 -11
  70. package/scripts/daily_task.ps1 +213 -177
  71. package/scripts/fav_notes.py +321 -321
  72. package/scripts/graph_2d.py +156 -151
  73. package/scripts/graph_3d.py +672 -646
  74. package/scripts/health_check.py +473 -473
  75. package/scripts/imported_chats.py +68 -0
  76. package/scripts/install_tasks.ps1 +105 -91
  77. package/scripts/leads.py +179 -0
  78. package/scripts/network.py +206 -206
  79. package/scripts/nt_decrypt.py +135 -1
  80. package/scripts/on_this_day.py +178 -178
  81. package/scripts/opinion_timeline.py +138 -138
  82. package/scripts/promises.py +185 -185
  83. package/scripts/replay.py +803 -0
  84. package/scripts/run_hidden.vbs +25 -0
  85. package/scripts/semantic_search.py +73 -5
  86. package/scripts/user_notes.py +315 -315
  87. package/scripts/voice_transcribe.py +139 -139
  88. package/scripts/watch_issues.py +212 -212
  89. package/scripts/watch_mail.py +131 -131
  90. package/src/core/ntCore.ts +59 -0
  91. package/src/services/assistantService.ts +48 -8
  92. package/src/services/assistantTools.ts +73 -10
  93. package/src/services/configService.ts +3 -2
  94. package/src/services/cursorProbe.ts +222 -0
  95. package/src/services/qchatImport.ts +327 -0
  96. package/src/services/setupVenv.ts +238 -0
  97. package/src/services/syncService.ts +363 -363
  98. package/src/services/syncState.ts +275 -275
  99. package/src/services/wechatMedia.ts +166 -166
  100. package/src/utils/python.ts +33 -4
@@ -1,365 +1,365 @@
1
- # WeFlow CLI 功能增强技术方案
2
-
3
- > 状态:提案
4
- >
5
- > 更新时间:2026-09-19
6
- >
7
- > 适用范围:CLI、MCP、本地阅读器、聊天导出、日报、知识库和可选助手
8
-
9
- 本文把 WeFlow CLI 下一阶段的功能改进整理为可拆分的技术方案。它补充 [ROADMAP.md](ROADMAP.md) 的方向说明,面向实现、评审和验收;已经实现的能力以 [PROJECT_STATE.md](PROJECT_STATE.md) 为准。
10
-
11
- ## 1. 产品目标
12
-
13
- WeFlow CLI 已经覆盖本地聊天读取、联系人与收藏、HTML/JSON/TXT/Excel 导出、公众号日报、知识库检索、RAG、只读 MCP 和 Bot 助手。下一阶段不以增加零散命令为主要目标,而是把这些能力连接成四条可恢复、可验证的工作流:
14
-
15
- 1. **同步:** 从本地数据源读取增量变化,并明确哪些数据已覆盖、哪些数据读取失败。
16
- 2. **整理:** 将消息、媒体、文章和笔记写入统一的本地索引,保留原始来源。
17
- 3. **使用:** 通过搜索、阅读器、导出和 MCP 查询这些数据,并能回到原始记录。
18
- 4. **分析:** 在用户选择的范围内调用本地或云端 AI,输出带来源的结果。
19
-
20
- 功能完成的标准不是命令数量,而是用户可以在失败后继续运行、在结果中看到覆盖边界,并能验证 AI 或导出结果来自哪些原始资料。
21
-
22
- ## 2. 现状与功能缺口
23
-
24
- | 能力 | 当前基础 | 需要补齐的功能 |
25
- | --- | --- | --- |
26
- | 本地数据 | 支持会话、联系人、消息、收藏、朋友圈缓存和多种导出;已处理部分消息分片 | 增量同步、稳定游标、覆盖报告、失败分片重试和统一索引 |
27
- | 媒体导出 | 支持图片、表情、视频、链接卡片,部分场景支持语音转写 | 媒体清单、缺失原因、去重、导出断点和可迁移资料包 |
28
- | 日报阅读 | 支持来源筛选、分类、摘要、HTML 阅读器和收藏同步 | 来源健康、抓取重试、文章去重、阅读状态和离线归档 |
29
- | 搜索与知识库 | 支持语义搜索、RAG、Vault 和阅读笔记 | 混合检索、增量索引、引用定位、结果解释和评估集 |
30
- | MCP | 默认只读、有限制、使用版本化消息契约 | 稳定游标、更多只读资源、统一错误码和任务状态查询 |
31
- | 助手 | 有隐私模式、白名单、Bot 通道和确认边界 | 收件箱/发件箱、幂等发送、投递状态和会话级记忆控制 |
32
-
33
- ## 3. 优先级和交付顺序
34
-
35
- ### P0:数据可信和任务可恢复
36
-
37
- 先完成增量同步、统一任务状态和导出覆盖报告。这一阶段解决“有没有读全”“导出到哪一步”“缺的媒体是什么”三个问题。
38
-
39
- ### P1:检索和阅读闭环
40
-
41
- 在可信数据基础上完成混合搜索、引用定位、日报增量抓取和离线阅读。此阶段应让用户能够从一篇 AI 摘要或一个搜索结果返回到原始消息或文章。
42
-
43
- ### P2:受控 AI 和只读生态
44
-
45
- 统一 AI 执行层、MCP 增量接口和助手收发队列。所有云端分析和外发操作继续沿用预览、确认和隐私模式。
46
-
47
- ### P3:迁移和平台扩展
48
-
49
- 最后增加资料包迁移、Markdown/Obsidian 等互操作格式,以及 Linux/macOS 的能力适配。平台扩展必须建立在能力检测和明确降级结果上。
50
-
51
- ## 4. 统一数据同步和索引
52
-
53
- ### 4.1 模块划分
54
-
55
- 建议把同步流程拆成以下模块:
56
-
57
- | 模块 | 责任 |
58
- | --- | --- |
59
- | `SourceAdapter` | 从 NT 数据库、传统数据库、日报目录或文章源读取记录 |
60
- | `Normalizer` | 将不同来源转换为统一记录,保留未知字段 |
61
- | `IdentityResolver` | 生成消息、文章和媒体的稳定身份,拒绝不可靠匹配 |
62
- | `CheckpointStore` | 保存分片、时间窗、游标和失败重试状态 |
63
- | `LocalIndex` | 写入全文索引、向量索引和元数据索引 |
64
- | `CoverageReporter` | 统计读取范围、缺失分片、媒体覆盖和重复记录 |
65
-
66
- ### 4.2 消息身份
67
-
68
- 在稳定游标尚未得到所有后端支持前,使用组合身份并允许重叠读取:
69
-
70
- ```text
71
- messageKey = source + talker + shard + localId + serverId + createTime
72
- ```
73
-
74
- 其中 `serverId` 为非零值时优先使用;`localId` 不能单独作为全局身份。时间窗同步应保留少量重叠范围,由本地去重保证边界消息不丢失。
75
-
76
- ### 4.3 同步状态示例
77
-
78
- 状态文件不得包含密钥、完整聊天正文或无必要的本地绝对路径。可以保存如下信息:
79
-
80
- ```json
81
- {
82
- "schema": "weflow-sync/v1",
83
- "source": "wechat-nt",
84
- "scope": "示例联系人",
85
- "lastSuccessfulRun": "2026-09-19T09:00:00+08:00",
86
- "coveredFrom": "2026-09-01T00:00:00+08:00",
87
- "coveredTo": "2026-09-19T08:59:59+08:00",
88
- "recordsRead": 1200,
89
- "recordsDeduplicated": 14,
90
- "shardsRead": 4,
91
- "shardsFailed": 0,
92
- "mayHaveMore": false,
93
- "warnings": []
94
- }
95
- ```
96
-
97
- ### 4.4 建议入口
98
-
99
- 具体命令名称可以由实现者决定,但应覆盖以下能力:
100
-
101
- - `sync --dry-run --json`:只返回范围、预计记录数和风险标记;
102
- - `sync run`:执行全量或增量同步;
103
- - `sync status --json`:查看最后一次运行、覆盖范围和失败项;
104
- - `sync retry`:只重跑失败分片或失败来源;
105
- - `sync verify`:检查索引与原始数据的时间边界和计数摘要。
106
-
107
- ## 5. 统一任务中心
108
-
109
- 导出、日报、索引、语音转写和知识处理应共享任务模型,而不是各自实现一套进度逻辑。
110
-
111
- ### 5.1 生命周期
112
-
113
- ```text
114
- planned → waiting_confirmation → running → paused
115
- ↘ completed
116
- ↘ failed
117
- ↘ cancelled
118
- ```
119
-
120
- 任务必须具备稳定的 `jobId` 和幂等键。重复提交同一范围的任务时,应返回已有任务或明确创建新任务,不能悄悄重复写入。
121
-
122
- ### 5.2 任务记录
123
-
124
- ```json
125
- {
126
- "schema": "weflow-job/v1",
127
- "jobId": "job_20260919_0001",
128
- "kind": "export.html",
129
- "state": "running",
130
- "createdAt": "2026-09-19T09:00:00+08:00",
131
- "updatedAt": "2026-09-19T09:02:10+08:00",
132
- "progress": { "completed": 800, "total": 1200 },
133
- "resumeable": true,
134
- "result": { "records": 800, "mediaMissing": 3 },
135
- "error": null
136
- }
137
- ```
138
-
139
- 任务输出应区分“已完成”“部分完成”和“失败”。部分完成不能返回成功状态,也不能删除已生成的可用结果。
140
-
141
- ### 5.3 用户体验
142
-
143
- 所有长任务至少提供:
144
-
145
- - 预览和预计影响;
146
- - 进度、当前阶段和耗时;
147
- - 暂停、恢复、取消;
148
- - 失败原因和可重试动作;
149
- - 人类可读输出与机器可读 JSON 输出。
150
-
151
- ## 6. 搜索、引用和知识库
152
-
153
- ### 6.1 混合检索流程
154
-
155
- ```text
156
- 用户查询
157
- → 查询解析(关键词、日期、联系人、会话、消息类型)
158
- → 全文检索 + 向量检索
159
- → 元数据过滤
160
- → 去重和重排
161
- → 片段裁剪
162
- → 引用生成
163
- → AI 回答或结构化结果
164
- ```
165
-
166
- 关键词检索负责精确命中姓名、编号和原句;向量检索负责语义相近内容。两者都必须受到日期、会话和结果数量限制。
167
-
168
- ### 6.2 引用对象
169
-
170
- 每条引用至少包含:
171
-
172
- ```json
173
- {
174
- "sourceType": "wechat-message",
175
- "sourceId": "messageKey",
176
- "talker": "示例联系人",
177
- "createdAt": "2026-09-19T08:30:00+08:00",
178
- "snippet": "经脱敏或用户允许的原文片段",
179
- "locator": { "export": "示例联系人", "part": 1 }
180
- }
181
- ```
182
-
183
- AI 输出中的事实性结论应能回到一个或多个引用;没有引用的内容应标记为推断或建议。原始消息和 AI 生成文本必须分开保存。
184
-
185
- ### 6.3 索引更新
186
-
187
- - 只对新增或变更记录建立索引;
188
- - 内容哈希变化时重建对应条目;
189
- - 删除或失效记录进入 tombstone,避免旧结果继续出现;
190
- - 向量模型变化时支持新旧索引并行切换;
191
- - 建立不含真实聊天内容的评估集,测试召回率、引用准确率和重复率。
192
-
193
- ## 7. 导出资料包和媒体覆盖
194
-
195
- ### 7.1 导出配置
196
-
197
- 导出应从单一命令扩展为可保存的配置:
198
-
199
- - 会话或联系人范围;
200
- - 日期范围;
201
- - 输出格式;
202
- - 是否包含图片、表情、视频、语音和远程封面;
203
- - 是否脱敏;
204
- - 每页条数和媒体大小上限;
205
- - 离线模式或允许远程读取。
206
-
207
- ### 7.2 媒体清单
208
-
209
- 每个媒体项应记录状态,而不是只在页面中显示占位符:
210
-
211
- ```json
212
- {
213
- "mediaKey": "content-or-message-identity",
214
- "kind": "image",
215
- "status": "embedded",
216
- "mime": "image/jpeg",
217
- "bytes": 123456,
218
- "sha256": "redacted-example",
219
- "reason": null
220
- }
221
- ```
222
-
223
- 推荐状态:`embedded`、`cached`、`remote-fetched`、`missing`、`untrusted-match`、`unsupported`。无法可靠匹配时必须保留缺失原因,不能用相邻消息或裸 `localId` 猜测媒体。
224
-
225
- ### 7.3 离线资料包
226
-
227
- 资料包建议包含:
228
-
229
- ```text
230
- manifest.json
231
- messages.json
232
- index.html
233
- media/
234
- attachments/
235
- README.txt
236
- ```
237
-
238
- `manifest.json` 保存数据范围、生成时间、版本、覆盖统计和脱敏选项;不得保存数据库密钥、完整本地路径或未授权的配置内容。
239
-
240
- ## 8. 日报和本地阅读器
241
-
242
- ### 8.1 来源状态
243
-
244
- 每个公众号或文章来源应有本地状态:启用、停用、最近成功时间、最近失败原因、连续失败次数和最后抓取文章时间。状态用于提醒用户,不应自动扩大抓取范围。
245
-
246
- ### 8.2 文章去重
247
-
248
- 优先使用文章 URL,其次使用来源、标题、发布时间和正文哈希组合判断重复。重复文章应保留来源关系,但不重复生成日报和向量索引。
249
-
250
- ### 8.3 阅读状态
251
-
252
- 阅读器可以增加未读、已读、稍后阅读、收藏和笔记关联。所有状态保存在本地,刷新、重新生成 HTML 或重启服务后仍能恢复。
253
-
254
- ## 9. 受控 AI 执行层
255
-
256
- 统一 AI 配置应至少包含:
257
-
258
- | 配置 | 作用 |
259
- | --- | --- |
260
- | provider/model | 选择云端或本地模型 |
261
- | privacyMode | `strict`、`balanced`、`local` |
262
- | inputLimit | 限制消息数量、字符数或 token 数 |
263
- | timeLimit | 限制单任务耗时 |
264
- | budget | 限制调用次数或费用 |
265
- | outputSchema | 约束结构化输出 |
266
- | citationRequired | 要求返回引用 |
267
-
268
- AI 调用前应生成不含正文的预览,说明数据范围、是否联网、预计规模和写入位置。云端调用失败时,保留本地抓取和无 AI 结果,不把整个工作流判定为不可用。
269
-
270
- ## 10. MCP 和助手功能
271
-
272
- ### 10.1 MCP 只读扩展
273
-
274
- 优先扩展以下只读能力:
275
-
276
- - 会话发现和唯一名称解析;
277
- - 分页读取消息和覆盖状态;
278
- - 联系人、收藏、日报和知识条目查询;
279
- - 导出任务创建、查询和结果摘要;
280
- - 能力、契约和限制发现。
281
-
282
- 分页建议使用不透明游标,响应带 `nextCursor`、`returnedCount`、`mayHaveMore` 和请求范围。错误响应使用稳定错误码,避免让客户端解析中文错误文本。
283
-
284
- ### 10.2 Bot 助手收发队列
285
-
286
- 助手只围绕已验证的 Bot 通道增强,不扩展为个人微信账号自动化。建议引入:
287
-
288
- - 入站事件 ID 和去重表;
289
- - 出站消息队列;
290
- - `queued`、`sent`、`delivered`、`failed` 状态;
291
- - 重试次数和最后失败原因;
292
- - 会话级记忆开关和数据范围;
293
- - 外发前的预览和确认。
294
-
295
- ## 11. 验收指标
296
-
297
- 每个阶段至少记录以下指标:
298
-
299
- | 指标 | 目标 |
300
- | --- | --- |
301
- | 增量同步重复率 | 重复读取可被本地去重,不产生重复索引 |
302
- | 分片覆盖率 | 可报告成功、失败和未扫描分片 |
303
- | 媒体错配率 | 可靠匹配失败时宁缺毋滥,禁止跨会话错配 |
304
- | 导出可恢复率 | 中断后从最近检查点继续,不必从头开始 |
305
- | 搜索引用准确率 | 引用能定位到实际返回片段 |
306
- | 日报重复率 | 同一文章重复运行不重复生成内容 |
307
- | AI 数据边界 | 每次云端调用均可说明输入范围和隐私模式 |
308
- | MCP 响应边界 | 结果数量、时间范围和字段均受限制 |
309
-
310
- ## 12. 风险和明确不做的事
311
-
312
- - 不使用裸 `localId`、相邻记录或模糊名称猜测消息和媒体身份。
313
- - 不把数据库路径、密钥、完整聊天正文或任意本地目录暴露给 MCP 客户端或合作应用。
314
- - 不在默认工作流中加入个人微信账号自动发消息、拉群、群管理或未验证的客户端自动化。
315
- - 不把云端 AI 作为日报、导出和本地搜索的必需依赖。
316
- - 不在没有迁移方案、兼容性测试和回滚方式时修改现有 JSON 数据契约。
317
-
318
- ## 13. 建议的首轮开发任务
319
-
320
- 1. 定义 `weflow-sync/v1` 和 `weflow-job/v1` 两个内部状态契约。
321
- 2. 为消息同步增加检查点、重叠时间窗和覆盖摘要。
322
- 3. 为 HTML/JSON 导出增加媒体覆盖报告和部分完成状态。
323
- 4. 将导出、日报和索引任务接入统一任务状态查询。
324
- 5. 为搜索结果增加消息/文章引用对象,不改变现有原始数据字段。
325
- 6. 为 MCP 增加不透明游标和稳定错误码。
326
- 7. 使用合成数据建立同步、搜索、导出和 MCP 的端到端验收样例。
327
- 8. 让**参数解析错误**在 `--json` 下也走 JSON。现在漏了必需选项时 commander 直接往 stderr 打一句、stdout 是**空的**
328
- (2026-10-12 用"未配置状态驱动全部 `--json` 叶子命令"量到:83 条里 2 条命中,`scene add` / `scene bind`)。
329
- 做法大致是给 `program` 装一个会抛的出口(`exitOverride` + `configureOutput`),捕获后若 argv 里有 `--json`
330
- 就发 `{success:false, code:'INVALID_ARGUMENT', error:'<commander 那句>'}`。**代价**是 `--help` / `--version`
331
- 也走同一条出口,而全库的 `--help` 调用面很宽(含 `scripts/check_readme_commands.py` 那 277+ 条文档命令),
332
- 所以要先有一条覆盖 `--help`/`--version` 的判据再动 —— 否则把帮助信息弄坏的后果比这点收益大得多。
333
- 9. ~~**markdown 导出(连图一起)**~~ **已做(2026-10-13)**:`export … md` 走 Python 那条路
334
- (`--format md`),图片落盘到 `<输出>/<会话>_assets/`、正文用相对路径引用,并写一份 `manifest.json`;
335
- 同目录里再导出 `json`/`txt`/`excel` 会自动带上同样的路径(`media` 字段 / `[图片: …]` / 「媒体」列)。
336
- 实现上**没有**在 TS 里重写匹配逻辑 —— 那样两份实现必然漂移。
337
- 落地时踩到两件值得记住的事:清单原本按 `local_id` 做键,而**它在分片之间会重复**(1,914 条里 763 条
338
- 撞上 522 个条目 ⇒ 非 md 格式会引用到别人的图),改成 `local_id:create_time` 后四种格式一致在 556;
339
- 以及 **Python 失败时 TS 回退成简版 HTML、CLI 仍打印「✓ 导出成功」**(失败伪装成成功)—— md 现在没有
340
- 静默回退,HTML 的回退会往 stderr 说明它丢了图片。
341
- 10. ~~**导出文件本体(`.pdf`/`.docx`/`.xlsx`…)**~~ **已做(2026-10-13)**:`md` 与 `json|txt|excel --media`
342
- 会把文档本体**按原名与扩展名**拷进 `<输出>/<会话>_assets/files/`,md 正文按原名链接到 `files/` 下,
343
- 其余格式经同一份清单带上(`mediaKind`/`mediaName`、`[文件: …]`、「媒体」列)。
344
- "消息 → 文件"的链路实测清楚了,与图片那套**不同**:文件消息(appmsg `type=6`)只有名字/md5/大小,
345
- 本体在账号目录 `msg/file/YYYY-MM/` 下且**与 talker 无关**(按月分目录、目录里没有会话痕迹),
346
- 所以索引是**只 stat 的** `(归一化名, 字节数)`,多候选才用 md5 消歧(磁盘名常带微信加的 `(1)`)。
347
- 本机实测 1,855 条文件消息,1,489 条唯一命中 + 154 条 md5 认下 = **88.6%**,212 条本地没有
348
- (未下载/已清理)—— 不猜,计数照实报。
349
- 两处坑:`<refermsg>`(引用原文)嵌在 `<appmsg>` **内部**,满文档搜首个同名标签会把引用里的
350
- `<type>6</type>` 当成这条消息的文件(首次测量 7,083 条"像文件"里绝大多数是引用)⇒ 改为只取
351
- `<appmsg>` 的**直接子元素**;以及"新拷贝数"被当成"导出数"(第二次跑打印「0 document(s)」而 67 份
352
- 就在磁盘上)⇒ 拆成 `exported` 与 `written`。
353
- 仍然**不做**的:HTML 不嵌文件本体(单文件可分享的用法下,内嵌几 MB 的 PDF 没有意义),语音/视频仍只有标签。
354
-
355
- 首轮完成后,再根据实际失败记录决定是否进入阅读状态、AI 执行层和助手收发队列,避免在数据边界尚未稳定时继续扩大功能面。
356
-
357
- ## 14. 关联文档
358
-
359
- - [整体路线图](ROADMAP.md)
360
- - [项目当前状态](PROJECT_STATE.md)
361
- - [消息数据契约](DATA_CONTRACT.md)
362
- - [MCP 接口说明](MCP.md)
363
- - [AI 接口和确认协议](AI_INTERFACE.md)
364
- - [技术决策记录](DECISIONS.md)
365
- - [安全说明](../SECURITY.md)
1
+ # WeFlow CLI 功能增强技术方案
2
+
3
+ > 状态:提案
4
+ >
5
+ > 更新时间:2026-09-19
6
+ >
7
+ > 适用范围:CLI、MCP、本地阅读器、聊天导出、日报、知识库和可选助手
8
+
9
+ 本文把 WeFlow CLI 下一阶段的功能改进整理为可拆分的技术方案。它补充 [ROADMAP.md](ROADMAP.md) 的方向说明,面向实现、评审和验收;已经实现的能力以 [PROJECT_STATE.md](PROJECT_STATE.md) 为准。
10
+
11
+ ## 1. 产品目标
12
+
13
+ WeFlow CLI 已经覆盖本地聊天读取、联系人与收藏、HTML/JSON/TXT/Excel 导出、公众号日报、知识库检索、RAG、只读 MCP 和 Bot 助手。下一阶段不以增加零散命令为主要目标,而是把这些能力连接成四条可恢复、可验证的工作流:
14
+
15
+ 1. **同步:** 从本地数据源读取增量变化,并明确哪些数据已覆盖、哪些数据读取失败。
16
+ 2. **整理:** 将消息、媒体、文章和笔记写入统一的本地索引,保留原始来源。
17
+ 3. **使用:** 通过搜索、阅读器、导出和 MCP 查询这些数据,并能回到原始记录。
18
+ 4. **分析:** 在用户选择的范围内调用本地或云端 AI,输出带来源的结果。
19
+
20
+ 功能完成的标准不是命令数量,而是用户可以在失败后继续运行、在结果中看到覆盖边界,并能验证 AI 或导出结果来自哪些原始资料。
21
+
22
+ ## 2. 现状与功能缺口
23
+
24
+ | 能力 | 当前基础 | 需要补齐的功能 |
25
+ | --- | --- | --- |
26
+ | 本地数据 | 支持会话、联系人、消息、收藏、朋友圈缓存和多种导出;已处理部分消息分片 | 增量同步、稳定游标、覆盖报告、失败分片重试和统一索引 |
27
+ | 媒体导出 | 支持图片、表情、视频、链接卡片,部分场景支持语音转写 | 媒体清单、缺失原因、去重、导出断点和可迁移资料包 |
28
+ | 日报阅读 | 支持来源筛选、分类、摘要、HTML 阅读器和收藏同步 | 来源健康、抓取重试、文章去重、阅读状态和离线归档 |
29
+ | 搜索与知识库 | 支持语义搜索、RAG、Vault 和阅读笔记 | 混合检索、增量索引、引用定位、结果解释和评估集 |
30
+ | MCP | 默认只读、有限制、使用版本化消息契约 | 稳定游标、更多只读资源、统一错误码和任务状态查询 |
31
+ | 助手 | 有隐私模式、白名单、Bot 通道和确认边界 | 收件箱/发件箱、幂等发送、投递状态和会话级记忆控制 |
32
+
33
+ ## 3. 优先级和交付顺序
34
+
35
+ ### P0:数据可信和任务可恢复
36
+
37
+ 先完成增量同步、统一任务状态和导出覆盖报告。这一阶段解决“有没有读全”“导出到哪一步”“缺的媒体是什么”三个问题。
38
+
39
+ ### P1:检索和阅读闭环
40
+
41
+ 在可信数据基础上完成混合搜索、引用定位、日报增量抓取和离线阅读。此阶段应让用户能够从一篇 AI 摘要或一个搜索结果返回到原始消息或文章。
42
+
43
+ ### P2:受控 AI 和只读生态
44
+
45
+ 统一 AI 执行层、MCP 增量接口和助手收发队列。所有云端分析和外发操作继续沿用预览、确认和隐私模式。
46
+
47
+ ### P3:迁移和平台扩展
48
+
49
+ 最后增加资料包迁移、Markdown/Obsidian 等互操作格式,以及 Linux/macOS 的能力适配。平台扩展必须建立在能力检测和明确降级结果上。
50
+
51
+ ## 4. 统一数据同步和索引
52
+
53
+ ### 4.1 模块划分
54
+
55
+ 建议把同步流程拆成以下模块:
56
+
57
+ | 模块 | 责任 |
58
+ | --- | --- |
59
+ | `SourceAdapter` | 从 NT 数据库、传统数据库、日报目录或文章源读取记录 |
60
+ | `Normalizer` | 将不同来源转换为统一记录,保留未知字段 |
61
+ | `IdentityResolver` | 生成消息、文章和媒体的稳定身份,拒绝不可靠匹配 |
62
+ | `CheckpointStore` | 保存分片、时间窗、游标和失败重试状态 |
63
+ | `LocalIndex` | 写入全文索引、向量索引和元数据索引 |
64
+ | `CoverageReporter` | 统计读取范围、缺失分片、媒体覆盖和重复记录 |
65
+
66
+ ### 4.2 消息身份
67
+
68
+ 在稳定游标尚未得到所有后端支持前,使用组合身份并允许重叠读取:
69
+
70
+ ```text
71
+ messageKey = source + talker + shard + localId + serverId + createTime
72
+ ```
73
+
74
+ 其中 `serverId` 为非零值时优先使用;`localId` 不能单独作为全局身份。时间窗同步应保留少量重叠范围,由本地去重保证边界消息不丢失。
75
+
76
+ ### 4.3 同步状态示例
77
+
78
+ 状态文件不得包含密钥、完整聊天正文或无必要的本地绝对路径。可以保存如下信息:
79
+
80
+ ```json
81
+ {
82
+ "schema": "weflow-sync/v1",
83
+ "source": "wechat-nt",
84
+ "scope": "示例联系人",
85
+ "lastSuccessfulRun": "2026-09-19T09:00:00+08:00",
86
+ "coveredFrom": "2026-09-01T00:00:00+08:00",
87
+ "coveredTo": "2026-09-19T08:59:59+08:00",
88
+ "recordsRead": 1200,
89
+ "recordsDeduplicated": 14,
90
+ "shardsRead": 4,
91
+ "shardsFailed": 0,
92
+ "mayHaveMore": false,
93
+ "warnings": []
94
+ }
95
+ ```
96
+
97
+ ### 4.4 建议入口
98
+
99
+ 具体命令名称可以由实现者决定,但应覆盖以下能力:
100
+
101
+ - `sync --dry-run --json`:只返回范围、预计记录数和风险标记;
102
+ - `sync run`:执行全量或增量同步;
103
+ - `sync status --json`:查看最后一次运行、覆盖范围和失败项;
104
+ - `sync retry`:只重跑失败分片或失败来源;
105
+ - `sync verify`:检查索引与原始数据的时间边界和计数摘要。
106
+
107
+ ## 5. 统一任务中心
108
+
109
+ 导出、日报、索引、语音转写和知识处理应共享任务模型,而不是各自实现一套进度逻辑。
110
+
111
+ ### 5.1 生命周期
112
+
113
+ ```text
114
+ planned → waiting_confirmation → running → paused
115
+ ↘ completed
116
+ ↘ failed
117
+ ↘ cancelled
118
+ ```
119
+
120
+ 任务必须具备稳定的 `jobId` 和幂等键。重复提交同一范围的任务时,应返回已有任务或明确创建新任务,不能悄悄重复写入。
121
+
122
+ ### 5.2 任务记录
123
+
124
+ ```json
125
+ {
126
+ "schema": "weflow-job/v1",
127
+ "jobId": "job_20260919_0001",
128
+ "kind": "export.html",
129
+ "state": "running",
130
+ "createdAt": "2026-09-19T09:00:00+08:00",
131
+ "updatedAt": "2026-09-19T09:02:10+08:00",
132
+ "progress": { "completed": 800, "total": 1200 },
133
+ "resumeable": true,
134
+ "result": { "records": 800, "mediaMissing": 3 },
135
+ "error": null
136
+ }
137
+ ```
138
+
139
+ 任务输出应区分“已完成”“部分完成”和“失败”。部分完成不能返回成功状态,也不能删除已生成的可用结果。
140
+
141
+ ### 5.3 用户体验
142
+
143
+ 所有长任务至少提供:
144
+
145
+ - 预览和预计影响;
146
+ - 进度、当前阶段和耗时;
147
+ - 暂停、恢复、取消;
148
+ - 失败原因和可重试动作;
149
+ - 人类可读输出与机器可读 JSON 输出。
150
+
151
+ ## 6. 搜索、引用和知识库
152
+
153
+ ### 6.1 混合检索流程
154
+
155
+ ```text
156
+ 用户查询
157
+ → 查询解析(关键词、日期、联系人、会话、消息类型)
158
+ → 全文检索 + 向量检索
159
+ → 元数据过滤
160
+ → 去重和重排
161
+ → 片段裁剪
162
+ → 引用生成
163
+ → AI 回答或结构化结果
164
+ ```
165
+
166
+ 关键词检索负责精确命中姓名、编号和原句;向量检索负责语义相近内容。两者都必须受到日期、会话和结果数量限制。
167
+
168
+ ### 6.2 引用对象
169
+
170
+ 每条引用至少包含:
171
+
172
+ ```json
173
+ {
174
+ "sourceType": "wechat-message",
175
+ "sourceId": "messageKey",
176
+ "talker": "示例联系人",
177
+ "createdAt": "2026-09-19T08:30:00+08:00",
178
+ "snippet": "经脱敏或用户允许的原文片段",
179
+ "locator": { "export": "示例联系人", "part": 1 }
180
+ }
181
+ ```
182
+
183
+ AI 输出中的事实性结论应能回到一个或多个引用;没有引用的内容应标记为推断或建议。原始消息和 AI 生成文本必须分开保存。
184
+
185
+ ### 6.3 索引更新
186
+
187
+ - 只对新增或变更记录建立索引;
188
+ - 内容哈希变化时重建对应条目;
189
+ - 删除或失效记录进入 tombstone,避免旧结果继续出现;
190
+ - 向量模型变化时支持新旧索引并行切换;
191
+ - 建立不含真实聊天内容的评估集,测试召回率、引用准确率和重复率。
192
+
193
+ ## 7. 导出资料包和媒体覆盖
194
+
195
+ ### 7.1 导出配置
196
+
197
+ 导出应从单一命令扩展为可保存的配置:
198
+
199
+ - 会话或联系人范围;
200
+ - 日期范围;
201
+ - 输出格式;
202
+ - 是否包含图片、表情、视频、语音和远程封面;
203
+ - 是否脱敏;
204
+ - 每页条数和媒体大小上限;
205
+ - 离线模式或允许远程读取。
206
+
207
+ ### 7.2 媒体清单
208
+
209
+ 每个媒体项应记录状态,而不是只在页面中显示占位符:
210
+
211
+ ```json
212
+ {
213
+ "mediaKey": "content-or-message-identity",
214
+ "kind": "image",
215
+ "status": "embedded",
216
+ "mime": "image/jpeg",
217
+ "bytes": 123456,
218
+ "sha256": "redacted-example",
219
+ "reason": null
220
+ }
221
+ ```
222
+
223
+ 推荐状态:`embedded`、`cached`、`remote-fetched`、`missing`、`untrusted-match`、`unsupported`。无法可靠匹配时必须保留缺失原因,不能用相邻消息或裸 `localId` 猜测媒体。
224
+
225
+ ### 7.3 离线资料包
226
+
227
+ 资料包建议包含:
228
+
229
+ ```text
230
+ manifest.json
231
+ messages.json
232
+ index.html
233
+ media/
234
+ attachments/
235
+ README.txt
236
+ ```
237
+
238
+ `manifest.json` 保存数据范围、生成时间、版本、覆盖统计和脱敏选项;不得保存数据库密钥、完整本地路径或未授权的配置内容。
239
+
240
+ ## 8. 日报和本地阅读器
241
+
242
+ ### 8.1 来源状态
243
+
244
+ 每个公众号或文章来源应有本地状态:启用、停用、最近成功时间、最近失败原因、连续失败次数和最后抓取文章时间。状态用于提醒用户,不应自动扩大抓取范围。
245
+
246
+ ### 8.2 文章去重
247
+
248
+ 优先使用文章 URL,其次使用来源、标题、发布时间和正文哈希组合判断重复。重复文章应保留来源关系,但不重复生成日报和向量索引。
249
+
250
+ ### 8.3 阅读状态
251
+
252
+ 阅读器可以增加未读、已读、稍后阅读、收藏和笔记关联。所有状态保存在本地,刷新、重新生成 HTML 或重启服务后仍能恢复。
253
+
254
+ ## 9. 受控 AI 执行层
255
+
256
+ 统一 AI 配置应至少包含:
257
+
258
+ | 配置 | 作用 |
259
+ | --- | --- |
260
+ | provider/model | 选择云端或本地模型 |
261
+ | privacyMode | `strict`、`balanced`、`local` |
262
+ | inputLimit | 限制消息数量、字符数或 token 数 |
263
+ | timeLimit | 限制单任务耗时 |
264
+ | budget | 限制调用次数或费用 |
265
+ | outputSchema | 约束结构化输出 |
266
+ | citationRequired | 要求返回引用 |
267
+
268
+ AI 调用前应生成不含正文的预览,说明数据范围、是否联网、预计规模和写入位置。云端调用失败时,保留本地抓取和无 AI 结果,不把整个工作流判定为不可用。
269
+
270
+ ## 10. MCP 和助手功能
271
+
272
+ ### 10.1 MCP 只读扩展
273
+
274
+ 优先扩展以下只读能力:
275
+
276
+ - 会话发现和唯一名称解析;
277
+ - 分页读取消息和覆盖状态;
278
+ - 联系人、收藏、日报和知识条目查询;
279
+ - 导出任务创建、查询和结果摘要;
280
+ - 能力、契约和限制发现。
281
+
282
+ 分页建议使用不透明游标,响应带 `nextCursor`、`returnedCount`、`mayHaveMore` 和请求范围。错误响应使用稳定错误码,避免让客户端解析中文错误文本。
283
+
284
+ ### 10.2 Bot 助手收发队列
285
+
286
+ 助手只围绕已验证的 Bot 通道增强,不扩展为个人微信账号自动化。建议引入:
287
+
288
+ - 入站事件 ID 和去重表;
289
+ - 出站消息队列;
290
+ - `queued`、`sent`、`delivered`、`failed` 状态;
291
+ - 重试次数和最后失败原因;
292
+ - 会话级记忆开关和数据范围;
293
+ - 外发前的预览和确认。
294
+
295
+ ## 11. 验收指标
296
+
297
+ 每个阶段至少记录以下指标:
298
+
299
+ | 指标 | 目标 |
300
+ | --- | --- |
301
+ | 增量同步重复率 | 重复读取可被本地去重,不产生重复索引 |
302
+ | 分片覆盖率 | 可报告成功、失败和未扫描分片 |
303
+ | 媒体错配率 | 可靠匹配失败时宁缺毋滥,禁止跨会话错配 |
304
+ | 导出可恢复率 | 中断后从最近检查点继续,不必从头开始 |
305
+ | 搜索引用准确率 | 引用能定位到实际返回片段 |
306
+ | 日报重复率 | 同一文章重复运行不重复生成内容 |
307
+ | AI 数据边界 | 每次云端调用均可说明输入范围和隐私模式 |
308
+ | MCP 响应边界 | 结果数量、时间范围和字段均受限制 |
309
+
310
+ ## 12. 风险和明确不做的事
311
+
312
+ - 不使用裸 `localId`、相邻记录或模糊名称猜测消息和媒体身份。
313
+ - 不把数据库路径、密钥、完整聊天正文或任意本地目录暴露给 MCP 客户端或合作应用。
314
+ - 不在默认工作流中加入个人微信账号自动发消息、拉群、群管理或未验证的客户端自动化。
315
+ - 不把云端 AI 作为日报、导出和本地搜索的必需依赖。
316
+ - 不在没有迁移方案、兼容性测试和回滚方式时修改现有 JSON 数据契约。
317
+
318
+ ## 13. 建议的首轮开发任务
319
+
320
+ 1. 定义 `weflow-sync/v1` 和 `weflow-job/v1` 两个内部状态契约。
321
+ 2. 为消息同步增加检查点、重叠时间窗和覆盖摘要。
322
+ 3. 为 HTML/JSON 导出增加媒体覆盖报告和部分完成状态。
323
+ 4. 将导出、日报和索引任务接入统一任务状态查询。
324
+ 5. 为搜索结果增加消息/文章引用对象,不改变现有原始数据字段。
325
+ 6. 为 MCP 增加不透明游标和稳定错误码。
326
+ 7. 使用合成数据建立同步、搜索、导出和 MCP 的端到端验收样例。
327
+ 8. 让**参数解析错误**在 `--json` 下也走 JSON。现在漏了必需选项时 commander 直接往 stderr 打一句、stdout 是**空的**
328
+ (2026-10-12 用"未配置状态驱动全部 `--json` 叶子命令"量到:83 条里 2 条命中,`scene add` / `scene bind`)。
329
+ 做法大致是给 `program` 装一个会抛的出口(`exitOverride` + `configureOutput`),捕获后若 argv 里有 `--json`
330
+ 就发 `{success:false, code:'INVALID_ARGUMENT', error:'<commander 那句>'}`。**代价**是 `--help` / `--version`
331
+ 也走同一条出口,而全库的 `--help` 调用面很宽(含 `scripts/check_readme_commands.py` 那 277+ 条文档命令),
332
+ 所以要先有一条覆盖 `--help`/`--version` 的判据再动 —— 否则把帮助信息弄坏的后果比这点收益大得多。
333
+ 9. ~~**markdown 导出(连图一起)**~~ **已做(2026-10-13)**:`export … md` 走 Python 那条路
334
+ (`--format md`),图片落盘到 `<输出>/<会话>_assets/`、正文用相对路径引用,并写一份 `manifest.json`;
335
+ 同目录里再导出 `json`/`txt`/`excel` 会自动带上同样的路径(`media` 字段 / `[图片: …]` / 「媒体」列)。
336
+ 实现上**没有**在 TS 里重写匹配逻辑 —— 那样两份实现必然漂移。
337
+ 落地时踩到两件值得记住的事:清单原本按 `local_id` 做键,而**它在分片之间会重复**(1,914 条里 763 条
338
+ 撞上 522 个条目 ⇒ 非 md 格式会引用到别人的图),改成 `local_id:create_time` 后四种格式一致在 556;
339
+ 以及 **Python 失败时 TS 回退成简版 HTML、CLI 仍打印「✓ 导出成功」**(失败伪装成成功)—— md 现在没有
340
+ 静默回退,HTML 的回退会往 stderr 说明它丢了图片。
341
+ 10. ~~**导出文件本体(`.pdf`/`.docx`/`.xlsx`…)**~~ **已做(2026-10-13)**:`md` 与 `json|txt|excel --media`
342
+ 会把文档本体**按原名与扩展名**拷进 `<输出>/<会话>_assets/files/`,md 正文按原名链接到 `files/` 下,
343
+ 其余格式经同一份清单带上(`mediaKind`/`mediaName`、`[文件: …]`、「媒体」列)。
344
+ "消息 → 文件"的链路实测清楚了,与图片那套**不同**:文件消息(appmsg `type=6`)只有名字/md5/大小,
345
+ 本体在账号目录 `msg/file/YYYY-MM/` 下且**与 talker 无关**(按月分目录、目录里没有会话痕迹),
346
+ 所以索引是**只 stat 的** `(归一化名, 字节数)`,多候选才用 md5 消歧(磁盘名常带微信加的 `(1)`)。
347
+ 本机实测 1,855 条文件消息,1,489 条唯一命中 + 154 条 md5 认下 = **88.6%**,212 条本地没有
348
+ (未下载/已清理)—— 不猜,计数照实报。
349
+ 两处坑:`<refermsg>`(引用原文)嵌在 `<appmsg>` **内部**,满文档搜首个同名标签会把引用里的
350
+ `<type>6</type>` 当成这条消息的文件(首次测量 7,083 条"像文件"里绝大多数是引用)⇒ 改为只取
351
+ `<appmsg>` 的**直接子元素**;以及"新拷贝数"被当成"导出数"(第二次跑打印「0 document(s)」而 67 份
352
+ 就在磁盘上)⇒ 拆成 `exported` 与 `written`。
353
+ 仍然**不做**的:HTML 不嵌文件本体(单文件可分享的用法下,内嵌几 MB 的 PDF 没有意义),语音/视频仍只有标签。
354
+
355
+ 首轮完成后,再根据实际失败记录决定是否进入阅读状态、AI 执行层和助手收发队列,避免在数据边界尚未稳定时继续扩大功能面。
356
+
357
+ ## 14. 关联文档
358
+
359
+ - [整体路线图](ROADMAP.md)
360
+ - [项目当前状态](PROJECT_STATE.md)
361
+ - [消息数据契约](DATA_CONTRACT.md)
362
+ - [MCP 接口说明](MCP.md)
363
+ - [AI 接口和确认协议](AI_INTERFACE.md)
364
+ - [技术决策记录](DECISIONS.md)
365
+ - [安全说明](../SECURITY.md)