weflow-cli 1.5.0 → 1.6.1

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 (332) hide show
  1. package/ARCHITECTURE.md +105 -0
  2. package/CHANGELOG.md +121 -0
  3. package/CONTRIBUTING.md +45 -0
  4. package/LICENSE +21 -21
  5. package/OPERATIONS.md +272 -0
  6. package/README.en.md +281 -0
  7. package/README.md +334 -149
  8. package/SECURITY.md +47 -0
  9. package/bin/weflow-cli.ts +4050 -581
  10. package/dist/bin/weflow-cli.js +4310 -588
  11. package/dist/bin/weflow-cli.js.map +1 -1
  12. package/dist/mcp-server/index.js +178 -236
  13. package/dist/mcp-server/index.js.map +1 -1
  14. package/dist/src/core/dbPathService.d.ts +15 -1
  15. package/dist/src/core/dbPathService.d.ts.map +1 -1
  16. package/dist/src/core/dbPathService.js +177 -19
  17. package/dist/src/core/dbPathService.js.map +1 -1
  18. package/dist/src/core/keyService.d.ts +8 -0
  19. package/dist/src/core/keyService.d.ts.map +1 -1
  20. package/dist/src/core/keyService.js +127 -22
  21. package/dist/src/core/keyService.js.map +1 -1
  22. package/dist/src/core/ntCore.d.ts +59 -1
  23. package/dist/src/core/ntCore.d.ts.map +1 -1
  24. package/dist/src/core/ntCore.js +201 -65
  25. package/dist/src/core/ntCore.js.map +1 -1
  26. package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
  27. package/dist/src/core/sqlcipherCore.js +18 -1
  28. package/dist/src/core/sqlcipherCore.js.map +1 -1
  29. package/dist/src/core/wcdbCore.d.ts.map +1 -1
  30. package/dist/src/core/wcdbCore.js +3 -6
  31. package/dist/src/core/wcdbCore.js.map +1 -1
  32. package/dist/src/services/assistantDaemon.d.ts +19 -0
  33. package/dist/src/services/assistantDaemon.d.ts.map +1 -0
  34. package/dist/src/services/assistantDaemon.js +117 -0
  35. package/dist/src/services/assistantDaemon.js.map +1 -0
  36. package/dist/src/services/assistantMemory.d.ts +36 -0
  37. package/dist/src/services/assistantMemory.d.ts.map +1 -0
  38. package/dist/src/services/assistantMemory.js +182 -0
  39. package/dist/src/services/assistantMemory.js.map +1 -0
  40. package/dist/src/services/assistantPrivacy.d.ts +25 -0
  41. package/dist/src/services/assistantPrivacy.d.ts.map +1 -0
  42. package/dist/src/services/assistantPrivacy.js +77 -0
  43. package/dist/src/services/assistantPrivacy.js.map +1 -0
  44. package/dist/src/services/assistantRouting.d.ts +18 -0
  45. package/dist/src/services/assistantRouting.d.ts.map +1 -0
  46. package/dist/src/services/assistantRouting.js +68 -0
  47. package/dist/src/services/assistantRouting.js.map +1 -0
  48. package/dist/src/services/assistantService.d.ts +37 -0
  49. package/dist/src/services/assistantService.d.ts.map +1 -0
  50. package/dist/src/services/assistantService.js +284 -0
  51. package/dist/src/services/assistantService.js.map +1 -0
  52. package/dist/src/services/assistantTools.d.ts +24 -0
  53. package/dist/src/services/assistantTools.d.ts.map +1 -0
  54. package/dist/src/services/assistantTools.js +582 -0
  55. package/dist/src/services/assistantTools.js.map +1 -0
  56. package/dist/src/services/chatService.d.ts +44 -0
  57. package/dist/src/services/chatService.d.ts.map +1 -1
  58. package/dist/src/services/chatService.js +105 -4
  59. package/dist/src/services/chatService.js.map +1 -1
  60. package/dist/src/services/configService.d.ts +55 -0
  61. package/dist/src/services/configService.d.ts.map +1 -1
  62. package/dist/src/services/configService.js +147 -6
  63. package/dist/src/services/configService.js.map +1 -1
  64. package/dist/src/services/evidenceService.d.ts +30 -0
  65. package/dist/src/services/evidenceService.d.ts.map +1 -0
  66. package/dist/src/services/evidenceService.js +98 -0
  67. package/dist/src/services/evidenceService.js.map +1 -0
  68. package/dist/src/services/exportService.d.ts +13 -20
  69. package/dist/src/services/exportService.d.ts.map +1 -1
  70. package/dist/src/services/exportService.js +113 -47
  71. package/dist/src/services/exportService.js.map +1 -1
  72. package/dist/src/services/initKeyService.d.ts +19 -0
  73. package/dist/src/services/initKeyService.d.ts.map +1 -0
  74. package/dist/src/services/initKeyService.js +80 -0
  75. package/dist/src/services/initKeyService.js.map +1 -0
  76. package/dist/src/services/messageContract.d.ts +23 -0
  77. package/dist/src/services/messageContract.d.ts.map +1 -0
  78. package/dist/src/services/messageContract.js +49 -0
  79. package/dist/src/services/messageContract.js.map +1 -0
  80. package/dist/src/services/messageQuery.d.ts +7 -0
  81. package/dist/src/services/messageQuery.d.ts.map +1 -0
  82. package/dist/src/services/messageQuery.js +30 -0
  83. package/dist/src/services/messageQuery.js.map +1 -0
  84. package/dist/src/services/wechat-formatter.js +14 -14
  85. package/dist/src/services/wechatMessageService.d.ts.map +1 -1
  86. package/dist/src/services/wechatMessageService.js +31 -3
  87. package/dist/src/services/wechatMessageService.js.map +1 -1
  88. package/dist/src/services/whitelistService.d.ts +52 -4
  89. package/dist/src/services/whitelistService.d.ts.map +1 -1
  90. package/dist/src/services/whitelistService.js +183 -17
  91. package/dist/src/services/whitelistService.js.map +1 -1
  92. package/dist/src/types.d.ts +20 -0
  93. package/dist/src/types.d.ts.map +1 -1
  94. package/dist/src/utils/dateRange.d.ts +15 -0
  95. package/dist/src/utils/dateRange.d.ts.map +1 -0
  96. package/dist/src/utils/dateRange.js +48 -0
  97. package/dist/src/utils/dateRange.js.map +1 -0
  98. package/dist/src/utils/mcpSecurity.d.ts +5 -0
  99. package/dist/src/utils/mcpSecurity.d.ts.map +1 -0
  100. package/dist/src/utils/mcpSecurity.js +39 -0
  101. package/dist/src/utils/mcpSecurity.js.map +1 -0
  102. package/dist/src/utils/packageRoot.d.ts +2 -0
  103. package/dist/src/utils/packageRoot.d.ts.map +1 -0
  104. package/dist/src/utils/packageRoot.js +16 -0
  105. package/dist/src/utils/packageRoot.js.map +1 -0
  106. package/dist/src/utils/python.d.ts +2 -0
  107. package/dist/src/utils/python.d.ts.map +1 -0
  108. package/dist/src/utils/python.js +72 -0
  109. package/dist/src/utils/python.js.map +1 -0
  110. package/dist/src/utils/pythonProcessEnv.d.ts +3 -0
  111. package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -0
  112. package/dist/src/utils/pythonProcessEnv.js +46 -0
  113. package/dist/src/utils/pythonProcessEnv.js.map +1 -0
  114. package/dist/src/utils/talkerUtils.d.ts +4 -1
  115. package/dist/src/utils/talkerUtils.d.ts.map +1 -1
  116. package/dist/src/utils/talkerUtils.js +9 -9
  117. package/dist/src/utils/talkerUtils.js.map +1 -1
  118. package/dist/src/utils/wechatEmoji.d.ts +5 -0
  119. package/dist/src/utils/wechatEmoji.d.ts.map +1 -0
  120. package/dist/src/utils/wechatEmoji.js +35 -0
  121. package/dist/src/utils/wechatEmoji.js.map +1 -0
  122. package/docs/AI_INTERFACE.md +189 -0
  123. package/docs/BRANCHES.md +87 -0
  124. package/docs/DATA_CONTRACT.md +74 -0
  125. package/docs/DECISIONS.md +270 -0
  126. package/docs/DEEPSEEK_V4_FLASH/346/226/275/345/267/245/346/226/207/346/241/243.md +234 -0
  127. package/docs/EVIDENCE_GUIDE.md +96 -0
  128. package/docs/MCP.md +95 -0
  129. package/docs/PARTNERS.md +41 -0
  130. package/docs/PROJECT_STATE.md +96 -0
  131. package/docs/RELEASING.md +124 -0
  132. package/docs/ROADMAP.md +191 -0
  133. package/docs/SETUP.md +103 -0
  134. package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
  135. package/docs/images/weflow-architecture.png +0 -0
  136. package/docs/images/weflow-architecture.svg +65 -0
  137. package/mcp-server/index.ts +169 -269
  138. package/package.json +81 -72
  139. package/requirements-3x.txt +4 -0
  140. package/requirements-voice.txt +15 -0
  141. package/requirements.txt +9 -0
  142. package/resources/js/marked.min.js +69 -0
  143. package/resources/wechat-emoji/666.png +0 -0
  144. package/resources/wechat-emoji/Awesome.png +0 -0
  145. package/resources/wechat-emoji/Concerned.png +0 -0
  146. package/resources/wechat-emoji/Cry.png +0 -0
  147. package/resources/wechat-emoji/Emm.png +0 -0
  148. package/resources/wechat-emoji/Facepalm.png +0 -0
  149. package/resources/wechat-emoji/Grin.png +0 -0
  150. package/resources/wechat-emoji/OK.png +0 -0
  151. package/resources/wechat-emoji/Respect.png +0 -0
  152. package/resources/wechat-emoji/Sick.png +0 -0
  153. package/resources/wechat-emoji/Sleep.png +0 -0
  154. package/resources/wechat-emoji/Smile.png +0 -0
  155. package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
  156. package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
  157. package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
  158. package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
  159. package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
  160. package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
  161. package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
  162. package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
  163. package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
  164. package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
  165. package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
  166. package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
  167. package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
  168. package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
  169. package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
  170. package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
  171. package/resources/wechat-emoji//345/220/220.png +0 -0
  172. package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
  173. package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
  174. package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
  175. package/resources/wechat-emoji//345/223/207.png +0 -0
  176. package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
  177. package/resources/wechat-emoji//345/230/230.png +0 -0
  178. package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
  179. package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
  180. package/resources/wechat-emoji//345/233/247.png +0 -0
  181. package/resources/wechat-emoji//345/233/260.png +0 -0
  182. package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
  183. package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
  184. package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
  185. package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
  186. package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
  187. package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
  188. package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
  189. package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
  190. package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
  191. package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
  192. package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
  193. package/resources/wechat-emoji//345/274/261.png +0 -0
  194. package/resources/wechat-emoji//345/274/272.png +0 -0
  195. package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
  196. package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
  197. package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
  198. package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
  199. package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
  200. package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
  201. package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
  202. package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
  203. package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
  204. package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
  205. package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
  206. package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
  207. package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
  208. package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
  209. package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
  210. package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
  211. package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
  212. package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
  213. package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
  214. package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
  215. package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
  216. package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
  217. package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
  218. package/resources/wechat-emoji//346/231/225.png +0 -0
  219. package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
  220. package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
  221. package/resources/wechat-emoji//346/261/227.png +0 -0
  222. package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
  223. package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
  224. package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
  225. package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
  226. package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
  227. package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
  228. package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
  229. package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
  230. package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
  231. package/resources/wechat-emoji//347/231/274.png +0 -0
  232. package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
  233. package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
  234. package/resources/wechat-emoji//347/235/241.png +0 -0
  235. package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
  236. package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
  237. package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
  238. package/resources/wechat-emoji//347/246/217.png +0 -0
  239. package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
  240. package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
  241. package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
  242. package/resources/wechat-emoji//350/200/266.png +0 -0
  243. package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
  244. package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
  245. package/resources/wechat-emoji//350/211/262.png +0 -0
  246. package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
  247. package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
  248. package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
  249. package/resources/wechat-emoji//350/241/260.png +0 -0
  250. package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
  251. package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
  252. package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
  253. package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
  254. package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
  255. package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
  256. package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
  257. package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
  258. package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
  259. package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
  260. package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
  261. package/scripts/_batch_link_sources.py +73 -73
  262. package/scripts/_utils.py +29 -0
  263. package/scripts/annual_report.py +796 -796
  264. package/scripts/auto_tag.py +128 -128
  265. package/scripts/biz_daily.py +210 -53
  266. package/scripts/chat_report.py +10 -0
  267. package/scripts/chat_stats.py +702 -702
  268. package/scripts/classify_daily.py +17 -11
  269. package/scripts/create_reading_notes.py +370 -370
  270. package/scripts/daily_stats.py +132 -0
  271. package/scripts/enrich_backlinks.py +163 -163
  272. package/scripts/export_chat_html.py +1704 -107
  273. package/scripts/extract_todos.py +445 -404
  274. package/scripts/fav_server.py +119 -12
  275. package/scripts/fix_topics.py +111 -111
  276. package/scripts/generate_ai_report.py +1 -1
  277. package/scripts/generate_html.py +597 -277
  278. package/scripts/inspection_records.py +354 -0
  279. package/scripts/mcp_bridge.py +398 -398
  280. package/scripts/nt_decrypt.py +702 -48
  281. package/scripts/pipeline.py +27 -19
  282. package/scripts/promote_all.py +312 -310
  283. package/scripts/promote_ideas.py +299 -297
  284. package/scripts/rag_chat.py +194 -191
  285. package/scripts/semantic_search.py +435 -431
  286. package/scripts/sync_fav.py +201 -146
  287. package/scripts/sync_weread.py +192 -192
  288. package/scripts/vault_rag.py +140 -139
  289. package/scripts/vault_search.py +141 -141
  290. package/scripts/wechat_emoji.py +132 -0
  291. package/scripts/wechat_emoticon.py +433 -0
  292. package/scripts/wechat_image.py +258 -0
  293. package/scripts/wechat_voice.py +327 -0
  294. package/src/core/dbPathService.ts +163 -18
  295. package/src/core/keyService.ts +137 -25
  296. package/src/core/ntCore.ts +215 -62
  297. package/src/core/sqlcipherCore.ts +17 -1
  298. package/src/core/wcdbCore.ts +3 -7
  299. package/src/services/assistantDaemon.ts +111 -0
  300. package/src/services/assistantMemory.ts +193 -0
  301. package/src/services/assistantPrivacy.ts +87 -0
  302. package/src/services/assistantRouting.ts +92 -0
  303. package/src/services/assistantService.ts +295 -0
  304. package/src/services/assistantTools.ts +549 -0
  305. package/src/services/chatService.ts +133 -4
  306. package/src/services/configService.ts +194 -6
  307. package/src/services/evidenceService.ts +129 -0
  308. package/src/services/exportService.ts +113 -48
  309. package/src/services/initKeyService.ts +83 -0
  310. package/src/services/messageContract.ts +77 -0
  311. package/src/services/messageQuery.ts +38 -0
  312. package/src/services/wechat-formatter.ts +348 -348
  313. package/src/services/wechatMessageService.ts +25 -3
  314. package/src/services/wereadService.ts +308 -308
  315. package/src/services/whitelistService.ts +209 -22
  316. package/src/types.ts +20 -0
  317. package/src/utils/dateRange.ts +58 -0
  318. package/src/utils/mcpSecurity.ts +39 -0
  319. package/src/utils/packageRoot.ts +14 -0
  320. package/src/utils/python.ts +73 -0
  321. package/src/utils/pythonProcessEnv.ts +45 -0
  322. package/src/utils/talkerUtils.ts +14 -2
  323. package/dist/src/utils/errors.d.ts +0 -25
  324. package/dist/src/utils/errors.d.ts.map +0 -1
  325. package/dist/src/utils/errors.js +0 -43
  326. package/dist/src/utils/errors.js.map +0 -1
  327. package/dist/src/utils/pythonRunner.d.ts +0 -27
  328. package/dist/src/utils/pythonRunner.d.ts.map +0 -1
  329. package/dist/src/utils/pythonRunner.js +0 -62
  330. package/dist/src/utils/pythonRunner.js.map +0 -1
  331. package/src/utils/errors.ts +0 -42
  332. package/src/utils/pythonRunner.ts +0 -81
