weflow-cli 1.5.0 → 1.6.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 (331) hide show
  1. package/ARCHITECTURE.md +105 -0
  2. package/CHANGELOG.md +110 -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 +333 -149
  8. package/SECURITY.md +47 -0
  9. package/bin/weflow-cli.ts +4032 -583
  10. package/dist/bin/weflow-cli.js +4296 -593
  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 +188 -64
  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/ROADMAP.md +191 -0
  132. package/docs/SETUP.md +103 -0
  133. package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
  134. package/docs/images/weflow-architecture.png +0 -0
  135. package/docs/images/weflow-architecture.svg +65 -0
  136. package/mcp-server/index.ts +169 -269
  137. package/package.json +81 -72
  138. package/requirements-3x.txt +4 -0
  139. package/requirements-voice.txt +15 -0
  140. package/requirements.txt +9 -0
  141. package/resources/js/marked.min.js +69 -0
  142. package/resources/wechat-emoji/666.png +0 -0
  143. package/resources/wechat-emoji/Awesome.png +0 -0
  144. package/resources/wechat-emoji/Concerned.png +0 -0
  145. package/resources/wechat-emoji/Cry.png +0 -0
  146. package/resources/wechat-emoji/Emm.png +0 -0
  147. package/resources/wechat-emoji/Facepalm.png +0 -0
  148. package/resources/wechat-emoji/Grin.png +0 -0
  149. package/resources/wechat-emoji/OK.png +0 -0
  150. package/resources/wechat-emoji/Respect.png +0 -0
  151. package/resources/wechat-emoji/Sick.png +0 -0
  152. package/resources/wechat-emoji/Sleep.png +0 -0
  153. package/resources/wechat-emoji/Smile.png +0 -0
  154. package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
  155. package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
  156. package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
  157. package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
  158. package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
  159. package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
  160. package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
  161. package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
  162. package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
  163. package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
  164. package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
  165. package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
  166. package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
  167. package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
  168. package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
  169. package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
  170. package/resources/wechat-emoji//345/220/220.png +0 -0
  171. package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
  172. package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
  173. package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
  174. package/resources/wechat-emoji//345/223/207.png +0 -0
  175. package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
  176. package/resources/wechat-emoji//345/230/230.png +0 -0
  177. package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
  178. package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
  179. package/resources/wechat-emoji//345/233/247.png +0 -0
  180. package/resources/wechat-emoji//345/233/260.png +0 -0
  181. package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
  182. package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
  183. package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
  184. package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
  185. package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
  186. package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
  187. package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
  188. package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
  189. package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
  190. package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
  191. package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
  192. package/resources/wechat-emoji//345/274/261.png +0 -0
  193. package/resources/wechat-emoji//345/274/272.png +0 -0
  194. package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
  195. package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
  196. package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
  197. package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
  198. package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
  199. package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
  200. package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
  201. package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
  202. package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
  203. package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
  204. package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
  205. package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
  206. package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
  207. package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
  208. package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
  209. package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
  210. package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
  211. package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
  212. package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
  213. package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
  214. package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
  215. package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
  216. package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
  217. package/resources/wechat-emoji//346/231/225.png +0 -0
  218. package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
  219. package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
  220. package/resources/wechat-emoji//346/261/227.png +0 -0
  221. package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
  222. package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
  223. package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
  224. package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
  225. package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
  226. package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
  227. package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
  228. package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
  229. package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
  230. package/resources/wechat-emoji//347/231/274.png +0 -0
  231. package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
  232. package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
  233. package/resources/wechat-emoji//347/235/241.png +0 -0
  234. package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
  235. package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
  236. package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
  237. package/resources/wechat-emoji//347/246/217.png +0 -0
  238. package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
  239. package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
  240. package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
  241. package/resources/wechat-emoji//350/200/266.png +0 -0
  242. package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
  243. package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
  244. package/resources/wechat-emoji//350/211/262.png +0 -0
  245. package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
  246. package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
  247. package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
  248. package/resources/wechat-emoji//350/241/260.png +0 -0
  249. package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
  250. package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
  251. package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
  252. package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
  253. package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
  254. package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
  255. package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
  256. package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
  257. package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
  258. package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
  259. package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
  260. package/scripts/_batch_link_sources.py +73 -73
  261. package/scripts/_utils.py +29 -0
  262. package/scripts/annual_report.py +796 -796
  263. package/scripts/auto_tag.py +128 -128
  264. package/scripts/biz_daily.py +210 -53
  265. package/scripts/chat_report.py +10 -0
  266. package/scripts/chat_stats.py +702 -702
  267. package/scripts/classify_daily.py +17 -11
  268. package/scripts/create_reading_notes.py +370 -370
  269. package/scripts/daily_stats.py +132 -0
  270. package/scripts/enrich_backlinks.py +163 -163
  271. package/scripts/export_chat_html.py +1704 -107
  272. package/scripts/extract_todos.py +445 -404
  273. package/scripts/fav_server.py +119 -12
  274. package/scripts/fix_topics.py +111 -111
  275. package/scripts/generate_ai_report.py +1 -1
  276. package/scripts/generate_html.py +597 -277
  277. package/scripts/inspection_records.py +354 -0
  278. package/scripts/mcp_bridge.py +398 -398
  279. package/scripts/nt_decrypt.py +693 -48
  280. package/scripts/pipeline.py +27 -19
  281. package/scripts/promote_all.py +312 -310
  282. package/scripts/promote_ideas.py +299 -297
  283. package/scripts/rag_chat.py +194 -191
  284. package/scripts/semantic_search.py +435 -431
  285. package/scripts/sync_fav.py +201 -146
  286. package/scripts/sync_weread.py +192 -192
  287. package/scripts/vault_rag.py +140 -139
  288. package/scripts/vault_search.py +141 -141
  289. package/scripts/wechat_emoji.py +132 -0
  290. package/scripts/wechat_emoticon.py +433 -0
  291. package/scripts/wechat_image.py +258 -0
  292. package/scripts/wechat_voice.py +327 -0
  293. package/src/core/dbPathService.ts +163 -18
  294. package/src/core/keyService.ts +137 -25
  295. package/src/core/ntCore.ts +202 -61
  296. package/src/core/sqlcipherCore.ts +17 -1
  297. package/src/core/wcdbCore.ts +3 -7
  298. package/src/services/assistantDaemon.ts +111 -0
  299. package/src/services/assistantMemory.ts +193 -0
  300. package/src/services/assistantPrivacy.ts +87 -0
  301. package/src/services/assistantRouting.ts +92 -0
  302. package/src/services/assistantService.ts +295 -0
  303. package/src/services/assistantTools.ts +549 -0
  304. package/src/services/chatService.ts +133 -4
  305. package/src/services/configService.ts +194 -6
  306. package/src/services/evidenceService.ts +129 -0
  307. package/src/services/exportService.ts +113 -48
  308. package/src/services/initKeyService.ts +83 -0
  309. package/src/services/messageContract.ts +77 -0
  310. package/src/services/messageQuery.ts +38 -0
  311. package/src/services/wechat-formatter.ts +348 -348
  312. package/src/services/wechatMessageService.ts +25 -3
  313. package/src/services/wereadService.ts +308 -308
  314. package/src/services/whitelistService.ts +209 -22
  315. package/src/types.ts +20 -0
  316. package/src/utils/dateRange.ts +58 -0
  317. package/src/utils/mcpSecurity.ts +39 -0
  318. package/src/utils/packageRoot.ts +14 -0
  319. package/src/utils/python.ts +73 -0
  320. package/src/utils/pythonProcessEnv.ts +45 -0
  321. package/src/utils/talkerUtils.ts +14 -2
  322. package/dist/src/utils/errors.d.ts +0 -25
  323. package/dist/src/utils/errors.d.ts.map +0 -1
  324. package/dist/src/utils/errors.js +0 -43
  325. package/dist/src/utils/errors.js.map +0 -1
  326. package/dist/src/utils/pythonRunner.d.ts +0 -27
  327. package/dist/src/utils/pythonRunner.d.ts.map +0 -1
  328. package/dist/src/utils/pythonRunner.js +0 -62
  329. package/dist/src/utils/pythonRunner.js.map +0 -1
  330. package/src/utils/errors.ts +0 -42
  331. package/src/utils/pythonRunner.ts +0 -81
