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
@@ -1,146 +1,201 @@
1
- #!/usr/bin/env python3
2
- """
3
- 同步收藏夹 — 根据 .fav_state.json 将收藏文章 symlink 到 收藏/ 文件夹。
4
-
5
- 用法:
6
- python scripts/sync_fav.py --date 2026-05-19
7
- python scripts/sync_fav.py --date 2026-05-19 --add "AI/某文章.md" --remove "学术/某文章.md"
8
-
9
- .fav_state.json 格式: ["AI/文章1.md", "学术/文章2.md", ...]
10
- 与 HTML localStorage 的 weflow_fav_{date} 格式一致。
11
- """
12
-
13
- import sys, os, json
14
- from pathlib import Path
15
- from datetime import datetime
16
-
17
- SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
18
- SOURCE_ROOT = os.path.join(os.path.dirname(SCRIPTS_DIR), 'output', 'biz-daily')
19
-
20
-
21
- def sync_favorites(date_str: str):
22
- """读取 .fav_state.json 并同步 收藏/ 文件夹中的 symlink。"""
23
- date_dir = Path(SOURCE_ROOT) / date_str
24
- if not date_dir.is_dir():
25
- print(f'[ERROR] 目录不存在: {date_dir}')
26
- return
27
-
28
- fav_dir = date_dir / '收藏'
29
- fav_state_file = date_dir / '.fav_state.json'
30
-
31
- # 读取收藏列表
32
- if fav_state_file.exists():
33
- try:
34
- with open(fav_state_file, 'r', encoding='utf-8') as f:
35
- fav_list = json.load(f)
36
- except Exception:
37
- print(f'[ERROR] 无法解析 .fav_state.json')
38
- return
39
- else:
40
- fav_list = []
41
-
42
- if not fav_list:
43
- print('收藏列表为空,清理 收藏/ 文件夹...')
44
- if fav_dir.is_dir():
45
- for link in fav_dir.iterdir():
46
- if link.is_symlink() or link.is_file():
47
- link.unlink()
48
- print(f' 移除: {link.name}')
49
- return
50
-
51
- # 创建收藏文件夹
52
- fav_dir.mkdir(parents=True, exist_ok=True)
53
-
54
- # 构建期望的链接集合
55
- desired = set()
56
- for rel_path in fav_list:
57
- src = date_dir / rel_path
58
- if src.exists():
59
- desired.add(rel_path)
60
- else:
61
- print(f'[WARN] 源文件不存在: {rel_path}')
62
-
63
- # 清理不在列表中的旧链接
64
- existing = set()
65
- for item in fav_dir.iterdir():
66
- if item.is_symlink():
67
- existing.add(item.name)
68
- if item.name not in {Path(p).name for p in desired}:
69
- item.unlink()
70
- print(f' 移除: {item.name}')
71
- elif item.is_file():
72
- # symlink 的文件也清理
73
- existing.add(item.name)
74
- if item.name not in {Path(p).name for p in desired}:
75
- item.unlink()
76
- print(f' 移除: {item.name}')
77
-
78
- # 创建缺失的 symlink
79
- for rel_path in desired:
80
- src = date_dir / rel_path
81
- link = fav_dir / src.name
82
- if not link.exists():
83
- try:
84
- link.symlink_to(os.path.relpath(src, fav_dir))
85
- print(f' 添加: {src.name}')
86
- except OSError:
87
- # Windows 可能不支持 symlink,尝试复制
88
- import shutil
89
- shutil.copy2(src, link)
90
- print(f' 复制: {src.name}')
91
-
92
-
93
- def manage_fav(date_str: str, add: list = None, remove: list = None):
94
- """手动添加/移除收藏项。"""
95
- date_dir = Path(SOURCE_ROOT) / date_str
96
- fav_state_file = date_dir / '.fav_state.json'
97
-
98
- fav_list = []
99
- if fav_state_file.exists():
100
- try:
101
- with open(fav_state_file, 'r', encoding='utf-8') as f:
102
- fav_list = json.load(f)
103
- except Exception:
104
- pass
105
-
106
- changed = False
107
- if add:
108
- for item in add:
109
- if item not in fav_list:
110
- fav_list.append(item)
111
- print(f' 收藏: {item}')
112
- changed = True
113
-
114
- if remove:
115
- for item in remove:
116
- if item in fav_list:
117
- fav_list.remove(item)
118
- print(f' 取消收藏: {item}')
119
- changed = True
120
-
121
- if changed:
122
- with open(fav_state_file, 'w', encoding='utf-8') as f:
123
- json.dump(fav_list, f, ensure_ascii=False, indent=2)
124
- print(f'已更新 .fav_state.json ({len(fav_list)} 篇收藏)')
125
-
126
- sync_favorites(date_str)
127
-
128
-
129
- def main():
130
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
131
- import argparse
132
- parser = argparse.ArgumentParser(description='同步收藏夹到 收藏/ 文件夹')
133
- parser.add_argument('--date', required=True, help='日期 YYYY-MM-DD')
134
- parser.add_argument('--add', nargs='*', help='添加收藏 (相对路径)')
135
- parser.add_argument('--remove', nargs='*', help='取消收藏 (相对路径)')
136
- args = parser.parse_args()
137
-
138
- if args.add or args.remove:
139
- manage_fav(args.date, args.add, args.remove)
140
- else:
141
- sync_favorites(args.date)
142
- print(f'✓ 收藏同步完成')
143
-
144
-
145
- if __name__ == '__main__':
146
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ 同步收藏夹 — 根据 .fav_state.json 将收藏文章 symlink 到 收藏/ 文件夹。
4
+
5
+ 用法:
6
+ python scripts/sync_fav.py --date 2026-05-19
7
+ python scripts/sync_fav.py --date 2026-05-19 --add "AI/某文章.md" --remove "学术/某文章.md"
8
+
9
+ .fav_state.json 格式: ["AI/文章1.md", "学术/文章2.md", ...]
10
+ 与 HTML localStorage 的 weflow_fav_{date} 格式一致。
11
+ """
12
+
13
+ import sys, os, json, re
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+
17
+ SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
18
+ SOURCE_ROOT = os.path.join(os.path.dirname(SCRIPTS_DIR), 'output', 'biz-daily')
19
+
20
+
21
+ def resolve_date_dir(date_str: str, source_root: str) -> Path:
22
+ if not re.fullmatch(r'\d{4}-\d{2}-\d{2}', date_str):
23
+ raise ValueError('日期必须是 YYYY-MM-DD')
24
+ root = Path(source_root).resolve()
25
+ date_dir = (root / date_str).resolve()
26
+ if date_dir.parent != root:
27
+ raise ValueError('日期目录超出日报根目录')
28
+ return date_dir
29
+
30
+
31
+ def resolve_favorite_source(date_dir: Path, relative_path: object) -> tuple[str, Path] | None:
32
+ if not isinstance(relative_path, str) or not relative_path.strip():
33
+ return None
34
+ candidate = Path(relative_path)
35
+ if candidate.is_absolute() or candidate.suffix.lower() != '.md':
36
+ return None
37
+ source = (date_dir / candidate).resolve()
38
+ try:
39
+ normalized = source.relative_to(date_dir)
40
+ except ValueError:
41
+ return None
42
+ if not normalized.parts or normalized.parts[0] == '收藏' or not source.is_file():
43
+ return None
44
+ return normalized.as_posix(), source
45
+
46
+
47
+ def sync_favorites(date_str: str, source_root: str = SOURCE_ROOT) -> bool:
48
+ """读取 .fav_state.json 并同步 收藏/ 文件夹中的 symlink。"""
49
+ try:
50
+ date_dir = resolve_date_dir(date_str, source_root)
51
+ except ValueError as error:
52
+ print(f'[ERROR] {error}')
53
+ return False
54
+ if not date_dir.is_dir():
55
+ print(f'[ERROR] 目录不存在: {date_dir}')
56
+ return False
57
+
58
+ fav_dir = date_dir / '收藏'
59
+ fav_state_file = date_dir / '.fav_state.json'
60
+
61
+ # 读取收藏列表
62
+ if fav_state_file.exists():
63
+ try:
64
+ with open(fav_state_file, 'r', encoding='utf-8') as f:
65
+ fav_list = json.load(f)
66
+ except Exception:
67
+ print(f'[ERROR] 无法解析 .fav_state.json')
68
+ return False
69
+ else:
70
+ fav_list = []
71
+
72
+ if not fav_list:
73
+ print('收藏列表为空,清理 收藏/ 文件夹...')
74
+ if fav_dir.is_dir():
75
+ for link in fav_dir.iterdir():
76
+ if link.is_symlink() or link.is_file():
77
+ link.unlink()
78
+ print(f' 移除: {link.name}')
79
+ return True
80
+
81
+ # 创建收藏文件夹
82
+ fav_dir.mkdir(parents=True, exist_ok=True)
83
+
84
+ # 构建期望的链接集合
85
+ desired: dict[str, Path] = {}
86
+ for rel_path in fav_list:
87
+ resolved = resolve_favorite_source(date_dir, rel_path)
88
+ if resolved:
89
+ normalized, source = resolved
90
+ desired[normalized] = source
91
+ else:
92
+ print('[WARN] 忽略无效或越界的收藏项')
93
+
94
+ # 清理不在列表中的旧链接
95
+ existing = set()
96
+ for item in fav_dir.iterdir():
97
+ if item.is_symlink():
98
+ existing.add(item.name)
99
+ if item.name not in {source.name for source in desired.values()}:
100
+ item.unlink()
101
+ print(f' 移除: {item.name}')
102
+ elif item.is_file():
103
+ # 非 symlink 的文件也清理
104
+ existing.add(item.name)
105
+ if item.name not in {source.name for source in desired.values()}:
106
+ item.unlink()
107
+ print(f' 移除: {item.name}')
108
+
109
+ # 创建缺失的 symlink
110
+ for src in desired.values():
111
+ link = fav_dir / src.name
112
+ if not link.exists():
113
+ try:
114
+ link.symlink_to(os.path.relpath(src, fav_dir))
115
+ print(f' 添加: {src.name}')
116
+ except OSError:
117
+ # Windows 可能不支持 symlink,尝试复制
118
+ import shutil
119
+ shutil.copy2(src, link)
120
+ print(f' 复制: {src.name}')
121
+ return True
122
+
123
+
124
+ def manage_fav(date_str: str, add: list = None, remove: list = None, source_root: str = SOURCE_ROOT) -> bool:
125
+ """手动添加/移除收藏项。"""
126
+ try:
127
+ date_dir = resolve_date_dir(date_str, source_root)
128
+ except ValueError as error:
129
+ print(f'[ERROR] {error}')
130
+ return False
131
+ if not date_dir.is_dir():
132
+ print(f'[ERROR] 日报目录不存在')
133
+ return False
134
+ fav_state_file = date_dir / '.fav_state.json'
135
+
136
+ fav_list = []
137
+ if fav_state_file.exists():
138
+ try:
139
+ with open(fav_state_file, 'r', encoding='utf-8') as f:
140
+ fav_list = json.load(f)
141
+ except Exception:
142
+ pass
143
+
144
+ changed = False
145
+ invalid_input = False
146
+ if add:
147
+ for item in add:
148
+ resolved = resolve_favorite_source(date_dir, item)
149
+ if not resolved:
150
+ print('[WARN] 忽略无效或越界的收藏项')
151
+ invalid_input = True
152
+ continue
153
+ normalized, _ = resolved
154
+ if normalized not in fav_list:
155
+ fav_list.append(normalized)
156
+ print(f' 收藏: {normalized}')
157
+ changed = True
158
+
159
+ if remove:
160
+ for item in remove:
161
+ candidate = Path(item)
162
+ if candidate.is_absolute() or candidate.suffix.lower() != '.md' or '..' in candidate.parts:
163
+ print('[WARN] 忽略无效或越界的收藏项')
164
+ invalid_input = True
165
+ continue
166
+ normalized = candidate.as_posix()
167
+ if normalized in fav_list:
168
+ fav_list.remove(normalized)
169
+ print(f' 取消收藏: {normalized}')
170
+ changed = True
171
+
172
+ if changed:
173
+ with open(fav_state_file, 'w', encoding='utf-8') as f:
174
+ json.dump(fav_list, f, ensure_ascii=False, indent=2)
175
+ print(f'已更新 .fav_state.json ({len(fav_list)} 篇收藏)')
176
+
177
+ synced = sync_favorites(date_str, source_root)
178
+ return not invalid_input and synced
179
+
180
+
181
+ def main():
182
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
183
+ import argparse
184
+ parser = argparse.ArgumentParser(description='同步收藏夹到 收藏/ 文件夹')
185
+ parser.add_argument('--date', required=True, help='日期 YYYY-MM-DD')
186
+ parser.add_argument('--root', default=SOURCE_ROOT, help='日报输出根目录')
187
+ parser.add_argument('--add', nargs='*', help='添加收藏 (相对路径)')
188
+ parser.add_argument('--remove', nargs='*', help='取消收藏 (相对路径)')
189
+ args = parser.parse_args()
190
+
191
+ if args.add or args.remove:
192
+ if not manage_fav(args.date, args.add, args.remove, args.root):
193
+ sys.exit(1)
194
+ else:
195
+ if not sync_favorites(args.date, args.root):
196
+ sys.exit(1)
197
+ print(f'✓ 收藏同步完成')
198
+
199
+
200
+ if __name__ == '__main__':
201
+ main()