@@ -2,35 +2,50 @@ import { join, basename, dirname } from 'path'
2
2
  import { existsSync, mkdirSync, writeFileSync } from 'fs'
3
3
  import { execFile } from 'child_process'
4
4
  import { promisify } from 'util'
5
- import { fileURLToPath } from 'url'
6
5
  import { chatService } from './chatService.js'
7
6
  import { configService } from './configService.js'
7
+ import { getPythonCommand } from '../utils/python.js'
8
+ import { createPythonProcessEnv, safeSubprocessError } from '../utils/pythonProcessEnv.js'
8
9
  import type { Message } from '../types.js'
10
+ import { createWeFlowEnvelope } from './messageContract.js'
11
+ import { parseLocalDateOrIso } from '../utils/dateRange.js'
12
+ import { resolvePackageRoot } from '../utils/packageRoot.js'
13
+
14
+ export type ExportContract = 'raw' | 'weflow-v1'
15
+ export interface ExportResult { success: boolean; path?: string; count?: number; error?: string }
9
16
 
10
17
  const execFileAsync = promisify(execFile)
11
- const __filename = fileURLToPath(import.meta.url)
12
- const __dirname = dirname(__filename)
18
+ function getPackageRoot(): string {
19
+ return resolvePackageRoot(import.meta.url)
20
+ }
13
21
 