@@ -7,15 +7,19 @@
7
7
  */
8
8
  import { execFile } from 'child_process'
9
9
  import { promisify } from 'util'
10
- import { existsSync } from 'fs'
11
- import { join, dirname } from 'path'
12
- import { fileURLToPath } from 'url'
10
+ import { existsSync, openSync, readSync, closeSync } from 'fs'
11
+ import { join } from 'path'
12
+ import crypto from 'crypto'
13
13
  import { configService } from '../services/configService.js'
14
+ import { getPythonCommand } from '../utils/python.js'
15
+ import { createPythonProcessEnv, safeSubprocessError } from '../utils/pythonProcessEnv.js'
16
+ import { resolvePackageRoot } from '../utils/packageRoot.js'
14
17
  import type { ChatSession, Message, Contact } from '../types.js'
15
18
 
16
19
  const execFileAsync = promisify(execFile)
17
- const __filename = fileURLToPath(import.meta.url)
18
- const __dirname = dirname(__filename)
20
+ function getPackageRoot(): string {
21
+ return resolvePackageRoot(import.meta.url)
22
+ }
19
23
 
20
24
  export interface NtResult {
21
25
  success: boolean
@@ -42,6 +46,19 @@ export interface NtScanResult {
42
46
  matched?: Array<{ path: string; name: string; salt: string; size: number; wxid: string; key: string }>
43
47
  }
44
48
 
49
+ interface NtPythonSecrets {
50
+ dbPath?: string
51
+ key?: string
52
+ salt?: string
53
+ contactDbPath?: string | null
54
+ contactKey?: string | null
55
+ contactSalt?: string | null
56
+ talker?: string
57
+ ownWxid?: string
58
+ keyword?: string
59
+ usernames?: string
60
+ }
61
+
45
62
  export class NtCore {
46
63
  private dbPath: string
47
64
  private keyHex: string
@@ -81,37 +98,58 @@ export class NtCore {
81
98
  }
82
99
 
83
100
  private get scriptPath(): string {
84
- // __dirname = dist/src/core, so ../../.. = package root
85
- const pkgRoot = join(__dirname, '..', '..', '..')
86
- return join(pkgRoot, 'scripts', 'nt_decrypt.py')
101
+ return join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
87
102
  }
88
103
 
89
- private async callPython(args: string[]): Promise<any> {
104
+ private async callPython(args: string[], secrets: NtPythonSecrets = {}): Promise<any> {
105
+ const values: NtPythonSecrets = {
106
+ dbPath: this.dbPath,
107
+ key: this.keyHex,
108
+ salt: this.saltHex,
109
+ contactDbPath: this.contactDbPath,
110
+ contactKey: this.contactKey,
111
+ contactSalt: this.contactSalt,
112
+ ...secrets,
113
+ }
90
114
  try {
91
- const { stdout } = await execFileAsync('python', [this.scriptPath, ...args], {
115
+ const { stdout } = await execFileAsync(getPythonCommand(), [this.scriptPath, ...args], {
92
116
  timeout: 120_000,
93
117
  maxBuffer: 50 * 1024 * 1024,
94
- env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
118
+ env: createPythonProcessEnv({
119
+ WEFLOW_DB_PATH: values.dbPath,
120
+ WEFLOW_NT_KEY: values.key,
121
+ WEFLOW_NT_SALT: values.salt,
122
+ WEFLOW_CONTACT_DB: values.contactDbPath || undefined,
123
+ WEFLOW_CONTACT_KEY: values.contactKey || undefined,
124
+ WEFLOW_CONTACT_SALT: values.contactSalt || undefined,
125
+ WEFLOW_TALKER: values.talker,
126
+ WEFLOW_OWN_WXID: values.ownWxid,
127
+ WEFLOW_QUERY_KEYWORD: values.keyword,
128
+ WEFLOW_QUERY_USERNAMES: values.usernames,
129
+ }),
95
130
  encoding: 'utf-8',
96
131
  })
97
132
 
98
133
  const lines = stdout.split('\n').filter((l: string) => l.trim())
99
134
  for (let i = lines.length - 1; i >= 0; i--) {
100
135
  const trimmed = lines[i].trim()
101
- if (trimmed.startsWith('{')) {
102
- return JSON.parse(trimmed)
136
+ if (trimmed.startsWith('{"')) {
137
+ try {
138
+ return JSON.parse(trimmed)
139
+ } catch {
140
+ // Try next line
141
+ }
103
142
  }
104
143
  }
105
144
  return { error: 'No valid JSON output from Python script' }
106
145
  } catch (e: any) {
107
- const msg = e?.message || String(e)
108
- if (msg.includes('ENOENT') || msg.includes('python')) {
146
+ if (e?.code === 'ENOENT') {
109
147
  return { error: 'Python not found. Install Python and sqlcipher3: pip install sqlcipher3' }
110
148
  }
111
- if (msg.includes('ETIMEDOUT') || msg.includes('killed')) {
149
+ if (e?.code === 'ETIMEDOUT' || e?.killed || e?.signal) {
112
150
  return { error: 'NT database query timed out' }
113
151
  }
114
- return { error: `NT database query failed: ${msg}` }
152
+ return { error: safeSubprocessError(e, 'NT database query failed') }
115
153
  }
116
154
  }
117
155
 
@@ -119,22 +157,23 @@ export class NtCore {
119
157
  * Scan memory for NT keys and match to databases.
120
158
  * Requires WeChat to be running.
121
159
  */
122
- static async scan(): Promise<NtScanResult> {
160
+ static async scan(root?: string): Promise<NtScanResult> {
123
161
  try {
124
- const pkgRoot = join(__dirname, '..', '..', '..')
125
- const scriptPath = join(pkgRoot, 'scripts', 'nt_decrypt.py')
126
- const { stdout } = await execFileAsync('python', [scriptPath, 'scan', '--json'], {
162
+ const scriptPath = join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
163
+ const scanArgs = ['scan', '--json']
164
+ const { stdout } = await execFileAsync(getPythonCommand(), [scriptPath, ...scanArgs], {
127
165
  timeout: 300_000,
128
166
  maxBuffer: 10 * 1024 * 1024,
129
- env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
167
+ env: createPythonProcessEnv(root ? { WEFLOW_SCAN_ROOT: root } : {}),
130
168
  encoding: 'utf-8',
131
169
  })
132
170
 
133
171
  const lines = stdout.split('\n').filter((l: string) => l.trim())
134
172
  for (let i = lines.length - 1; i >= 0; i--) {
135
173
  const trimmed = lines[i].trim()
136
- if (trimmed.startsWith('{')) {
137
- const result = JSON.parse(trimmed)
174
+ if (trimmed.startsWith('{"')) {
175
+ let result: any
176
+ try { result = JSON.parse(trimmed) } catch { continue }
138
177
  if (result.error) {
139
178
  return { success: false, error: result.error }
140
179
  }
@@ -148,28 +187,64 @@ export class NtCore {
148
187
  }
149
188
  return { success: false, error: 'Scan produced no valid output' }
150
189
  } catch (e: any) {
151
- const msg = e?.message || String(e)
152
- if (msg.includes('ENOENT') || msg.includes('python')) {
190
+ if (e?.code === 'ENOENT') {
153
191
  return { success: false, error: 'Python not found. Install Python and add to PATH.' }
154
192
  }
155
- return { success: false, error: `NT scan failed: ${msg}` }
193
+ return { success: false, error: safeSubprocessError(e, 'NT scan failed') }
156
194
  }
157
195
  }
158
196
 
159
- async getSessions(keyword?: string): Promise<NtSessionsResult> {
160
- const args: string[] = [
161
- 'sessions',
162
- '--db', this.dbPath,
163
- '--key', this.keyHex,
164
- '--salt', this.saltHex,
165
- ]
166
- if (keyword) args.push('--keyword', keyword)
167
- if (this.contactDbPath && this.contactKey && this.contactSalt) {
168
- args.push('--contact-db', this.contactDbPath)
169
- args.push('--contact-key', this.contactKey)
170
- args.push('--contact-salt', this.contactSalt)
197
+ /**
198
+ * 验证 key+salt 能否真实打开数据库 (读取 sqlite_master 触发解密)。
199
+ * cannotVerify=true 表示环境问题 (Python/sqlcipher3 缺失) 而非密钥错误。
200
+ */
201
+ static async verifyDbKey(dbPath: string, keyHex: string, saltHex: string): Promise<{
202
+ success: boolean
203
+ error?: string
204
+ tables?: number
205
+ cannotVerify?: boolean
206
+ }> {
207
+ const scriptPath = join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
208
+ let stdout = ''
209
+ try {
210
+ const r = await execFileAsync(getPythonCommand(), [scriptPath, 'verify'], {
211
+ timeout: 60_000,
212
+ maxBuffer: 1024 * 1024,
213
+ env: createPythonProcessEnv({
214
+ WEFLOW_DB_PATH: dbPath,
215
+ WEFLOW_NT_KEY: keyHex,
216
+ WEFLOW_NT_SALT: saltHex,
217
+ }),
218
+ encoding: 'utf-8',
219
+ })
220
+ stdout = r.stdout
221
+ } catch (e: any) {
222
+ // 缺 sqlcipher3 等场景: 脚本打印 JSON 后 exit 1
223
+ stdout = e?.stdout || ''
224
+ if (!stdout.trim()) {
225
+ return { success: false, cannotVerify: true, error: safeSubprocessError(e, 'Python 调用失败') }
226
+ }
227
+ }
228
+ const lines = stdout.split('\n').filter((l: string) => l.trim())
229
+ for (let i = lines.length - 1; i >= 0; i--) {
230
+ const trimmed = lines[i].trim()
231
+ if (trimmed.startsWith('{"')) {
232
+ try {
233
+ const r = JSON.parse(trimmed)
234
+ if (r.success === undefined) {
235
+ // 只有 error 字段 = 环境问题 (如 "需要 sqlcipher3")
236
+ return { success: false, cannotVerify: true, error: r.error }
237
+ }
238
+ return { success: !!r.success, tables: r.tables, error: r.error }
239
+ } catch { /* try next line */ }
240
+ }
171
241
  }
172
- const result = await this.callPython(args)
242
+ return { success: false, cannotVerify: true, error: 'verify 无有效输出' }
243
+ }
244
+
245
+ async getSessions(keyword?: string): Promise<NtSessionsResult> {
246
+ const args: string[] = ['sessions']
247
+ const result = await this.callPython(args, { keyword })
173
248
  if (result.error) {
174
249
  return { success: false, error: result.error }
175
250
  }
@@ -179,22 +254,12 @@ export class NtCore {
179
254
  async getMessages(talker: string, limit = 100, offset = 0): Promise<NtMessagesResult> {
180
255
  const args: string[] = [
181
256
  'messages',
182
- '--db', this.dbPath,
183
- '--key', this.keyHex,
184
- '--salt', this.saltHex,
185
- '--talker', talker,
186
257
  '--limit', String(limit),
187
258
  '--offset', String(offset),
188
259
  ]
189
- if (this.contactDbPath && this.contactKey && this.contactSalt) {
190
- args.push('--contact-db', this.contactDbPath)
191
- args.push('--contact-key', this.contactKey)
192
- args.push('--contact-salt', this.contactSalt)
193
- }
194
260
  // Pass own wxid from config for self-message detection
195
261
  const ownWxid = configService.get('wxid')
196
- if (ownWxid) args.push('--own-wxid', ownWxid)
197
- const result = await this.callPython(args)
262
+ const result = await this.callPython(args, { talker, ownWxid })
198
263
  if (result.error) {
199
264
  return { success: false, error: result.error }
200
265
  }
@@ -204,21 +269,97 @@ export class NtCore {
204
269
  async getContacts(keyword?: string, limit = 200): Promise<NtContactsResult> {
205
270
  const args: string[] = [
206
271
  'contacts',
207
- '--db', this.dbPath,
208
- '--key', this.keyHex,
209
- '--salt', this.saltHex,
210
272
  '--limit', String(limit),
211
273
  ]
212
- if (keyword) args.push('--keyword', keyword)
213
- if (this.contactDbPath && this.contactKey && this.contactSalt) {
214
- args.push('--contact-db', this.contactDbPath)
215
- args.push('--contact-key', this.contactKey)
216
- args.push('--contact-salt', this.contactSalt)
217
- }
218
- const result = await this.callPython(args)
274
+ const result = await this.callPython(args, { keyword })
219
275
  if (result.error) {
220
276
  return { success: false, error: result.error }
221
277
  }
222
278
  return { success: true, contacts: result.contacts || [] }
223
279
  }
280
+
281
+ // ====== SNS (朋友圈) ======
282
+
283
+ /** 查询 SNS 朋友圈时间线 */
284
+ async getSnsTimeline(snsDbPath: string, snsKey: string, snsSalt: string, opts: {
285
+ limit?: number; offset?: number; usernames?: string[];
286
+ keyword?: string; startTime?: number; endTime?: number;
287
+ } = {}): Promise<{ success: boolean; timeline?: any[]; error?: string }> {
288
+ const args: string[] = [
289
+ 'sns-timeline',
290
+ '--limit', String(opts.limit ?? 20),
291
+ '--offset', String(opts.offset ?? 0),
292
+ ]
293
+ if (opts.startTime) args.push('--start-time', String(opts.startTime))
294
+ if (opts.endTime) args.push('--end-time', String(opts.endTime))
295
+ const result = await this.callPython(args, {
296
+ dbPath: snsDbPath,
297
+ key: snsKey,
298
+ salt: snsSalt,
299
+ keyword: opts.keyword,
300
+ usernames: opts.usernames?.length ? JSON.stringify(opts.usernames) : undefined,
301
+ })
302
+ if (result.error) return { success: false, error: result.error }
303
+ return { success: true, timeline: result.timeline || [] }
304
+ }
305
+
306
+ /** 获取朋友圈中有动态的用户列表 */
307
+ async getSnsUsernames(snsDbPath: string, snsKey: string, snsSalt: string): Promise<{ success: boolean; usernames?: string[]; error?: string }> {
308
+ const result = await this.callPython(['sns-usernames'], { dbPath: snsDbPath, key: snsKey, salt: snsSalt })
309
+ if (result.error) return { success: false, error: result.error }
310
+ return { success: true, usernames: result.usernames || [] }
311
+ }
312
+
313
+ /** 获取朋友圈统计信息 */
314
+ async getSnsExportStats(snsDbPath: string, snsKey: string, snsSalt: string, myWxid?: string): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> {
315
+ const result = await this.callPython(['sns-stats'], { dbPath: snsDbPath, key: snsKey, salt: snsSalt, ownWxid: myWxid })
316
+ if (result.error) return { success: false, error: result.error }
317
+ return { success: true, data: result.data }
318
+ }
319
+
320
+ // ====== 收藏 (favorite.db) ======
321
+
322
+ /**
323
+ * 从全库共用 passphrase 派生某库的 raw key。
324
+ * 微信 4.x 各库密钥 = PBKDF2-HMAC-SHA512(passphrase, 库salt, 256000轮, 32字节)
325
+ */
326
+ static deriveRawKey(passphraseHex: string, saltHex: string): string {
327
+ const passphrase = Buffer.from(passphraseHex, 'hex')
328
+ const salt = Buffer.from(saltHex, 'hex')
329
+ return crypto.pbkdf2Sync(passphrase, salt, 256000, 32, 'sha512').toString('hex')
330
+ }
331
+
332
+ /** 读取数据库文件前 16 字节盐值 */
333
+ static readDbSalt(dbPath: string): string | null {
334
+ let fd: number
335
+ try {
336
+ fd = openSync(dbPath, 'r')
337
+ } catch {
338
+ return null
339
+ }
340
+ try {
341
+ const buf = Buffer.alloc(16)
342
+ const n = readSync(fd, buf, 0, 16, 0)
343
+ return n === 16 ? buf.toString('hex') : null
344
+ } catch {
345
+ return null
346
+ } finally {
347
+ closeSync(fd)
348
+ }
349
+ }
350
+
351
+ /** 查询收藏列表 (keyHex 为派生后的 raw key) */
352
+ async getFavorites(favDbPath: string, keyHex: string, opts: {
353
+ limit?: number; offset?: number; keyword?: string; favType?: number;
354
+ } = {}): Promise<{ success: boolean; favorites?: any[]; total?: number; error?: string }> {
355
+ const args: string[] = [
356
+ 'fav-list',
357
+ '--limit', String(opts.limit ?? 100),
358
+ '--offset', String(opts.offset ?? 0),
359
+ ]
360
+ if (opts.favType != null) args.push('--type', String(opts.favType))
361
+ const result = await this.callPython(args, { dbPath: favDbPath, key: keyHex, salt: undefined, keyword: opts.keyword })
362
+ if (result.error) return { success: false, error: result.error }
363
+ return { success: true, favorites: result.favorites || [], total: result.total ?? 0 }
364
+ }
224
365
  }
@@ -17,9 +17,23 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync, writeFile
17
17
  import { join, basename, dirname } from 'path'
18
18
  import os from 'os'
19
19
  import { DatabaseSync } from 'node:sqlite'
20
- import lz4 from 'lz4'
20
+ import { createRequire } from 'module'
21
21
  import type { ChatSession, Message, Contact } from '../types.js'
22
22
 
23
+ const require = createRequire(import.meta.url)
24
+
25
+ // lz4 是原生模块(仅 3.x WCDB 解压用),懒加载避免非 Windows 平台启动即崩溃
26
+ let lz4Lazy: any = null
27
+ function getLz4(): any {
28
+ if (lz4Lazy !== null) return lz4Lazy
29
+ try {
30
+ lz4Lazy = require('lz4')
31
+ } catch (e) {
32
+ lz4Lazy = undefined
33
+ }
34
+ return lz4Lazy
35
+ }
36
+
23
37
  const PAGE_SIZE = 4096
24
38
  const KEY_SIZE = 32
25
39
  const DEFAULT_ITER = 64000
@@ -549,6 +563,8 @@ export class SqlcipherCore {
549
563
  if (!buf) return null
550
564
 
551
565
  try {
566
+ const lz4 = getLz4()
567
+ if (!lz4) return null
552
568
  const maxSize = buf.length * 256
553
569
  const outBuf = Buffer.alloc(maxSize)
554
570
  const actualSize = lz4.decodeBlock(buf, outBuf)
@@ -3586,19 +3586,15 @@ export class WcdbCore {
3586
3586
  if (!this.ensureReady()) {
3587
3587
  return { success: false, error: 'WCDB 未连接' }
3588
3588
  }
3589
+ if (params && params.length > 0) {
3590
+ return { success: false, error: 'PARAMETER_BINDING_UNSUPPORTED' }
3591
+ }
3589
3592
  const startedAt = Date.now()
3590
3593
  try {
3591
3594
  if (!this.wcdbExecQuery) return { success: false, error: '接口未就绪' }
3592
3595
  const fallbackFlag = /fallback|diag|diagnostic/i.test(String(sql || ''))
3593
3596
  this.writeLog(`[audit:execQuery] kind=${kind} path=${path || ''} sql_len=${String(sql || '').length} fallback=${fallbackFlag ? 1 : 0}`)
3594
3597
 
3595
- // 如果提供了参数,使用参数化查询(需要 C++ 层支持)
3596
- // 注意:当前 wcdbExecQuery 可能不支持参数化,这是一个占位符实现
3597
- // TODO: 需要更新 C++ 层的 wcdb_exec_query 以支持参数绑定
3598
- if (params && params.length > 0) {
3599
- console.warn('[wcdbCore] execQuery: 参数化查询暂未在 C++ 层实现,将使用原始 SQL(可能存在注入风险)')
3600
- }
3601
-
3602
3598
  const normalizedKind = String(kind || '').toLowerCase()
3603
3599
  const isContactQuery = normalizedKind === 'contact' || /\bfrom\s+contact\b/i.test(String(sql))
3604
3600
  let effectivePath = path || ''
@@ -0,0 +1,111 @@
1
+ /**
2
+ * 助手守护进程管理 — start(后台常驻) / stop / status / run(前台)
3
+ * pid 文件: ~/.weflow-cli/assistant.pid, 日志: ~/.weflow-cli/assistant.log
4
+ */
5
+ import { join } from 'path'
6
+ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, appendFileSync, statSync } from 'fs'
7
+ import os from 'os'
8
+ import { spawn, type ChildProcess } from 'child_process'
9
+ import { resolvePackageRoot } from '../utils/packageRoot.js'
10
+ import { createPythonProcessEnv } from '../utils/pythonProcessEnv.js'
11
+
12
+ const DIR = join(os.homedir(), '.weflow-cli')
13
+ const PID_FILE = join(DIR, 'assistant.pid')
14
+ const LOG_FILE = join(DIR, 'assistant.log')
15
+ const MARKER = 'WEFLOW_ASSISTANT_DAEMON=1'
16
+
17
+ export function isDaemonAlive(): { alive: boolean; pid: number | null } {
18
+ if (!existsSync(PID_FILE)) return { alive: false, pid: null }
19
+ try {
20
+ const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim())
21
+ // Windows: process.kill(pid, 0) 对不存在进程抛错; 对存在进程返回 undefined
22
+ process.kill(pid, 0)
23
+ return { alive: true, pid }
24
+ } catch {
25
+ rmSync(PID_FILE, { force: true })
26
+ return { alive: false, pid: null }
27
+ }
28
+ }
29
+
30
+ export function appendLog(line: string): void {
31
+ try {
32
+ if (!existsSync(DIR)) mkdirSync(DIR, { recursive: true })
33
+ appendFileSync(LOG_FILE, line + '\n', 'utf8')
34
+ } catch { /* 日志失败不阻断 */ }
35
+ }
36
+
37
+ /** 日志轮转: 超过 1MB 保留尾部 256KB, 防止常驻进程日志无限增长 */
38
+ export function rotateLogIfNeeded(): void {
39
+ try {
40
+ if (!existsSync(LOG_FILE)) return
41
+ if (statSync(LOG_FILE).size < 1024 * 1024) return
42
+ const buf = readFileSync(LOG_FILE)
43
+ const tail = buf.subarray(Math.max(0, buf.length - 256 * 1024))
44
+ const cut = tail.indexOf('\n') // 对齐到行首
45
+ writeFileSync(LOG_FILE, `--- rotated at ${new Date().toLocaleString('zh-CN')} ---\n` + tail.subarray(cut + 1))
46
+ appendLog(`[轮转] 日志已截断 (原 ${statSync(LOG_FILE).size} 字节)`)
47
+ } catch { /* 轮转失败不阻断 */ }
48
+ }
49
+
50
+ export function tailLog(n = 20): string {
51
+ try {
52
+ const lines = readFileSync(LOG_FILE, 'utf8').trim().split('\n')
53
+ return lines.slice(-n).join('\n')
54
+ } catch {
55
+ return '(暂无日志)'
56
+ }
57
+ }
58
+
59
+ /** 后台启动守护进程 (detached, 脱离终端生命周期) */
60
+ export function startDaemon(): { started: boolean; pid?: number; error?: string } {
61
+ const { alive, pid } = isDaemonAlive()
62
+ if (alive) return { started: false, pid: pid!, error: `已在运行 (pid ${pid})` }
63
+
64
+ const packageRoot = resolvePackageRoot(import.meta.url)
65
+ const compiledEntry = join(packageRoot, 'dist', 'bin', 'weflow-cli.js')
66
+ const sourceEntry = join(packageRoot, 'bin', 'weflow-cli.ts')
67
+ const entry = existsSync(compiledEntry) ? compiledEntry : sourceEntry
68
+
69
+ if (!existsSync(entry)) return { started: false, error: `入口不存在: ${entry}` }
70
+
71
+ try {
72
+ appendLog(`--- ${new Date().toLocaleString('zh-CN')} daemon starting ---`)
73
+ const entryArgs = entry === sourceEntry ? ['--import', 'tsx', entry] : [entry]
74
+ const child: ChildProcess = spawn(process.execPath, [...entryArgs, 'assistant', 'run'], {
75
+ detached: true,
76
+ stdio: ['ignore', 'pipe', 'pipe'],
77
+ env: createPythonProcessEnv({ [MARKER]: '1' }),
78
+ windowsHide: true,
79
+ })
80
+
81
+ // 收集子进程输出写入日志
82
+ child.stdout?.on('data', (d: Buffer) => appendLog(d.toString().trim()))
83
+ child.stderr?.on('data', (d: Buffer) => appendLog('[stderr] ' + d.toString().trim()))
84
+ child.on('exit', (code) => {
85
+ appendLog(`--- daemon exited (code ${code}) ---`)
86
+ try { if (existsSync(PID_FILE)) rmSync(PID_FILE, { force: true }) } catch {}
87
+ })
88
+
89
+ if (child.pid) {
90
+ writeFileSync(PID_FILE, String(child.pid), 'utf8')
91
+ return { started: true, pid: child.pid }
92
+ }
93
+ return { started: false, error: '无法获取子进程 pid' }
94
+ } catch (e: any) {
95
+ return { started: false, error: e.message }
96
+ }
97
+ }
98
+
99
+ export function stopDaemon(): { stopped: boolean; message: string } {
100
+ const { alive, pid } = isDaemonAlive()
101
+ if (!alive) return { stopped: false, message: '守护进程未在运行' }
102
+ try {
103
+ // Windows 下 SIGTERM/SIGKILL 等价 terminate
104
+ process.kill(pid!, 'SIGTERM')
105
+ rmSync(PID_FILE, { force: true })
106
+ appendLog(`--- ${new Date().toLocaleString('zh-CN')} daemon stopped manually ---`)
107
+ return { stopped: true, message: `已停止 (pid ${pid})` }
108
+ } catch (e: any) {
109
+ return { stopped: false, message: `停止失败: ${e.message}` }
110
+ }
111
+ }