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
@@ -1,5 +1,5 @@
1
- import { join, basename } from 'path'
2
- import { existsSync, readdirSync, statSync, readFileSync } from 'fs'
1
+ import { join, basename, dirname } from 'path'
2
+ import { existsSync, readdirSync, statSync, lstatSync, readFileSync } from 'fs'
3
3
  import { homedir } from 'os'
4
4
  import { createDecipheriv } from 'crypto'
5
5
  import { expandHomePath } from '../utils/pathUtils.js'
@@ -12,6 +12,100 @@ export interface WxidInfo {
12
12
  }
13
13
 
14
14
  export class DbPathService {
15
+ /**
16
+ * Validate a user-selected WeChat data location. WeChat's picker may expose
17
+ * either the data root, an account folder, or that account's db_storage.
18
+ */
19
+ resolveDataRoot(inputPath: string): string | null {
20
+ const rootPath = expandHomePath(inputPath)
21
+ if (!rootPath || !existsSync(rootPath)) return null
22
+
23
+ const ancestorPath = this.findAccountAncestor(rootPath)
24
+ if (ancestorPath) return dirname(ancestorPath)
25
+
26
+ const descendantPath = this.findAccountDescendant(rootPath, 3)
27
+ if (descendantPath) {
28
+ return dirname(descendantPath)
29
+ }
30
+
31
+ return null
32
+ }
33
+
34
+ private findAccountAncestor(inputPath: string): string | null {
35
+ let currentPath = inputPath
36
+ for (let level = 0; level <= 4; level++) {
37
+ if (this.isAccountDir(currentPath)) return currentPath
38
+ const parentPath = dirname(currentPath)
39
+ if (parentPath === currentPath) break
40
+ currentPath = parentPath
41
+ }
42
+ return null
43
+ }
44
+
45
+ private findAccountDescendant(rootPath: string, maxDepth: number): string | null {
46
+ if (maxDepth < 0) return null
47
+ if (this.isAccountDir(rootPath)) return rootPath
48
+
49
+ try {
50
+ for (const entry of readdirSync(rootPath)) {
51
+ const entryPath = join(rootPath, entry)
52
+ let stat: ReturnType<typeof statSync>
53
+ try {
54
+ if (lstatSync(entryPath).isSymbolicLink()) continue
55
+ stat = statSync(entryPath)
56
+ } catch {
57
+ continue
58
+ }
59
+ if (!stat.isDirectory()) continue
60
+ if (this.shouldSkipSearchDirectory(entry)) continue
61
+ const accountPath = this.findAccountDescendant(entryPath, maxDepth - 1)
62
+ if (accountPath) return accountPath
63
+ }
64
+ } catch { }
65
+
66
+ return null
67
+ }
68
+
69
+ private shouldSkipSearchDirectory(name: string): boolean {
70
+ return [
71
+ '$recycle.bin', 'node_modules', 'windows', 'program files',
72
+ 'program files (x86)', 'programdata', 'system volume information',
73
+ ].includes(name.toLowerCase())
74
+ }
75
+
76
+ private findKnownDataRoots(basePath: string, maxDepth: number): string[] {
77
+ const roots: string[] = []
78
+ const visit = (currentPath: string, depth: number): void => {
79
+ if (depth > maxDepth) return
80
+ let entries: string[]
81
+ try {
82
+ entries = readdirSync(currentPath)
83
+ } catch {
84
+ return
85
+ }
86
+
87
+ for (const entry of entries) {
88
+ const entryPath = join(currentPath, entry)
89
+ try {
90
+ if (lstatSync(entryPath).isSymbolicLink()) continue
91
+ } catch {
92
+ continue
93
+ }
94
+ if (!this.isDirectory(entryPath)) continue
95
+ const lower = entry.toLowerCase()
96
+ if (lower === 'xwechat_files' || lower === 'wechat files') {
97
+ roots.push(entryPath)
98
+ continue
99
+ }
100
+ if (depth < maxDepth && !this.shouldSkipSearchDirectory(lower) && lower !== 'appdata') {
101
+ visit(entryPath, depth + 1)
102
+ }
103
+ }
104
+ }
105
+ if (this.isDirectory(basePath)) visit(basePath, 0)
106
+ return roots
107
+ }
108
+
15
109
  private readVarint(buf: Buffer, offset: number): { value: number, length: number } {
16
110
  let value = 0;
17
111
  let length = 0;
@@ -89,7 +183,7 @@ export class DbPathService {
89
183
  /**
90
184
  * 自动检测微信数据库根目录
91
185
  */
92
- async autoDetect(): Promise<{ success: boolean; path?: string; error?: string }> {
186
+ async autoDetect(options: { searchDrives?: boolean; fullScan?: boolean } = {}): Promise<{ success: boolean; path?: string; error?: string }> {
93
187
  try {
94
188
  const possiblePaths: string[] = []
95
189
  const home = homedir()
@@ -110,6 +204,12 @@ export class DbPathService {
110
204
  }
111
205
  // macOS 旧路径兜底
112
206
  possiblePaths.push(join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Documents', 'xwechat_files'))
207
+ } else if (process.platform === 'linux') {
208
+ // Linux 原生微信 4.x:数据结构与 Windows 相同(xwechat_files),XDG 文档目录随 locale 变化
209
+ possiblePaths.push(join(home, '.local', 'share', 'xwechat_files'))
210
+ possiblePaths.push(join(home, 'xwechat_files'))
211
+ possiblePaths.push(join(home, 'Documents', 'xwechat_files'))
212
+ possiblePaths.push(join(home, '文档', 'xwechat_files'))
113
213
  } else {
114
214
  // Windows: 优先检测 NT 格式 (xwechat_files)
115
215
  possiblePaths.push(join(home, 'xwechat_files'))
@@ -117,20 +217,36 @@ export class DbPathService {
117
217
  possiblePaths.push(join(home, 'Documents', 'xwechat_files'))
118
218
  // Windows 微信3.x 数据目录
119
219
  possiblePaths.push(join(home, 'Documents', 'WeChat Files'))
220
+ for (const basePath of [
221
+ process.env.LOCALAPPDATA,
222
+ process.env.APPDATA,
223
+ process.env.USERPROFILE,
224
+ process.env.OneDrive,
225
+ ]) {
226
+ if (basePath) possiblePaths.push(...this.findKnownDataRoots(basePath, 3))
227
+ }
120
228
  }
121
229
 
122
230
  for (const path of possiblePaths) {
123
- if (!existsSync(path)) continue
231
+ const dataRoot = this.resolveDataRoot(path)
232
+ if (dataRoot) {
233
+ return { success: true, path: dataRoot }
234
+ }
235
+ }
124
236
 
125
- // 检查是否有有效的账号目录,或本身就是账号目录
126
- const accounts = this.findAccountDirs(path)
127
- if (accounts.length > 0) {
128
- return { success: true, path }
237
+ if (process.platform === 'win32' && (options.searchDrives || options.fullScan)) {
238
+ for (const volumeRoot of this.getVolumeRoots()) {
239
+ for (const dataRootPath of this.findKnownDataRoots(volumeRoot, 12)) {
240
+ const dataRoot = this.resolveDataRoot(dataRootPath)
241
+ if (dataRoot) return { success: true, path: dataRoot }
242
+ }
129
243
  }
244
+ }
130
245
 
131
- // 如果该目录本身就是账号目录(直接包含 db_storage 等)
132
- if (this.isAccountDir(path)) {
133
- return { success: true, path }
246
+ if (options.fullScan) {
247
+ for (const volumeRoot of this.getVolumeRoots()) {
248
+ const accountPath = this.findAccountDescendant(volumeRoot, 32)
249
+ if (accountPath) return { success: true, path: dirname(accountPath) }
134
250
  }
135
251
  }
136
252
 
@@ -175,13 +291,31 @@ export class DbPathService {
175
291
 
176
292
  private isAccountDir(entryPath: string): boolean {
177
293
  return (
178
- existsSync(join(entryPath, 'db_storage')) || // WeChat 4.x
179
- existsSync(join(entryPath, 'Msg')) || // WeChat 3.x
180
- existsSync(join(entryPath, 'FileStorage', 'Image')) ||
181
- existsSync(join(entryPath, 'FileStorage', 'Image2'))
294
+ this.isDirectory(join(entryPath, 'db_storage')) || // WeChat 4.x
295
+ this.isDirectory(join(entryPath, 'Msg')) || // WeChat 3.x
296
+ this.isDirectory(join(entryPath, 'FileStorage', 'Image')) ||
297
+ this.isDirectory(join(entryPath, 'FileStorage', 'Image2'))
182
298
  )
183
299
  }
184
300
 
301
+ private getVolumeRoots(): string[] {
302
+ if (process.platform !== 'win32') return [homedir()]
303
+ const roots: string[] = []
304
+ for (let code = 65; code <= 90; code++) {
305
+ const volumeRoot = `${String.fromCharCode(code)}:\\`
306
+ if (this.isDirectory(volumeRoot)) roots.push(volumeRoot)
307
+ }
308
+ return roots
309
+ }
310
+
311
+ private isDirectory(entryPath: string): boolean {
312
+ try {
313
+ return statSync(entryPath).isDirectory()
314
+ } catch {
315
+ return false
316
+ }
317
+ }
318
+
185
319
  private isPotentialAccountName(name: string): boolean {
186
320
  const lower = name.toLowerCase()
187
321
  if (lower.startsWith('all') || lower.startsWith('applet') || lower.startsWith('backup') || lower.startsWith('wmpf')) {
@@ -223,7 +357,7 @@ export class DbPathService {
223
357
  * 扫描目录名候选(仅包含下划线的文件夹,排除 all_users)
224
358
  */
225
359
  scanWxidCandidates(rootPath: string): WxidInfo[] {
226
- const resolvedRootPath = expandHomePath(rootPath)
360
+ const resolvedRootPath = this.resolveDataRoot(rootPath) || expandHomePath(rootPath)
227
361
  const wxids: WxidInfo[] = []
228
362
 
229
363
  try {
@@ -237,6 +371,7 @@ export class DbPathService {
237
371
  const lower = entry.toLowerCase()
238
372
  if (lower === 'all_users') continue
239
373
  if (!entry.includes('_')) continue
374
+ if (!this.isAccountDir(entryPath)) continue
240
375
  wxids.push({ wxid: entry, modifiedTime: stat.mtimeMs })
241
376
  }
242
377
  }
@@ -244,7 +379,7 @@ export class DbPathService {
244
379
 
245
380
  if (wxids.length === 0) {
246
381
  const rootName = basename(resolvedRootPath)
247
- if (rootName.includes('_') && rootName.toLowerCase() !== 'all_users') {
382
+ if (rootName.includes('_') && rootName.toLowerCase() !== 'all_users' && this.isAccountDir(resolvedRootPath)) {
248
383
  const rootStat = statSync(resolvedRootPath)
249
384
  wxids.push({ wxid: rootName, modifiedTime: rootStat.mtimeMs })
250
385
  }
@@ -274,7 +409,7 @@ export class DbPathService {
274
409
  * 扫描 wxid 列表
275
410
  */
276
411
  scanWxids(rootPath: string): WxidInfo[] {
277
- const resolvedRootPath = expandHomePath(rootPath)
412
+ const resolvedRootPath = this.resolveDataRoot(rootPath) || expandHomePath(rootPath)
278
413
  const wxids: WxidInfo[] = []
279
414
 
280
415
  try {
@@ -332,6 +467,16 @@ export class DbPathService {
332
467
  // 旧版本路径兜底
333
468
  return join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Documents', 'xwechat_files')
334
469
  }
470
+ if (process.platform === 'linux') {
471
+ for (const p of [
472
+ join(home, '.local', 'share', 'xwechat_files'),
473
+ join(home, 'xwechat_files'),
474
+ join(home, 'Documents', 'xwechat_files'),
475
+ join(home, '文档', 'xwechat_files'),
476
+ ]) {
477
+ if (existsSync(p)) return p
478
+ }
479
+ }
335
480
  // 优先返回 4.x 路径
336
481
  const xwechatPath = join(home, 'Documents', 'xwechat_files')
337
482
  if (existsSync(xwechatPath)) return xwechatPath
@@ -1,13 +1,23 @@
1
1
  import { join } from 'path'
2
2
  import { existsSync, copyFileSync, mkdirSync } from 'fs'
3
- import { execFile, execSync } from 'child_process'
3
+ import { execFile } from 'child_process'
4
4
  import { promisify } from 'util'
5
5
  import { createRequire } from 'module'
6
6
  import os from 'os'
7
7
  import type { DbKeyResult } from '../types.js'
8
+ import { configService } from '../services/configService.js'
9
+ import { getPythonCommand } from '../utils/python.js'
10
+ import { createPythonProcessEnv } from '../utils/pythonProcessEnv.js'
11
+ import { resolvePackageRoot } from '../utils/packageRoot.js'
8
12
 
9
13
  const require = createRequire(import.meta.url)
10
14
  const execFileAsync = promisify(execFile)
15
+ const PACKAGE_ROOT = resolvePackageRoot(import.meta.url)
16
+
17
+ /** 基于模块位置定位包根目录(全局安装时 process.cwd() 不可靠) */
18
+ function resolveScriptPath(scriptName: string): string {
19
+ return join(PACKAGE_ROOT, 'scripts', scriptName)
20
+ }
11
21
 
12
22
  export class KeyService {
13
23
  private koffi: any = null
@@ -26,6 +36,8 @@ export class KeyService {
26
36
  private QueryFullProcessImageNameW: any = null
27
37
 
28
38
  private readonly PROCESS_ALL_ACCESS = 0x1F0FFF
39
+ private readonly PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
40
+ private readonly exitedPids = new Set<number>()
29
41
 
30
42
  private getDllPath(): string {
31
43
  const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
@@ -35,10 +47,15 @@ export class KeyService {
35
47
  candidates.push(process.env.WX_KEY_DLL_PATH)
36
48
  }
37
49
 
50
+ // 基于模块位置定位(全局安装时 cwd 不是包根)
51
+ candidates.push(join(PACKAGE_ROOT, 'resources', 'key', 'win32', archDir, 'wx_key.dll'))
52
+ candidates.push(join(PACKAGE_ROOT, 'resources', 'key', 'win32', 'x64', 'wx_key.dll'))
53
+ candidates.push(join(PACKAGE_ROOT, 'resources', 'key', 'win32', 'wx_key.dll'))
54
+ candidates.push(join(PACKAGE_ROOT, 'resources', 'wx_key.dll'))
55
+
56
+ // cwd 兜底(开发模式在项目根运行)
38
57
  const cwd = process.cwd()
39
58
  candidates.push(join(cwd, 'resources', 'key', 'win32', archDir, 'wx_key.dll'))
40
- candidates.push(join(cwd, 'resources', 'key', 'win32', 'x64', 'wx_key.dll'))
41
- candidates.push(join(cwd, 'resources', 'key', 'win32', 'wx_key.dll'))
42
59
  candidates.push(join(cwd, 'resources', 'wx_key.dll'))
43
60
 
44
61
  for (const path of candidates) {
@@ -139,7 +156,7 @@ export class KeyService {
139
156
  const parts = line.split('","').map((p) => p.replace(/^"|"$/g, ''))
140
157
  if (parts[0]?.toLowerCase() === imageName.toLowerCase()) {
141
158
  const pid = Number(parts[1])
142
- if (!Number.isNaN(pid)) return pid
159
+ if (!Number.isNaN(pid) && await this.isProcessRunning(pid)) return pid
143
160
  }
144
161
  }
145
162
  return null
@@ -148,6 +165,20 @@ export class KeyService {
148
165
  }
149
166
  }
150
167
 
168
+ private async isProcessRunning(pid: number): Promise<boolean> {
169
+ if (this.exitedPids.has(pid)) return false
170
+ try {
171
+ const command = `$process = Get-Process -Id ${pid} -ErrorAction SilentlyContinue; if ($process -and -not $process.HasExited) { 'running' }`
172
+ const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command])
173
+ const running = stdout.trim() === 'running'
174
+ if (!running) this.exitedPids.add(pid)
175
+ return running
176
+ } catch {
177
+ this.exitedPids.add(pid)
178
+ return false
179
+ }
180
+ }
181
+
151
182
  async findWeChatPid(): Promise<number | null> {
152
183
  const names = ['Weixin.exe', 'WeChat.exe']
153
184
  for (const name of names) {
@@ -159,7 +190,7 @@ export class KeyService {
159
190
 
160
191
  private async getProcessExecutablePath(pid: number): Promise<string | null> {
161
192
  if (!this.ensureKernel32()) return null
162
- const hProcess = this.OpenProcess(0x1000, false, pid)
193
+ const hProcess = this.OpenProcess(this.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
163
194
  if (!hProcess) return null
164
195
 
165
196
  try {
@@ -215,7 +246,7 @@ export class KeyService {
215
246
  return { success: false, error: '等待超时,未检测到微信进程' }
216
247
  }
217
248
 
218
- onStatus?.(`检测到微信进程 (PID: ${pid}),立即 hook...`)
249
+ onStatus?.('检测到微信进程,正在准备初始化...')
219
250
  return this.extractKeyFromPid(pid, logs, onStatus)
220
251
  }
221
252
 
@@ -244,8 +275,8 @@ export class KeyService {
244
275
  return { success: false, error: '未找到微信进程,请先启动微信并登录' }
245
276
  }
246
277
 
247
- onStatus?.(`检测到微信进程 (PID: ${pid}),正在提取 key...`)
248
- return this.extractKeyFromPid(pid, logs, onStatus)
278
+ onStatus?.('检测到微信进程,正在准备初始化...')
279
+ return this.extractKeyFromPid(pid, logs, onStatus, timeoutMs)
249
280
  }
250
281
 
251
282
  private async extractKeyFromPid(
@@ -254,7 +285,7 @@ export class KeyService {
254
285
  onStatus?: (message: string) => void,
255
286
  timeoutMs = 90_000
256
287
  ): Promise<DbKeyResult> {
257
-
288
+ onStatus?.('正在安装本地访问组件...')
258
289
  const ok = this.initHook(pid)
259
290
  if (!ok) {
260
291
  const error = this.getLastErrorMsg ? this.decodeCString(this.getLastErrorMsg()) : ''
@@ -262,11 +293,13 @@ export class KeyService {
262
293
  if (error.includes('0xC0000022') || error.includes('ACCESS_DENIED')) {
263
294
  return { success: false, error: '权限不足,请以管理员身份运行 CLI' }
264
295
  }
265
- return { success: false, error }
296
+ return { success: false, error: '本地访问组件初始化失败' }
266
297
  }
267
298
  return { success: false, error: '初始化 Hook 失败' }
268
299
  }
269
300
 
301
+ onStatus?.('本地访问组件已就绪,等待数据库初始化事件...')
302
+
270
303
  const keyBuffer = Buffer.alloc(128)
271
304
  const start = Date.now()
272
305
  let pollCount = 0
@@ -277,27 +310,19 @@ export class KeyService {
277
310
  const pollResult = this.pollKeyData(keyBuffer, keyBuffer.length)
278
311
  if (pollResult) {
279
312
  const key = this.decodeUtf8(keyBuffer)
280
- onStatus?.(`[调试] pollKeyData=true, key长度=${key.length}, 内容=${key.slice(0, 20)}...`)
281
313
  if (key.length === 64) {
282
314
  onStatus?.('密钥获取成功!')
283
315
  return { success: true, key, logs }
284
316
  }
285
- } else if (pollCount <= 5 || pollCount % 20 === 0) {
286
- // 前5次和每20次输出一次调试信息
287
- const hexSample = keyBuffer.slice(0, 16).toString('hex')
288
- onStatus?.(`[调试] poll #${pollCount}: pollResult=${pollResult}, buffer=${hexSample}`)
317
+ } else if (pollCount % 100 === 0) {
318
+ onStatus?.('仍在等待数据库初始化事件...')
289
319
  }
290
320
 
291
- // 读取状态消息
321
+ // Drain native status messages without forwarding process details to the terminal.
292
322
  for (let i = 0; i < 5; i++) {
293
323
  const statusBuffer = Buffer.alloc(256)
294
324
  const levelOut = [0]
295
325
  if (!this.getStatusMessage(statusBuffer, statusBuffer.length, levelOut)) break
296
- const msg = this.decodeUtf8(statusBuffer)
297
- if (msg) {
298
- logs.push(msg)
299
- onStatus?.(msg)
300
- }
301
326
  }
302
327
 
303
328
  await new Promise(resolve => setTimeout(resolve, 120))
@@ -306,7 +331,94 @@ export class KeyService {
306
331
  try { this.cleanupHook() } catch { }
307
332
  }
308
333
 
309
- return { success: false, error: '获取密钥超时,请确保微信已登录', logs }
334
+ return { success: false, error: '等待超时,未观察到数据库初始化事件', logs }
335
+ }
336
+
337
+ /**
338
+ * 捕获 sns.db 解密密钥。通过 Hook 微信数据库打开操作,
339
+ * 捕获 sns.db 被访问时的密钥并测试匹配。
340
+ */
341
+ async captureSnsKey(
342
+ timeoutMs = 30000,
343
+ snsDbPath: string,
344
+ onStatus?: (message: string) => void
345
+ ): Promise<DbKeyResult> {
346
+ if (process.platform !== 'win32') {
347
+ return { success: false, error: '朋友圈密钥捕获仅支持 Windows(依赖进程 Hook)' }
348
+ }
349
+ if (!this.ensureLoaded()) {
350
+ return { success: false, error: 'wx_key.dll 未加载,请确保 resources/key/ 目录存在' }
351
+ }
352
+
353
+ const pid = await this.findWeChatPid()
354
+ if (!pid) {
355
+ return { success: false, error: '未找到微信进程 (Weixin.exe),请确保微信已登录' }
356
+ }
357
+
358
+ const ok = this.initHook(pid)
359
+ if (!ok) {
360
+ const error = this.getLastErrorMsg ? this.decodeCString(this.getLastErrorMsg()) : ''
361
+ if (error) return { success: false, error }
362
+ return { success: false, error: '初始化 Hook 失败,请以管理员身份运行' }
363
+ }
364
+
365
+ onStatus?.('本地访问组件已就绪,等待数据库访问...')
366
+
367
+ const keyBuffer = Buffer.alloc(128)
368
+ const capturedKeys = new Set<string>()
369
+ const start = Date.now()
370
+
371
+ try {
372
+ while (Date.now() - start < timeoutMs) {
373
+ const pollResult = this.pollKeyData(keyBuffer, keyBuffer.length)
374
+ if (pollResult) {
375
+ const key = this.decodeUtf8(keyBuffer)
376
+ if (key.length === 64 && !capturedKeys.has(key)) {
377
+ capturedKeys.add(key)
378
+ onStatus?.('捕获到密钥,正在测试 sns.db...')
379
+
380
+ // Test against sns.db using Python
381
+ const snsSalt = configService.get('snsSalt')
382
+ try {
383
+ const scriptPath = resolveScriptPath('nt_decrypt.py')
384
+ const { stdout } = await execFileAsync(getPythonCommand(), [
385
+ scriptPath, 'sns-stats',
386
+ ], {
387
+ timeout: 15000, encoding: 'utf8',
388
+ env: createPythonProcessEnv({
389
+ WEFLOW_DB_PATH: snsDbPath,
390
+ WEFLOW_NT_KEY: key,
391
+ WEFLOW_NT_SALT: snsSalt || '00000000000000000000000000000000',
392
+ }),
393
+ })
394
+
395
+ const lines = stdout.trim().split('\n')
396
+ const result = JSON.parse(lines[lines.length - 1] || '{}')
397
+ if (result.success) {
398
+ return { success: true, key }
399
+ }
400
+ } catch {
401
+ // Test fails for this key, continue polling
402
+ }
403
+ }
404
+ }
405
+
406
+ // Read status
407
+ for (let i = 0; i < 2; i++) {
408
+ const statusBuffer = Buffer.alloc(256)
409
+ const levelOut = [0]
410
+ if (!this.getStatusMessage(statusBuffer, statusBuffer.length, levelOut)) break
411
+ const msg = this.decodeUtf8(statusBuffer)
412
+ if (msg) onStatus?.(msg)
413
+ }
414
+
415
+ await new Promise(resolve => setTimeout(resolve, 200))
416
+ }
417
+ } finally {
418
+ try { this.cleanupHook() } catch { }
419
+ }
420
+
421
+ return { success: false, error: `超时 (${timeoutMs / 1000}s),未捕获到 sns.db 的密钥。请确保在微信中打开了朋友圈` }
310
422
  }
311
423
 
312
424
  /**
@@ -339,16 +451,16 @@ export class KeyService {
339
451
  return { success: false, error: '未找到 WeChat 3.x (WeChat.exe) 进程,请先启动 3.x 微信并登录' }
340
452
  }
341
453
 
342
- onStatus?.(`找到 3.x 进程 (PID: ${pid}),正在通过内存搜索提取密钥...`)
454
+ onStatus?.('检测到 3.x 微信进程,正在准备初始化...')
343
455
 
344
456
  try {
345
- const scriptPath = join(process.cwd(), 'scripts', 'extract_3x_key.py')
457
+ const scriptPath = resolveScriptPath('extract_3x_key.py')
346
458
 
347
459
  if (!existsSync(scriptPath)) {
348
460
  return { success: false, error: `Python 脚本不存在: ${scriptPath}` }
349
461
  }
350
462
 
351
- const { stdout } = await execFileAsync('python', [scriptPath], {
463
+ const { stdout } = await execFileAsync(getPythonCommand(), [scriptPath], {
352
464
  timeout: 120_000,
353
465
  maxBuffer: 1024 * 1024
354
466
  })