14
22
  export class ExportService {
15
- async exportJson(talker: string, outputDir: string, limit = 0): Promise<{ success: boolean; path?: string; error?: string }> {
23
+ async exportJson(talker: string, outputDir: string, limit = 0, from?: number, to?: number, contract: ExportContract = 'raw'): Promise<ExportResult> {
16
24
  try {
17
- const messages = await chatService.getMessages(talker, limit)
25
+ const messages = await chatService.getMessagesInRange(talker, limit, from, to)
18
26
  if (messages.length === 0) {
19
27
  return { success: false, error: '未找到消息' }
20
28
  }
21
29
 
22
30
  const dir = this.ensureOutputDir(outputDir)
23
31
  const filePath = join(dir, `${talker}_messages.json`)
24
- writeFileSync(filePath, JSON.stringify(messages, null, 2), 'utf8')
25
- return { success: true, path: filePath }
26
- } catch (e) {
27
- return { success: false, error: String(e) }
32
+ const payload = contract === 'weflow-v1'
33
+ ? createWeFlowEnvelope(messages, new Date().toISOString(), {
34
+ requestedFrom: from,
35
+ requestedTo: to,
36
+ requestedLimit: limit,
37
+ })
38
+ : messages
39
+ writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8')
40
+ return { success: true, path: filePath, count: messages.length }
41
+ } catch {
42
+ return { success: false, error: 'JSON 导出失败' }
28
43
  }
29
44
  }
30
45
 
31
- async exportTxt(talker: string, outputDir: string, limit = 0): Promise<{ success: boolean; path?: string; error?: string }> {
46
+ async exportTxt(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
32
47
  try {
33
- const messages = await chatService.getMessages(talker, limit)
48
+ const messages = await chatService.getMessagesInRange(talker, limit, from, to)
34
49
  if (messages.length === 0) {
35
50
  return { success: false, error: '未找到消息' }
36
51
  }
@@ -45,32 +60,48 @@ export class ExportService {
45
60
  const dir = this.ensureOutputDir(outputDir)
46
61
  const filePath = join(dir, `${talker}_messages.txt`)
47
62
  writeFileSync(filePath, lines.join('\n'), 'utf8')
48
- return { success: true, path: filePath }
49
- } catch (e) {
50
- return { success: false, error: String(e) }
63
+ return { success: true, path: filePath, count: messages.length }
64
+ } catch {
65
+ return { success: false, error: '文本导出失败' }
51
66
  }
52
67
  }
53
68
 
54
- async exportHtml(talker: string, outputDir: string, limit = 0): Promise<{ success: boolean; path?: string; error?: string }> {
69
+ async exportHtml(talker: string, outputDir: string, limit = 0, date = '', from?: number, to?: number, quiet = false, options: { fullImages?: boolean } = {}): Promise<ExportResult> {
55
70
  try {
71
+ if (from !== undefined || to !== undefined) {
72
+ return this.exportHtmlBasic(talker, outputDir, limit, from, to)
73
+ }
56
74
  // Use Python export_chat_html.py for rich HTML with images
57
75
  const cfg = configService.getAll()
58
76
  const db = cfg.ntDbPath
59
77
  const key = cfg.ntKey
60
78
  const salt = cfg.ntSalt
79
+ // Derives each message shard's key (WeChat 4.1.12.26+). favPassphrase is
80
+ // the dedicated field, but decryptKey holds the same value and is what
81
+ // `init` writes - without this fallback the shard merge silently stays
82
+ // dormant and exports stop at the first shard's last message.
83
+ const passphrase = configService.get('favPassphrase') || cfg.decryptKey || ''
61
84
 
62
85
  if (!db || !key || !salt) {
63
86
  // Fallback: basic HTML
64
- return this.exportHtmlBasic(talker, outputDir, limit)
87
+ let fallbackFrom: number | undefined = from
88
+ let fallbackTo: number | undefined = to
89
+ if (date && fallbackFrom === undefined && fallbackTo === undefined) {
90
+ fallbackFrom = parseLocalDateOrIso(date)
91
+ fallbackTo = parseLocalDateOrIso(date, true)
92
+ }
93
+ return this.exportHtmlBasic(talker, outputDir, limit, fallbackFrom, fallbackTo)
65
94
  }
66
95
 
67
96
  // Resolve Python script path (dist/src/services → package root)
68
- const pkgRoot = join(__dirname, '..', '..', '..')
69
- const script = join(pkgRoot, 'scripts', 'export_chat_html.py')
97
+ const script = join(getPackageRoot(), 'scripts', 'export_chat_html.py')
70
98
 
71
99
  // Cache dir for image thumbnails
72
- const cacheDir = cfg.contactDbPath
73
- ? join(dirname(dirname(cfg.contactDbPath)), 'cache')
100
+ const cacheDir = cfg.ntDbPath
101
+ ? join(dirname(dirname(dirname(cfg.ntDbPath))), 'cache')
102
+ : ''
103
+ const accountDir = cfg.ntDbPath
104
+ ? dirname(dirname(dirname(cfg.ntDbPath)))
74
105
  : ''
75
106
 
76
107
  // Resolve display name for filename (prefer remark/nickname over wxid)
@@ -85,48 +116,82 @@ export class ExportService {
85
116
 
86
117
  const args: string[] = [
87
118
  script,
88
- '--db', db,
89
- '--key', key,
90
- '--salt', salt,
91
- '--talker', talker,
92
- '--out', outputDir || `./output`,
93
- '--single',
94
- '--parts', '1',
119
+ // Paginate: a whole year of chat in one file makes the browser crawl.
120
+ '--per-page', '100',
95
121
  ]
96
- if (displayName) {
97
- args.push('--name', displayName)
98
- }
99
- if (cacheDir && existsSync(cacheDir)) {
100
- args.push('--cache-dir', cacheDir)
101
- }
102
-
103
- console.log(` Exporting HTML via Python...`)
104
- const { stdout } = await execFileAsync('python', args, {
105
- timeout: 300_000,
122
+ const ownWxid = String(cfg.wxid || '').trim()
123
+ const exportCacheDir = cacheDir && existsSync(cacheDir) ? cacheDir : undefined
124
+ const exportAccountDir = accountDir && existsSync(accountDir) ? accountDir : undefined
125
+ if (!quiet) console.log(` Exporting HTML via Python...`)
126
+ const { stdout } = await execFileAsync(getPythonCommand(), args, {
127
+ // Headroom for the opt-in --full-images path, which resizes hundreds
128
+ // of multi-MB originals and can take several minutes.
129
+ timeout: 900_000,
106
130
  maxBuffer: 50 * 1024 * 1024,
131
+ env: createPythonProcessEnv({
132
+ WEFLOW_DB_PATH: db,
133
+ WEFLOW_NT_KEY: key,
134
+ WEFLOW_NT_SALT: salt,
135
+ WEFLOW_TALKER: talker,
136
+ WEFLOW_OWN_WXID: ownWxid || undefined,
137
+ WEFLOW_NT_PASSPHRASE: passphrase || undefined,
138
+ WEFLOW_EXPORT_NAME: displayName || undefined,
139
+ WEFLOW_EXPORT_OUTPUT: outputDir || './output',
140
+ WEFLOW_EXPORT_CACHE_DIR: exportCacheDir,
141
+ WEFLOW_EXPORT_ACCOUNT_DIR: exportAccountDir,
142
+ // Lets custom stickers be decrypted from WeChat's local cache.
143
+ WEFLOW_EMOTICON_SEED: String(cfg.emoticonSeed || '') || undefined,
144
+ // Group rows identify their sender only by wxid; the contact
145
+ // database is what turns that into a name.
146
+ WEFLOW_CONTACT_DB_PATH: cfg.contactDbPath || undefined,
147
+ WEFLOW_CONTACT_KEY: cfg.contactKey || undefined,
148
+ WEFLOW_CONTACT_SALT: cfg.contactSalt || undefined,
149
+ // Off by default: originals are 5-24MB each and downscaling a few
150
+ // hundred of them costs minutes in PIL.
151
+ WEFLOW_FULL_IMAGES: options.fullImages ? '1' : undefined,
152
+ WEFLOW_EXPORT_DATE: date || undefined,
153
+ }),
107
154
  })
108
155
 
156
+ // Surface Python's progress and diagnostics. Only the trailing JSON
157
+ // summary is machine-read; everything else used to be discarded, so a
158
+ // multi-minute export showed nothing between "Exporting..." and the
159
+ // result - and the sticker seed hint never reached the user at all.
160
+ if (!quiet) {
161
+ for (const raw of stdout.split('\n')) {
162
+ const line = raw.trim()
163
+ if (!line || line.startsWith('{') || line.startsWith('Total messages:')) continue
164
+ if (/^Fetched \d+\/\d+/.test(line)) continue
165
+ console.log(` ${line}`)
166
+ }
167
+ }
168
+
109
169
  // Parse JSON result from Python output
110
170
  const lines = stdout.split('\n').filter((l: string) => l.trim())
111
171
  for (let i = lines.length - 1; i >= 0; i--) {
112
172
  try {
113
173
  const result = JSON.parse(lines[i])
114
174
  if (result.success && result.files?.length > 0) {
115
- return { success: true, path: result.files[0] }
175
+ const total = Number(result.total)
176
+ return { success: true, path: result.files[0], count: Number.isFinite(total) ? total : undefined }
116
177
  }
117
178
  } catch {}
118
179
  }
119
180
 
120
181
  return { success: false, error: 'Python export succeeded but no output found' }
121
182
  } catch (e: any) {
122
- console.error(` Python export failed: ${e.message}`)
183
+ const failure = safeSubprocessError(e, 'Python export failed')
184
+ if (!quiet) console.error(` ${failure}`)
185
+ if (date) {
186
+ return { success: false, error: `指定日期导出失败: ${failure}` }
187
+ }
123
188
  // Fallback to basic HTML
124
- return this.exportHtmlBasic(talker, outputDir, limit)
189
+ return this.exportHtmlBasic(talker, outputDir, limit, from, to)
125
190
  }
126
191
  }
127
192
 
128
- private async exportHtmlBasic(talker: string, outputDir: string, limit = 0): Promise<{ success: boolean; path?: string; error?: string }> {
129
- const messages = await chatService.getMessages(talker, limit)
193
+ private async exportHtmlBasic(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
194
+ const messages = await chatService.getMessagesInRange(talker, limit, from, to)
130
195
  if (messages.length === 0) {
131
196
  return { success: false, error: '未找到消息' }
132
197
  }
@@ -135,13 +200,13 @@ export class ExportService {
135
200
  const dir = this.ensureOutputDir(outputDir)
136
201
  const filePath = join(dir, `${talker}_messages.html`)
137
202
  writeFileSync(filePath, html, 'utf8')
138
- return { success: true, path: filePath }
203
+ return { success: true, path: filePath, count: messages.length }
139
204
  }
140
205
 
141
- async exportExcel(talker: string, outputDir: string, limit = 0): Promise<{ success: boolean; path?: string; error?: string }> {
206
+ async exportExcel(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
142
207
  try {
143
208
  const ExcelJS = await import('exceljs')
144
- const messages = await chatService.getMessages(talker, limit)
209
+ const messages = await chatService.getMessagesInRange(talker, limit, from, to)
145
210
  if (messages.length === 0) {
146
211
  return { success: false, error: '未找到消息' }
147
212
  }
@@ -170,9 +235,9 @@ export class ExportService {
170
235
  const dir = this.ensureOutputDir(outputDir)
171
236
  const filePath = join(dir, `${talker}_messages.xlsx`)
172
237
  await workbook.xlsx.writeFile(filePath)
173
- return { success: true, path: filePath }
174
- } catch (e) {
175
- return { success: false, error: String(e) }
238
+ return { success: true, path: filePath, count: messages.length }
239
+ } catch {
240
+ return { success: false, error: 'Excel 导出失败' }
176
241
  }
177
242
  }
178
243
 
@@ -0,0 +1,83 @@
1
+ /**
2
+ * init 密钥派生服务 — 较新微信版本(实测 4.1.12.55)密钥体系适配。
3
+ *
4
+ * 新版微信进程内存中不再出现 x'<key><salt>' 文本, 内存扫描匹配不到任何密钥;
5
+ * 但 DLL hook 仍能拿到全库 passphrase, 各库密钥改为本地派生:
6
+ * raw_key = PBKDF2-HMAC-SHA512(passphrase, 库文件前16字节盐, 256000轮, 32字节)
7
+ * 派生后逐库用 sqlcipher 真实打开验证, 通过才写入配置。
8
+ */
9
+ import chalk from 'chalk'
10
+ import { existsSync } from 'fs'
11
+ import { NtCore } from '../core/ntCore.js'
12
+ import { configService } from './configService.js'
13
+
14
+ export interface NtDbEntry {
15
+ path: string
16
+ name: string
17
+ salt: string
18
+ size: number
19
+ wxid: string
20
+ }
21
+
22
+ type Log = (line: string) => void
23
+
24
+ /** 从已配置的 NT 主库路径推导 favorite.db 路径 */
25
+ export function detectFavDbPath(): string | null {
26
+ const ntDbPath = configService.get('ntDbPath')
27
+ if (!ntDbPath) return null
28
+ const parts = ntDbPath.replace(/\\/g, '/').split('/')
29
+ const idx = parts.lastIndexOf('db_storage')
30
+ if (idx < 0) return null
31
+ const favPath = [...parts.slice(0, idx), 'db_storage', 'favorite', 'favorite.db'].join('/')
32
+ return existsSync(favPath) ? favPath : null
33
+ }
34
+
35
+ /**
36
+ * 派生各 NT 库密钥并验证写入配置 (4.1.12+ 派生模式)。
37
+ * @returns message_0.db 是否成功配置 (聊天主路径可用)
38
+ */
39
+ export async function applyDerivedNtKeys(passphrase: string, databases: NtDbEntry[], log: Log = () => {}): Promise<boolean> {
40
+ const targets = [
41
+ { name: 'message/message_0.db', pathKey: 'ntDbPath', keyKey: 'ntKey', saltKey: 'ntSalt', label: '聊天主库' },
42
+ { name: 'contact/contact.db', pathKey: 'contactDbPath', keyKey: 'contactKey', saltKey: 'contactSalt', label: '联系人库' },
43
+ { name: 'sns/sns.db', pathKey: 'snsDbPath', keyKey: 'snsKey', saltKey: 'snsSalt', label: '朋友圈库' },
44
+ ] as const
45
+ let messageSaved = false
46
+ let anySaved = false
47
+ for (const t of targets) {
48
+ const db = databases.find((d) => d.name === t.name)
49
+ if (!db) continue
50
+ const derivedKey = NtCore.deriveRawKey(passphrase, db.salt)
51
+ const verify = await NtCore.verifyDbKey(db.path, derivedKey, db.salt)
52
+ if (verify.success || verify.cannotVerify) {
53
+ // cannotVerify = Python/sqlcipher3 环境问题而非密钥错误: 公式确定性派生, 值仍写入
54
+ configService.set(t.pathKey, db.path)
55
+ configService.set(t.keyKey, derivedKey)
56
+ configService.set(t.saltKey, db.salt)
57
+ const note = verify.success ? '派生密钥已验证' : `已写入但未能验证 (${verify.error})`
58
+ log(chalk.green(` ✓ ${t.label}: ${db.name} (${(db.size / 1024 / 1024).toFixed(1)}MB, ${note})`))
59
+ if (t.name === 'message/message_0.db') messageSaved = true
60
+ anySaved = true
61
+ } else {
62
+ log(chalk.red(` ✗ ${t.label}: 派生密钥验证失败 (${verify.error || '密钥不匹配'})`))
63
+ }
64
+ }
65
+ if (anySaved) await enableFavorites(passphrase, databases, log)
66
+ return messageSaved
67
+ }
68
+
69
+ /** 启用收藏功能: 验证 passphrase 能派生打开 favorite.db 后写入配置 */
70
+ export async function enableFavorites(passphrase: string, databases: NtDbEntry[], log: Log = () => {}): Promise<void> {
71
+ if (configService.get('favPassphrase')) return
72
+ const favDb = databases.find((d) => d.name === 'favorite/favorite.db')
73
+ const favPath = favDb?.path || detectFavDbPath()
74
+ if (!favPath) return
75
+ if (favDb) {
76
+ const derived = NtCore.deriveRawKey(passphrase, favDb.salt)
77
+ const v = await NtCore.verifyDbKey(favPath, derived, favDb.salt)
78
+ if (!v.success && !v.cannotVerify) return
79
+ }
80
+ configService.set('favDbPath', favPath)
81
+ configService.set('favPassphrase', passphrase)
82
+ log(chalk.green(' ✓ 收藏功能已启用 (favorite.db passphrase 已配置)'))
83
+ }
@@ -0,0 +1,77 @@
1
+ import type { Message } from '../types.js'
2
+
3
+ export type WeFlowMessageType =
4
+ | 'text' | 'image' | 'voice' | 'card' | 'video' | 'emoji'
5
+ | 'location' | 'link' | 'call' | 'system' | 'quote' | 'other'
6
+
7
+ const MESSAGE_TYPE_NAMES: Record<number, WeFlowMessageType> = {
8
+ 1: 'text',
9
+ 3: 'image',
10
+ 34: 'voice',
11
+ 42: 'card',
12
+ 43: 'video',
13
+ 47: 'emoji',
14
+ 48: 'location',
15
+ 49: 'link',
16
+ 50: 'call',
17
+ 10000: 'system',
18
+ 10002: 'quote',
19
+ }
20
+
21
+ export interface WeFlowMessageEnvelope {
22
+ schema: 'weflow-message/v1'
23
+ source: 'weflow-cli'
24
+ generatedAt: string
25
+ coverage?: WeFlowMessageCoverage
26
+ messages: Array<Message & { messageType: WeFlowMessageType }>
27
+ }
28
+
29
+ export interface WeFlowMessageCoverage {
30
+ requestedFrom?: number
31
+ requestedTo?: number
32
+ requestedLimit: number
33
+ returned: number
34
+ mayHaveMore: boolean
35
+ oldestCreateTime?: number
36
+ newestCreateTime?: number
37
+ }
38
+
39
+ export function toWeFlowMessages(messages: Message[]): WeFlowMessageEnvelope['messages'] {
40
+ return messages.map((message) => ({
41
+ ...message,
42
+ messageType: MESSAGE_TYPE_NAMES[message.localType] || 'other',
43
+ }))
44
+ }
45
+
46
+ export function createWeFlowEnvelope(
47
+ messages: Message[],
48
+ generatedAt = new Date().toISOString(),
49
+ coverage?: Omit<WeFlowMessageCoverage, 'returned' | 'oldestCreateTime' | 'newestCreateTime' | 'mayHaveMore'>,
50
+ ): WeFlowMessageEnvelope {
51
+ let oldestCreateTime: number | undefined
52
+ let newestCreateTime: number | undefined
53
+ for (const message of messages) {
54
+ const timestamp = Number(message.createTime)
55
+ if (!Number.isFinite(timestamp)) continue
56
+ oldestCreateTime = oldestCreateTime === undefined ? timestamp : Math.min(oldestCreateTime, timestamp)
57
+ newestCreateTime = newestCreateTime === undefined ? timestamp : Math.max(newestCreateTime, timestamp)
58
+ }
59
+ const result: WeFlowMessageEnvelope = {
60
+ schema: 'weflow-message/v1',
61
+ source: 'weflow-cli',
62
+ generatedAt,
63
+ messages: toWeFlowMessages(messages),
64
+ }
65
+ if (coverage) {
66
+ result.coverage = {
67
+ ...coverage,
68
+ returned: messages.length,
69
+ mayHaveMore: coverage.requestedLimit > 0 && messages.length >= coverage.requestedLimit,
70
+ ...(oldestCreateTime !== undefined ? {
71
+ oldestCreateTime,
72
+ newestCreateTime,
73
+ } : {}),
74
+ }
75
+ }
76
+ return result
77
+ }
@@ -0,0 +1,38 @@
1
+ import type { Message } from '../types.js'
2
+
3
+ export interface MessageTimeRange {
4
+ from?: number
5
+ to?: number
6
+ }
7
+
8
+ export async function collectMessagesInRange(
9
+ fetchPage: (limit: number, offset: number) => Promise<Message[]>,
10
+ limit: number,
11
+ range: MessageTimeRange,
12
+ pageSize = 500,
13
+ ): Promise<Message[]> {
14
+ const result: Message[] = []
15
+ let offset = 0
16
+
17
+ while (true) {
18
+ const page = await fetchPage(pageSize, offset)
19
+ if (page.length === 0) break
20
+
21
+ let oldestTimestamp = Number.POSITIVE_INFINITY
22
+ for (const message of page) {
23
+ const timestamp = Number(message.createTime)
24
+ if (!Number.isFinite(timestamp)) continue
25
+ oldestTimestamp = Math.min(oldestTimestamp, timestamp)
26
+ if (range.from !== undefined && timestamp < range.from) continue
27
+ if (range.to !== undefined && timestamp > range.to) continue
28
+ result.push(message)
29
+ if (limit > 0 && result.length >= limit) return result
30
+ }
31
+
32
+ offset += page.length
33
+ if (page.length < pageSize) break
34
+ if (range.from !== undefined && oldestTimestamp < range.from) break
35
+ }
36
+
37
+ return result
38
+ }