@jackwener/opencli 1.5.5 → 1.5.7

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 (540) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +31 -4
  3. package/README.zh-CN.md +40 -5
  4. package/SKILL.md +1 -1
  5. package/dist/browser/cdp.d.ts +1 -0
  6. package/dist/browser/cdp.js +30 -27
  7. package/dist/browser/daemon-client.d.ts +11 -1
  8. package/dist/browser/daemon-client.js +3 -0
  9. package/dist/browser/dom-helpers.js +1 -0
  10. package/dist/browser/dom-helpers.test.js +14 -1
  11. package/dist/browser/mcp.js +18 -13
  12. package/dist/browser/page.d.ts +6 -0
  13. package/dist/browser/page.js +37 -2
  14. package/dist/browser/page.test.d.ts +1 -0
  15. package/dist/browser/page.test.js +44 -0
  16. package/dist/browser/stealth.js +198 -0
  17. package/dist/browser/stealth.test.d.ts +1 -0
  18. package/dist/browser/stealth.test.js +134 -0
  19. package/dist/browser.test.js +1 -1
  20. package/dist/build-manifest.d.ts +1 -0
  21. package/dist/build-manifest.js +5 -1
  22. package/dist/build-manifest.test.js +2 -0
  23. package/dist/cli-manifest.json +1821 -252
  24. package/dist/cli.js +20 -3
  25. package/dist/clis/antigravity/serve.d.ts +1 -1
  26. package/dist/clis/antigravity/serve.js +5 -8
  27. package/dist/clis/band/bands.d.ts +1 -0
  28. package/dist/clis/band/bands.js +72 -0
  29. package/dist/clis/band/mentions.d.ts +1 -0
  30. package/dist/clis/band/mentions.js +127 -0
  31. package/dist/clis/band/post.d.ts +1 -0
  32. package/dist/clis/band/post.js +175 -0
  33. package/dist/clis/band/posts.d.ts +1 -0
  34. package/dist/clis/band/posts.js +94 -0
  35. package/dist/clis/bilibili/subtitle.js +4 -0
  36. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  37. package/dist/clis/bilibili/subtitle.test.js +48 -0
  38. package/dist/clis/chatwise/ask.js +0 -2
  39. package/dist/clis/chatwise/export.js +0 -2
  40. package/dist/clis/chatwise/history.js +0 -2
  41. package/dist/clis/chatwise/model.js +0 -2
  42. package/dist/clis/chatwise/new.js +1 -2
  43. package/dist/clis/chatwise/read.js +0 -2
  44. package/dist/clis/chatwise/screenshot.js +1 -2
  45. package/dist/clis/chatwise/send.js +0 -2
  46. package/dist/clis/chatwise/status.js +1 -2
  47. package/dist/clis/ctrip/search.d.ts +13 -0
  48. package/dist/clis/ctrip/search.js +73 -48
  49. package/dist/clis/ctrip/search.test.d.ts +1 -0
  50. package/dist/clis/ctrip/search.test.js +64 -0
  51. package/dist/clis/doubao/detail.d.ts +1 -0
  52. package/dist/clis/doubao/detail.js +33 -0
  53. package/dist/clis/doubao/detail.test.d.ts +1 -0
  54. package/dist/clis/doubao/detail.test.js +42 -0
  55. package/dist/clis/doubao/history.d.ts +1 -0
  56. package/dist/clis/doubao/history.js +28 -0
  57. package/dist/clis/doubao/history.test.d.ts +1 -0
  58. package/dist/clis/doubao/history.test.js +37 -0
  59. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  60. package/dist/clis/doubao/meeting-summary.js +39 -0
  61. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  62. package/dist/clis/doubao/meeting-transcript.js +36 -0
  63. package/dist/clis/doubao/utils.d.ts +27 -0
  64. package/dist/clis/doubao/utils.js +317 -0
  65. package/dist/clis/doubao/utils.test.d.ts +1 -0
  66. package/dist/clis/doubao/utils.test.js +24 -0
  67. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  68. package/dist/clis/douyin/_shared/public-api.js +29 -0
  69. package/dist/clis/douyin/_shared/sts2.js +8 -2
  70. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  71. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  72. package/dist/clis/douyin/activities.js +4 -2
  73. package/dist/clis/douyin/activities.test.js +34 -1
  74. package/dist/clis/douyin/collections.js +1 -1
  75. package/dist/clis/douyin/collections.test.js +24 -2
  76. package/dist/clis/douyin/draft.d.ts +8 -11
  77. package/dist/clis/douyin/draft.js +302 -185
  78. package/dist/clis/douyin/draft.test.d.ts +1 -1
  79. package/dist/clis/douyin/draft.test.js +357 -2
  80. package/dist/clis/douyin/hashtag.js +9 -2
  81. package/dist/clis/douyin/hashtag.test.js +35 -2
  82. package/dist/clis/douyin/profile.js +1 -1
  83. package/dist/clis/douyin/profile.test.js +36 -1
  84. package/dist/clis/douyin/user-videos.d.ts +5 -0
  85. package/dist/clis/douyin/user-videos.js +74 -0
  86. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  87. package/dist/clis/douyin/user-videos.test.js +108 -0
  88. package/dist/clis/douyin/videos.js +22 -5
  89. package/dist/clis/douyin/videos.test.js +45 -2
  90. package/dist/clis/facebook/search.test.d.ts +5 -0
  91. package/dist/clis/facebook/search.test.js +60 -0
  92. package/dist/clis/facebook/search.yaml +4 -3
  93. package/dist/clis/instagram/download.d.ts +16 -0
  94. package/dist/clis/instagram/download.js +225 -0
  95. package/dist/clis/instagram/download.test.d.ts +1 -0
  96. package/dist/clis/instagram/download.test.js +118 -0
  97. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  98. package/dist/clis/notebooklm/bind-current.js +29 -0
  99. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  100. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  101. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  102. package/dist/clis/notebooklm/binding.test.js +44 -0
  103. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  104. package/dist/clis/notebooklm/compat.test.js +16 -0
  105. package/dist/clis/notebooklm/current.d.ts +1 -0
  106. package/dist/clis/notebooklm/current.js +28 -0
  107. package/dist/clis/notebooklm/get.d.ts +1 -0
  108. package/dist/clis/notebooklm/get.js +37 -0
  109. package/dist/clis/notebooklm/history.d.ts +1 -0
  110. package/dist/clis/notebooklm/history.js +25 -0
  111. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  112. package/dist/clis/notebooklm/history.test.js +58 -0
  113. package/dist/clis/notebooklm/list.d.ts +1 -0
  114. package/dist/clis/notebooklm/list.js +35 -0
  115. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  116. package/dist/clis/notebooklm/note-list.js +28 -0
  117. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  118. package/dist/clis/notebooklm/note-list.test.js +56 -0
  119. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  120. package/dist/clis/notebooklm/notes-get.js +47 -0
  121. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  122. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  123. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  124. package/dist/clis/notebooklm/rpc.js +189 -0
  125. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  126. package/dist/clis/notebooklm/rpc.test.js +105 -0
  127. package/dist/clis/notebooklm/shared.d.ts +87 -0
  128. package/dist/clis/notebooklm/shared.js +3 -0
  129. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  130. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  131. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  132. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  133. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  134. package/dist/clis/notebooklm/source-get.js +40 -0
  135. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  136. package/dist/clis/notebooklm/source-get.test.js +84 -0
  137. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  138. package/dist/clis/notebooklm/source-guide.js +44 -0
  139. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  140. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  141. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  142. package/dist/clis/notebooklm/source-list.js +30 -0
  143. package/dist/clis/notebooklm/status.d.ts +1 -0
  144. package/dist/clis/notebooklm/status.js +31 -0
  145. package/dist/clis/notebooklm/summary.d.ts +1 -0
  146. package/dist/clis/notebooklm/summary.js +30 -0
  147. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  148. package/dist/clis/notebooklm/summary.test.js +78 -0
  149. package/dist/clis/notebooklm/utils.d.ts +37 -0
  150. package/dist/clis/notebooklm/utils.js +739 -0
  151. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  152. package/dist/clis/notebooklm/utils.test.js +390 -0
  153. package/dist/clis/ones/common.d.ts +32 -0
  154. package/dist/clis/ones/common.js +144 -0
  155. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  156. package/dist/clis/ones/enrich-tasks.js +37 -0
  157. package/dist/clis/ones/login.d.ts +1 -0
  158. package/dist/clis/ones/login.js +80 -0
  159. package/dist/clis/ones/logout.d.ts +1 -0
  160. package/dist/clis/ones/logout.js +17 -0
  161. package/dist/clis/ones/me.d.ts +1 -0
  162. package/dist/clis/ones/me.js +30 -0
  163. package/dist/clis/ones/my-tasks.d.ts +1 -0
  164. package/dist/clis/ones/my-tasks.js +120 -0
  165. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  166. package/dist/clis/ones/resolve-labels.js +64 -0
  167. package/dist/clis/ones/task-helpers.d.ts +29 -0
  168. package/dist/clis/ones/task-helpers.js +212 -0
  169. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  170. package/dist/clis/ones/task-helpers.test.js +12 -0
  171. package/dist/clis/ones/task.d.ts +1 -0
  172. package/dist/clis/ones/task.js +66 -0
  173. package/dist/clis/ones/tasks.d.ts +1 -0
  174. package/dist/clis/ones/tasks.js +79 -0
  175. package/dist/clis/ones/token-info.d.ts +1 -0
  176. package/dist/clis/ones/token-info.js +42 -0
  177. package/dist/clis/ones/worklog.d.ts +11 -0
  178. package/dist/clis/ones/worklog.js +267 -0
  179. package/dist/clis/ones/worklog.test.d.ts +1 -0
  180. package/dist/clis/ones/worklog.test.js +20 -0
  181. package/dist/clis/spotify/spotify.d.ts +1 -0
  182. package/dist/clis/spotify/spotify.js +316 -0
  183. package/dist/clis/spotify/utils.d.ts +21 -0
  184. package/dist/clis/spotify/utils.js +66 -0
  185. package/dist/clis/spotify/utils.test.d.ts +1 -0
  186. package/dist/clis/spotify/utils.test.js +67 -0
  187. package/dist/clis/substack/utils.d.ts +4 -0
  188. package/dist/clis/substack/utils.js +8 -2
  189. package/dist/clis/substack/utils.test.d.ts +1 -0
  190. package/dist/clis/substack/utils.test.js +46 -0
  191. package/dist/clis/tieba/commands.test.d.ts +4 -0
  192. package/dist/clis/tieba/commands.test.js +79 -0
  193. package/dist/clis/tieba/hot.d.ts +1 -0
  194. package/dist/clis/tieba/hot.js +48 -0
  195. package/dist/clis/tieba/posts.d.ts +1 -0
  196. package/dist/clis/tieba/posts.js +85 -0
  197. package/dist/clis/tieba/read.d.ts +1 -0
  198. package/dist/clis/tieba/read.js +140 -0
  199. package/dist/clis/tieba/search.d.ts +1 -0
  200. package/dist/clis/tieba/search.js +108 -0
  201. package/dist/clis/tieba/utils.d.ts +101 -0
  202. package/dist/clis/tieba/utils.js +240 -0
  203. package/dist/clis/tieba/utils.test.d.ts +1 -0
  204. package/dist/clis/tieba/utils.test.js +290 -0
  205. package/dist/clis/v2ex/hot.yaml +4 -1
  206. package/dist/clis/v2ex/latest.yaml +4 -1
  207. package/dist/clis/v2ex/topic.yaml +6 -1
  208. package/dist/clis/weixin/download.d.ts +9 -0
  209. package/dist/clis/weixin/download.js +76 -6
  210. package/dist/clis/weread/book.js +206 -13
  211. package/dist/clis/weread/commands.test.js +331 -0
  212. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  213. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  214. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  215. package/dist/clis/weread/search-regression.test.js +407 -0
  216. package/dist/clis/weread/search.js +143 -7
  217. package/dist/clis/weread/shelf.js +13 -95
  218. package/dist/clis/weread/utils.d.ts +56 -0
  219. package/dist/clis/weread/utils.js +234 -7
  220. package/dist/clis/weread/utils.test.js +71 -1
  221. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  222. package/dist/clis/xiaohongshu/comments.js +76 -17
  223. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  224. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  225. package/dist/clis/xiaohongshu/download.js +83 -22
  226. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  227. package/dist/clis/xiaohongshu/download.test.js +75 -0
  228. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  229. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  230. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  231. package/dist/clis/xiaohongshu/note.js +76 -0
  232. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  233. package/dist/clis/xiaohongshu/note.test.js +136 -0
  234. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  235. package/dist/clis/xiaohongshu/publish.js +78 -31
  236. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  237. package/dist/clis/xiaohongshu/search.js +9 -0
  238. package/dist/clis/xiaohongshu/search.test.js +10 -4
  239. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  240. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  241. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  242. package/dist/clis/xueqiu/comments.d.ts +118 -0
  243. package/dist/clis/xueqiu/comments.js +354 -0
  244. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  245. package/dist/clis/xueqiu/comments.test.js +696 -0
  246. package/dist/clis/youtube/search.js +57 -17
  247. package/dist/clis/youtube/transcript.js +2 -4
  248. package/dist/clis/youtube/utils.d.ts +9 -0
  249. package/dist/clis/youtube/utils.js +67 -3
  250. package/dist/clis/youtube/utils.test.d.ts +1 -0
  251. package/dist/clis/youtube/utils.test.js +37 -0
  252. package/dist/clis/youtube/video.js +16 -15
  253. package/dist/clis/zhihu/question.js +19 -17
  254. package/dist/clis/zhihu/question.test.d.ts +1 -0
  255. package/dist/clis/zhihu/question.test.js +54 -0
  256. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  257. package/dist/clis/zsxq/dynamics.js +47 -0
  258. package/dist/clis/zsxq/groups.d.ts +1 -0
  259. package/dist/clis/zsxq/groups.js +32 -0
  260. package/dist/clis/zsxq/search.d.ts +1 -0
  261. package/dist/clis/zsxq/search.js +43 -0
  262. package/dist/clis/zsxq/search.test.d.ts +1 -0
  263. package/dist/clis/zsxq/search.test.js +24 -0
  264. package/dist/clis/zsxq/topic.d.ts +1 -0
  265. package/dist/clis/zsxq/topic.js +47 -0
  266. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  267. package/dist/clis/zsxq/topic.test.js +29 -0
  268. package/dist/clis/zsxq/topics.d.ts +1 -0
  269. package/dist/clis/zsxq/topics.js +25 -0
  270. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  271. package/dist/clis/zsxq/topics.test.js +24 -0
  272. package/dist/clis/zsxq/utils.d.ts +97 -0
  273. package/dist/clis/zsxq/utils.js +230 -0
  274. package/dist/commanderAdapter.js +10 -1
  275. package/dist/commanderAdapter.test.js +64 -0
  276. package/dist/commands/daemon.d.ts +9 -0
  277. package/dist/commands/daemon.js +124 -0
  278. package/dist/commands/daemon.test.d.ts +1 -0
  279. package/dist/commands/daemon.test.js +185 -0
  280. package/dist/completion.js +3 -1
  281. package/dist/constants.d.ts +2 -0
  282. package/dist/constants.js +2 -0
  283. package/dist/daemon.d.ts +1 -1
  284. package/dist/daemon.js +25 -14
  285. package/dist/daemon.test.d.ts +1 -0
  286. package/dist/daemon.test.js +65 -0
  287. package/dist/discovery.d.ts +9 -0
  288. package/dist/discovery.js +47 -2
  289. package/dist/electron-apps.d.ts +29 -0
  290. package/dist/electron-apps.js +65 -0
  291. package/dist/electron-apps.test.d.ts +1 -0
  292. package/dist/electron-apps.test.js +43 -0
  293. package/dist/engine.test.js +41 -9
  294. package/dist/execution.js +20 -16
  295. package/dist/external-clis.yaml +17 -0
  296. package/dist/idle-manager.d.ts +19 -0
  297. package/dist/idle-manager.js +54 -0
  298. package/dist/launcher.d.ts +36 -0
  299. package/dist/launcher.js +152 -0
  300. package/dist/launcher.test.d.ts +1 -0
  301. package/dist/launcher.test.js +57 -0
  302. package/dist/main.js +3 -3
  303. package/dist/registry.d.ts +1 -0
  304. package/dist/registry.js +31 -3
  305. package/dist/registry.test.js +13 -0
  306. package/dist/runtime.d.ts +5 -3
  307. package/dist/runtime.js +12 -5
  308. package/dist/serialization.d.ts +1 -0
  309. package/dist/serialization.js +3 -0
  310. package/dist/serialization.test.js +17 -1
  311. package/dist/tui.d.ts +7 -0
  312. package/dist/tui.js +52 -0
  313. package/dist/tui.test.d.ts +1 -0
  314. package/dist/tui.test.js +19 -0
  315. package/dist/types.d.ts +5 -0
  316. package/dist/weixin-download.test.js +14 -0
  317. package/docs/.vitepress/config.mts +4 -0
  318. package/docs/adapters/browser/band.md +63 -0
  319. package/docs/adapters/browser/notebooklm.md +69 -0
  320. package/docs/adapters/browser/ones.md +59 -0
  321. package/docs/adapters/browser/spotify.md +62 -0
  322. package/docs/adapters/browser/tieba.md +45 -0
  323. package/docs/adapters/browser/xiaohongshu.md +19 -10
  324. package/docs/adapters/browser/xueqiu.md +5 -0
  325. package/docs/adapters/browser/zsxq.md +49 -0
  326. package/docs/adapters/index.md +67 -63
  327. package/docs/adapters-doc/ones.md +32 -0
  328. package/docs/guide/browser-bridge.md +12 -0
  329. package/docs/guide/troubleshooting.md +9 -4
  330. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  331. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  332. package/docs/zh/guide/browser-bridge.md +12 -0
  333. package/extension/dist/background.js +794 -513
  334. package/extension/src/background.test.ts +202 -2
  335. package/extension/src/background.ts +189 -10
  336. package/extension/src/cdp.ts +54 -0
  337. package/extension/src/protocol.ts +11 -5
  338. package/package.json +1 -1
  339. package/scripts/postinstall.js +16 -0
  340. package/src/browser/cdp.ts +24 -17
  341. package/src/browser/daemon-client.ts +11 -1
  342. package/src/browser/dom-helpers.test.ts +15 -1
  343. package/src/browser/dom-helpers.ts +1 -0
  344. package/src/browser/mcp.ts +18 -13
  345. package/src/browser/page.test.ts +58 -0
  346. package/src/browser/page.ts +34 -2
  347. package/src/browser/stealth.test.ts +153 -0
  348. package/src/browser/stealth.ts +198 -0
  349. package/src/browser.test.ts +1 -1
  350. package/src/build-manifest.test.ts +2 -0
  351. package/src/build-manifest.ts +6 -1
  352. package/src/cli.ts +21 -3
  353. package/src/clis/antigravity/SKILL.md +3 -12
  354. package/src/clis/antigravity/serve.ts +5 -10
  355. package/src/clis/band/bands.ts +76 -0
  356. package/src/clis/band/mentions.ts +134 -0
  357. package/src/clis/band/post.ts +187 -0
  358. package/src/clis/band/posts.ts +106 -0
  359. package/src/clis/bilibili/subtitle.test.ts +60 -0
  360. package/src/clis/bilibili/subtitle.ts +4 -0
  361. package/src/clis/chatwise/ask.ts +0 -2
  362. package/src/clis/chatwise/export.ts +0 -2
  363. package/src/clis/chatwise/history.ts +0 -2
  364. package/src/clis/chatwise/model.ts +0 -2
  365. package/src/clis/chatwise/new.ts +1 -2
  366. package/src/clis/chatwise/read.ts +0 -2
  367. package/src/clis/chatwise/screenshot.ts +1 -2
  368. package/src/clis/chatwise/send.ts +0 -2
  369. package/src/clis/chatwise/status.ts +1 -2
  370. package/src/clis/ctrip/search.test.ts +73 -0
  371. package/src/clis/ctrip/search.ts +97 -47
  372. package/src/clis/doubao/detail.test.ts +53 -0
  373. package/src/clis/doubao/detail.ts +41 -0
  374. package/src/clis/doubao/history.test.ts +45 -0
  375. package/src/clis/doubao/history.ts +32 -0
  376. package/src/clis/doubao/meeting-summary.ts +53 -0
  377. package/src/clis/doubao/meeting-transcript.ts +48 -0
  378. package/src/clis/doubao/utils.test.ts +45 -0
  379. package/src/clis/doubao/utils.ts +371 -0
  380. package/src/clis/douyin/_shared/public-api.ts +84 -0
  381. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  382. package/src/clis/douyin/_shared/sts2.ts +11 -3
  383. package/src/clis/douyin/activities.test.ts +41 -1
  384. package/src/clis/douyin/activities.ts +12 -3
  385. package/src/clis/douyin/collections.test.ts +35 -2
  386. package/src/clis/douyin/collections.ts +1 -1
  387. package/src/clis/douyin/draft.test.ts +444 -2
  388. package/src/clis/douyin/draft.ts +382 -218
  389. package/src/clis/douyin/hashtag.test.ts +42 -2
  390. package/src/clis/douyin/hashtag.ts +11 -3
  391. package/src/clis/douyin/profile.test.ts +43 -1
  392. package/src/clis/douyin/profile.ts +9 -2
  393. package/src/clis/douyin/user-videos.test.ts +122 -0
  394. package/src/clis/douyin/user-videos.ts +101 -0
  395. package/src/clis/douyin/videos.test.ts +52 -2
  396. package/src/clis/douyin/videos.ts +49 -15
  397. package/src/clis/facebook/search.test.ts +70 -0
  398. package/src/clis/facebook/search.yaml +4 -3
  399. package/src/clis/instagram/download.test.ts +159 -0
  400. package/src/clis/instagram/download.ts +286 -0
  401. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  402. package/src/clis/notebooklm/bind-current.ts +36 -0
  403. package/src/clis/notebooklm/binding.test.ts +53 -0
  404. package/src/clis/notebooklm/compat.test.ts +19 -0
  405. package/src/clis/notebooklm/current.ts +38 -0
  406. package/src/clis/notebooklm/get.ts +53 -0
  407. package/src/clis/notebooklm/history.test.ts +70 -0
  408. package/src/clis/notebooklm/history.ts +36 -0
  409. package/src/clis/notebooklm/list.ts +40 -0
  410. package/src/clis/notebooklm/note-list.test.ts +64 -0
  411. package/src/clis/notebooklm/note-list.ts +42 -0
  412. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  413. package/src/clis/notebooklm/notes-get.ts +67 -0
  414. package/src/clis/notebooklm/rpc.test.ts +126 -0
  415. package/src/clis/notebooklm/rpc.ts +286 -0
  416. package/src/clis/notebooklm/shared.ts +98 -0
  417. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  418. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  419. package/src/clis/notebooklm/source-get.test.ts +100 -0
  420. package/src/clis/notebooklm/source-get.ts +60 -0
  421. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  422. package/src/clis/notebooklm/source-guide.ts +69 -0
  423. package/src/clis/notebooklm/source-list.ts +45 -0
  424. package/src/clis/notebooklm/status.ts +34 -0
  425. package/src/clis/notebooklm/summary.test.ts +94 -0
  426. package/src/clis/notebooklm/summary.ts +45 -0
  427. package/src/clis/notebooklm/utils.test.ts +446 -0
  428. package/src/clis/notebooklm/utils.ts +893 -0
  429. package/src/clis/ones/common.ts +187 -0
  430. package/src/clis/ones/enrich-tasks.ts +47 -0
  431. package/src/clis/ones/login.ts +103 -0
  432. package/src/clis/ones/logout.ts +19 -0
  433. package/src/clis/ones/me.ts +34 -0
  434. package/src/clis/ones/my-tasks.ts +148 -0
  435. package/src/clis/ones/resolve-labels.ts +80 -0
  436. package/src/clis/ones/task-helpers.test.ts +14 -0
  437. package/src/clis/ones/task-helpers.ts +214 -0
  438. package/src/clis/ones/task.ts +79 -0
  439. package/src/clis/ones/tasks.ts +92 -0
  440. package/src/clis/ones/token-info.ts +46 -0
  441. package/src/clis/ones/worklog.test.ts +24 -0
  442. package/src/clis/ones/worklog.ts +306 -0
  443. package/src/clis/spotify/spotify.ts +328 -0
  444. package/src/clis/spotify/utils.test.ts +87 -0
  445. package/src/clis/spotify/utils.ts +92 -0
  446. package/src/clis/substack/utils.test.ts +54 -0
  447. package/src/clis/substack/utils.ts +10 -2
  448. package/src/clis/tieba/commands.test.ts +86 -0
  449. package/src/clis/tieba/hot.ts +52 -0
  450. package/src/clis/tieba/posts.ts +108 -0
  451. package/src/clis/tieba/read.ts +158 -0
  452. package/src/clis/tieba/search.ts +119 -0
  453. package/src/clis/tieba/utils.test.ts +322 -0
  454. package/src/clis/tieba/utils.ts +348 -0
  455. package/src/clis/v2ex/hot.yaml +4 -1
  456. package/src/clis/v2ex/latest.yaml +4 -1
  457. package/src/clis/v2ex/topic.yaml +6 -1
  458. package/src/clis/weixin/download.ts +95 -6
  459. package/src/clis/weread/book.ts +256 -13
  460. package/src/clis/weread/commands.test.ts +409 -0
  461. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  462. package/src/clis/weread/search-regression.test.ts +440 -0
  463. package/src/clis/weread/search.ts +189 -9
  464. package/src/clis/weread/shelf.ts +20 -122
  465. package/src/clis/weread/utils.test.ts +81 -1
  466. package/src/clis/weread/utils.ts +293 -7
  467. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  468. package/src/clis/xiaohongshu/comments.ts +76 -17
  469. package/src/clis/xiaohongshu/download.test.ts +96 -0
  470. package/src/clis/xiaohongshu/download.ts +83 -22
  471. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  472. package/src/clis/xiaohongshu/note.test.ts +164 -0
  473. package/src/clis/xiaohongshu/note.ts +86 -0
  474. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  475. package/src/clis/xiaohongshu/publish.ts +84 -30
  476. package/src/clis/xiaohongshu/search.test.ts +11 -4
  477. package/src/clis/xiaohongshu/search.ts +13 -0
  478. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  479. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  480. package/src/clis/xueqiu/comments.test.ts +823 -0
  481. package/src/clis/xueqiu/comments.ts +461 -0
  482. package/src/clis/youtube/search.ts +57 -17
  483. package/src/clis/youtube/transcript.ts +2 -4
  484. package/src/clis/youtube/utils.test.ts +43 -0
  485. package/src/clis/youtube/utils.ts +69 -0
  486. package/src/clis/youtube/video.ts +16 -15
  487. package/src/clis/zhihu/question.test.ts +71 -0
  488. package/src/clis/zhihu/question.ts +27 -15
  489. package/src/clis/zsxq/dynamics.ts +60 -0
  490. package/src/clis/zsxq/groups.ts +41 -0
  491. package/src/clis/zsxq/search.test.ts +29 -0
  492. package/src/clis/zsxq/search.ts +54 -0
  493. package/src/clis/zsxq/topic.test.ts +34 -0
  494. package/src/clis/zsxq/topic.ts +68 -0
  495. package/src/clis/zsxq/topics.test.ts +29 -0
  496. package/src/clis/zsxq/topics.ts +36 -0
  497. package/src/clis/zsxq/utils.ts +351 -0
  498. package/src/commanderAdapter.test.ts +77 -0
  499. package/src/commanderAdapter.ts +8 -1
  500. package/src/commands/daemon.test.ts +238 -0
  501. package/src/commands/daemon.ts +135 -0
  502. package/src/completion.ts +2 -1
  503. package/src/constants.ts +3 -0
  504. package/src/daemon.test.ts +88 -0
  505. package/src/daemon.ts +26 -14
  506. package/src/discovery.ts +52 -2
  507. package/src/electron-apps.test.ts +50 -0
  508. package/src/electron-apps.ts +89 -0
  509. package/src/engine.test.ts +45 -9
  510. package/src/execution.ts +24 -19
  511. package/src/external-clis.yaml +17 -0
  512. package/src/idle-manager.ts +60 -0
  513. package/src/launcher.test.ts +67 -0
  514. package/src/launcher.ts +185 -0
  515. package/src/main.ts +3 -2
  516. package/src/registry.test.ts +15 -0
  517. package/src/registry.ts +32 -3
  518. package/src/runtime.ts +13 -7
  519. package/src/serialization.test.ts +19 -1
  520. package/src/serialization.ts +2 -0
  521. package/src/tui.test.ts +23 -0
  522. package/src/tui.ts +65 -0
  523. package/src/types.ts +5 -0
  524. package/src/weixin-download.test.ts +27 -0
  525. package/tests/e2e/band-auth.test.ts +20 -0
  526. package/tests/e2e/browser-auth-helpers.ts +18 -0
  527. package/tests/e2e/browser-auth.test.ts +35 -47
  528. package/tests/e2e/browser-public-extended.test.ts +6 -2
  529. package/tests/e2e/browser-public.test.ts +288 -0
  530. package/tests/e2e/management.test.ts +1 -1
  531. package/tests/e2e/plugin-management.test.ts +1 -1
  532. package/vitest.config.ts +1 -0
  533. package/chatwise-opencli.ps1 +0 -82
  534. package/dist/clis/chatwise/shared.d.ts +0 -2
  535. package/dist/clis/chatwise/shared.js +0 -6
  536. package/dist/weread-private-api-regression.test.d.ts +0 -1
  537. package/dist/weread-search-regression.test.d.ts +0 -1
  538. package/dist/weread-search-regression.test.js +0 -39
  539. package/src/clis/chatwise/shared.ts +0 -8
  540. package/src/weread-search-regression.test.ts +0 -44
@@ -1,10 +1,13 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import { CliError } from '../../errors.js';
3
+ import { log } from '../../logger.js';
3
4
  import type { IPage } from '../../types.js';
4
- import { fetchPrivateApi } from './utils.js';
5
-
6
- const WEREAD_DOMAIN = 'weread.qq.com';
7
- const WEREAD_SHELF_URL = `https://${WEREAD_DOMAIN}/web/shelf`;
5
+ import {
6
+ buildWebShelfEntries,
7
+ fetchPrivateApi,
8
+ loadWebShelfSnapshot,
9
+ type WebShelfSnapshot,
10
+ } from './utils.js';
8
11
 
9
12
  interface ShelfRow {
10
13
  title: string;
@@ -13,24 +16,6 @@ interface ShelfRow {
13
16
  bookId: string;
14
17
  }
15
18
 
16
- interface WebShelfRawBook {
17
- bookId?: string;
18
- title?: string;
19
- author?: string;
20
- }
21
-
22
- interface WebShelfIndexEntry {
23
- bookId?: string;
24
- idx?: number;
25
- role?: string;
26
- }
27
-
28
- interface WebShelfSnapshot {
29
- cacheFound: boolean;
30
- rawBooks: WebShelfRawBook[];
31
- shelfIndexes: WebShelfIndexEntry[];
32
- }
33
-
34
19
  function normalizeShelfLimit(limit: number): number {
35
20
  if (!Number.isFinite(limit)) return 0;
36
21
  return Math.max(0, Math.trunc(limit));
@@ -50,110 +35,17 @@ function normalizePrivateApiRows(data: any, limit: number): ShelfRow[] {
50
35
  function normalizeWebShelfRows(snapshot: WebShelfSnapshot, limit: number): ShelfRow[] {
51
36
  if (limit <= 0) return [];
52
37
 
53
- const bookById = new Map<string, WebShelfRawBook>();
54
- for (const book of snapshot.rawBooks) {
55
- const bookId = String(book?.bookId || '').trim();
56
- if (!bookId) continue;
57
- bookById.set(bookId, book);
58
- }
59
-
60
- const orderedBookIds = snapshot.shelfIndexes
61
- .filter((entry) => String(entry?.role || 'book') === 'book')
62
- .sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
63
- .map((entry) => String(entry?.bookId || '').trim())
64
- .filter(Boolean);
65
-
66
- const fallbackOrder = snapshot.rawBooks
67
- .map((book) => String(book?.bookId || '').trim())
68
- .filter(Boolean);
69
-
70
- const orderedUniqueBookIds = Array.from(new Set([
71
- ...orderedBookIds,
72
- ...fallbackOrder,
73
- ]));
74
-
75
- return orderedUniqueBookIds
76
- .map((bookId) => {
77
- const book = bookById.get(bookId);
78
- if (!book) return null;
79
- return {
80
- title: String(book.title || '').trim(),
81
- author: String(book.author || '').trim(),
38
+ return buildWebShelfEntries(snapshot)
39
+ .map((entry) => ({
40
+ title: entry.title,
41
+ author: entry.author,
82
42
  progress: '-',
83
- bookId,
84
- } satisfies ShelfRow;
85
- })
86
- .filter((item): item is ShelfRow => Boolean(item && (item.title || item.bookId)))
43
+ bookId: entry.bookId,
44
+ } satisfies ShelfRow))
45
+ .filter((item): item is ShelfRow => Boolean(item.title || item.bookId))
87
46
  .slice(0, limit);
88
47
  }
89
48
 
90
- /**
91
- * Read the structured shelf cache from the web shelf page.
92
- * The page hydrates localStorage with raw book data plus shelf ordering.
93
- */
94
- async function loadWebShelfSnapshot(page: IPage): Promise<WebShelfSnapshot> {
95
- await page.goto(WEREAD_SHELF_URL);
96
-
97
- const cookies = await page.getCookies({ domain: WEREAD_DOMAIN });
98
- const currentVid = String(cookies.find((cookie) => cookie.name === 'wr_vid')?.value || '').trim();
99
-
100
- if (!currentVid) {
101
- return { cacheFound: false, rawBooks: [], shelfIndexes: [] };
102
- }
103
-
104
- const rawBooksKey = `shelf:rawBooks:${currentVid}`;
105
- const shelfIndexesKey = `shelf:shelfIndexes:${currentVid}`;
106
-
107
- const result = await page.evaluate(`
108
- (() => new Promise((resolve) => {
109
- const deadline = Date.now() + 5000;
110
- const rawBooksKey = ${JSON.stringify(rawBooksKey)};
111
- const shelfIndexesKey = ${JSON.stringify(shelfIndexesKey)};
112
-
113
- const readJson = (raw) => {
114
- if (typeof raw !== 'string') return null;
115
- try {
116
- return JSON.parse(raw);
117
- } catch {
118
- return null;
119
- }
120
- };
121
-
122
- const poll = () => {
123
- const rawBooksRaw = localStorage.getItem(rawBooksKey);
124
- const shelfIndexesRaw = localStorage.getItem(shelfIndexesKey);
125
- const rawBooks = readJson(rawBooksRaw);
126
- const shelfIndexes = readJson(shelfIndexesRaw);
127
- const cacheFound = Array.isArray(rawBooks);
128
-
129
- if (cacheFound || Date.now() >= deadline) {
130
- resolve({
131
- cacheFound,
132
- rawBooks: Array.isArray(rawBooks) ? rawBooks : [],
133
- shelfIndexes: Array.isArray(shelfIndexes) ? shelfIndexes : [],
134
- });
135
- return;
136
- }
137
-
138
- setTimeout(poll, 100);
139
- };
140
-
141
- poll();
142
- }))
143
- `);
144
-
145
- if (!result || typeof result !== 'object') {
146
- return { cacheFound: false, rawBooks: [], shelfIndexes: [] };
147
- }
148
-
149
- const snapshot = result as Partial<WebShelfSnapshot>;
150
- return {
151
- cacheFound: snapshot.cacheFound === true,
152
- rawBooks: Array.isArray(snapshot.rawBooks) ? snapshot.rawBooks : [],
153
- shelfIndexes: Array.isArray(snapshot.shelfIndexes) ? snapshot.shelfIndexes : [],
154
- };
155
- }
156
-
157
49
  cli({
158
50
  site: 'weread',
159
51
  name: 'shelf',
@@ -180,6 +72,12 @@ cli({
180
72
  if (!snapshot.cacheFound) {
181
73
  throw error;
182
74
  }
75
+
76
+ // Make the fallback explicit so users do not mistake cached shelf data
77
+ // for a valid private API session.
78
+ log.warn(
79
+ 'WeRead private API auth expired; showing cached shelf data from localStorage. Results may be stale, and detail commands may still require re-login.',
80
+ );
183
81
  return normalizeWebShelfRows(snapshot, limit);
184
82
  }
185
83
  },
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { formatDate, fetchWebApi } from './utils.js';
2
+ import { buildWebShelfEntries, formatDate, fetchWebApi } from './utils.js';
3
3
 
4
4
  describe('formatDate', () => {
5
5
  it('formats a typical Unix timestamp in UTC+8', () => {
@@ -71,3 +71,83 @@ describe('fetchWebApi', () => {
71
71
  await expect(fetchWebApi('/search/global')).rejects.toThrow('Invalid JSON');
72
72
  });
73
73
  });
74
+
75
+ describe('buildWebShelfEntries', () => {
76
+ it('keeps mixed shelf item reader urls aligned when shelf indexes include non-book roles', () => {
77
+ const result = buildWebShelfEntries(
78
+ {
79
+ cacheFound: true,
80
+ rawBooks: [
81
+ { bookId: 'MP_WXS_1', title: '公众号文章一', author: '作者甲' },
82
+ { bookId: 'BOOK_2', title: '普通书二', author: '作者乙' },
83
+ { bookId: 'MP_WXS_3', title: '公众号文章三', author: '作者丙' },
84
+ ],
85
+ shelfIndexes: [
86
+ { bookId: 'MP_WXS_1', idx: 0, role: 'mp' },
87
+ { bookId: 'BOOK_2', idx: 1, role: 'book' },
88
+ { bookId: 'MP_WXS_3', idx: 2, role: 'mp' },
89
+ ],
90
+ },
91
+ [
92
+ 'https://weread.qq.com/web/reader/mp1',
93
+ 'https://weread.qq.com/web/reader/book2',
94
+ 'https://weread.qq.com/web/reader/mp3',
95
+ ],
96
+ );
97
+
98
+ expect(result).toEqual([
99
+ {
100
+ bookId: 'MP_WXS_1',
101
+ title: '公众号文章一',
102
+ author: '作者甲',
103
+ readerUrl: 'https://weread.qq.com/web/reader/mp1',
104
+ },
105
+ {
106
+ bookId: 'BOOK_2',
107
+ title: '普通书二',
108
+ author: '作者乙',
109
+ readerUrl: 'https://weread.qq.com/web/reader/book2',
110
+ },
111
+ {
112
+ bookId: 'MP_WXS_3',
113
+ title: '公众号文章三',
114
+ author: '作者丙',
115
+ readerUrl: 'https://weread.qq.com/web/reader/mp3',
116
+ },
117
+ ]);
118
+ });
119
+
120
+ it('falls back to raw cache order when shelf indexes are incomplete', () => {
121
+ const result = buildWebShelfEntries(
122
+ {
123
+ cacheFound: true,
124
+ rawBooks: [
125
+ { bookId: 'BOOK_1', title: '第一本', author: '作者甲' },
126
+ { bookId: 'BOOK_2', title: '第二本', author: '作者乙' },
127
+ ],
128
+ shelfIndexes: [
129
+ { bookId: 'BOOK_2', idx: 0, role: 'book' },
130
+ ],
131
+ },
132
+ [
133
+ 'https://weread.qq.com/web/reader/book1',
134
+ 'https://weread.qq.com/web/reader/book2',
135
+ ],
136
+ );
137
+
138
+ expect(result).toEqual([
139
+ {
140
+ bookId: 'BOOK_1',
141
+ title: '第一本',
142
+ author: '作者甲',
143
+ readerUrl: 'https://weread.qq.com/web/reader/book1',
144
+ },
145
+ {
146
+ bookId: 'BOOK_2',
147
+ title: '第二本',
148
+ author: '作者乙',
149
+ readerUrl: 'https://weread.qq.com/web/reader/book2',
150
+ },
151
+ ]);
152
+ });
153
+ });
@@ -9,11 +9,49 @@
9
9
  import { CliError } from '../../errors.js';
10
10
  import type { BrowserCookie, IPage } from '../../types.js';
11
11
 
12
- const WEB_API = 'https://weread.qq.com/web';
13
- const API = 'https://i.weread.qq.com';
14
- const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
12
+ export const WEREAD_DOMAIN = 'weread.qq.com';
13
+ export const WEREAD_WEB_ORIGIN = `https://${WEREAD_DOMAIN}`;
14
+ export const WEREAD_SHELF_URL = `${WEREAD_WEB_ORIGIN}/web/shelf`;
15
+ const WEB_API = `${WEREAD_WEB_ORIGIN}/web`;
16
+ const API = `https://i.${WEREAD_DOMAIN}`;
17
+ export const WEREAD_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
15
18
  const WEREAD_AUTH_ERRCODES = new Set([-2010, -2012]);
16
19
 
20
+ export interface WebShelfRawBook {
21
+ bookId?: string;
22
+ title?: string;
23
+ author?: string;
24
+ }
25
+
26
+ export interface WebShelfIndexEntry {
27
+ bookId?: string;
28
+ idx?: number;
29
+ role?: string;
30
+ }
31
+
32
+ export interface WebShelfSnapshot {
33
+ cacheFound: boolean;
34
+ rawBooks: WebShelfRawBook[];
35
+ shelfIndexes: WebShelfIndexEntry[];
36
+ }
37
+
38
+ export interface WebShelfEntry {
39
+ bookId: string;
40
+ title: string;
41
+ author: string;
42
+ readerUrl: string;
43
+ }
44
+
45
+ export interface WebShelfReaderResolution {
46
+ snapshot: WebShelfSnapshot;
47
+ readerUrl: string | null;
48
+ }
49
+
50
+ interface WebShelfStorageKeys {
51
+ rawBooksKey: string;
52
+ shelfIndexesKey: string;
53
+ }
54
+
17
55
  function buildCookieHeader(cookies: BrowserCookie[]): string {
18
56
  return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');
19
57
  }
@@ -22,6 +60,88 @@ function isAuthErrorResponse(resp: Response, data: any): boolean {
22
60
  return resp.status === 401 || WEREAD_AUTH_ERRCODES.has(Number(data?.errcode));
23
61
  }
24
62
 
63
+ function getCurrentVid(cookies: BrowserCookie[]): string {
64
+ return String(cookies.find((cookie) => cookie.name === 'wr_vid')?.value || '').trim();
65
+ }
66
+
67
+ function getWebShelfStorageKeys(currentVid: string): WebShelfStorageKeys {
68
+ return {
69
+ rawBooksKey: `shelf:rawBooks:${currentVid}`,
70
+ shelfIndexesKey: `shelf:shelfIndexes:${currentVid}`,
71
+ };
72
+ }
73
+
74
+ function normalizeWebShelfSnapshot(value: Partial<WebShelfSnapshot> | null | undefined): WebShelfSnapshot {
75
+ return {
76
+ cacheFound: value?.cacheFound === true,
77
+ rawBooks: Array.isArray(value?.rawBooks) ? value.rawBooks : [],
78
+ shelfIndexes: Array.isArray(value?.shelfIndexes) ? value.shelfIndexes : [],
79
+ };
80
+ }
81
+
82
+ function buildShelfSnapshotPollScript(storageKeys: WebShelfStorageKeys, requireTrustedIndexes: boolean): string {
83
+ return `
84
+ (() => new Promise((resolve) => {
85
+ const deadline = Date.now() + 5000;
86
+ const rawBooksKey = ${JSON.stringify(storageKeys.rawBooksKey)};
87
+ const shelfIndexesKey = ${JSON.stringify(storageKeys.shelfIndexesKey)};
88
+ const requireTrustedIndexes = ${JSON.stringify(requireTrustedIndexes)};
89
+
90
+ const readJson = (raw) => {
91
+ if (typeof raw !== 'string') return null;
92
+ try {
93
+ return JSON.parse(raw);
94
+ } catch {
95
+ return null;
96
+ }
97
+ };
98
+
99
+ const collectBookIds = (items) => Array.isArray(items)
100
+ ? Array.from(new Set(items.map((item) => String(item?.bookId || '').trim()).filter(Boolean)))
101
+ : [];
102
+
103
+ // Mirror of getTrustedIndexedBookIds in Node.js — keep in sync
104
+ const hasTrustedIndexes = (rawBooks, shelfIndexes) => {
105
+ const rawBookIds = collectBookIds(rawBooks);
106
+ if (rawBookIds.length === 0) return false;
107
+
108
+ const rawBookIdSet = new Set(rawBookIds);
109
+ const projectedIndexedBookIds = Array.isArray(shelfIndexes)
110
+ ? Array.from(new Set(
111
+ shelfIndexes
112
+ .filter((entry) => Number.isFinite(entry?.idx))
113
+ .sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
114
+ .map((entry) => String(entry?.bookId || '').trim())
115
+ .filter((bookId) => rawBookIdSet.has(bookId)),
116
+ ))
117
+ : [];
118
+
119
+ return projectedIndexedBookIds.length === rawBookIds.length;
120
+ };
121
+
122
+ const poll = () => {
123
+ const rawBooks = readJson(localStorage.getItem(rawBooksKey));
124
+ const shelfIndexes = readJson(localStorage.getItem(shelfIndexesKey));
125
+ const cacheFound = Array.isArray(rawBooks);
126
+ const ready = cacheFound && (!requireTrustedIndexes || hasTrustedIndexes(rawBooks, shelfIndexes));
127
+
128
+ if (ready || Date.now() >= deadline) {
129
+ resolve({
130
+ cacheFound,
131
+ rawBooks: Array.isArray(rawBooks) ? rawBooks : [],
132
+ shelfIndexes: Array.isArray(shelfIndexes) ? shelfIndexes : [],
133
+ });
134
+ return;
135
+ }
136
+
137
+ setTimeout(poll, 100);
138
+ };
139
+
140
+ poll();
141
+ }))
142
+ `;
143
+ }
144
+
25
145
  /**
26
146
  * Fetch a public WeRead web endpoint (Node.js direct fetch).
27
147
  * Used by search and ranking commands (browser: false).
@@ -32,7 +152,7 @@ export async function fetchWebApi(path: string, params?: Record<string, string>)
32
152
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
33
153
  }
34
154
  const resp = await fetch(url.toString(), {
35
- headers: { 'User-Agent': UA },
155
+ headers: { 'User-Agent': WEREAD_UA },
36
156
  });
37
157
  if (!resp.ok) {
38
158
  throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
@@ -47,6 +167,10 @@ export async function fetchWebApi(path: string, params?: Record<string, string>)
47
167
  /**
48
168
  * Fetch a private WeRead API endpoint with cookies extracted from the browser.
49
169
  * The HTTP request itself runs in Node.js to avoid page-context CORS failures.
170
+ *
171
+ * Cookies are collected from both the API subdomain (i.weread.qq.com) and the
172
+ * main domain (weread.qq.com). WeRead may set auth cookies as host-only on
173
+ * weread.qq.com, which won't match i.weread.qq.com in a URL-based lookup.
50
174
  */
51
175
  export async function fetchPrivateApi(page: IPage, path: string, params?: Record<string, string>): Promise<any> {
52
176
  const url = new URL(`${API}${path}`);
@@ -55,14 +179,21 @@ export async function fetchPrivateApi(page: IPage, path: string, params?: Record
55
179
  }
56
180
  const urlStr = url.toString();
57
181
 
58
- const cookies = await page.getCookies({ url: urlStr });
59
- const cookieHeader = buildCookieHeader(cookies);
182
+ // Merge cookies from both domains; API-domain cookies take precedence on name collision
183
+ const [apiCookies, domainCookies] = await Promise.all([
184
+ page.getCookies({ url: urlStr }),
185
+ page.getCookies({ domain: WEREAD_DOMAIN }),
186
+ ]);
187
+ const merged = new Map<string, BrowserCookie>();
188
+ for (const c of domainCookies) merged.set(c.name, c);
189
+ for (const c of apiCookies) merged.set(c.name, c);
190
+ const cookieHeader = buildCookieHeader(Array.from(merged.values()));
60
191
 
61
192
  let resp: Response;
62
193
  try {
63
194
  resp = await fetch(urlStr, {
64
195
  headers: {
65
- 'User-Agent': UA,
196
+ 'User-Agent': WEREAD_UA,
66
197
  'Origin': 'https://weread.qq.com',
67
198
  'Referer': 'https://weread.qq.com/',
68
199
  ...(cookieHeader ? { 'Cookie': cookieHeader } : {}),
@@ -95,6 +226,161 @@ export async function fetchPrivateApi(page: IPage, path: string, params?: Record
95
226
  return data;
96
227
  }
97
228
 
229
+ function getUniqueRawBookIds(snapshot: WebShelfSnapshot): string[] {
230
+ return Array.from(new Set(
231
+ snapshot.rawBooks
232
+ .map((book) => String(book?.bookId || '').trim())
233
+ .filter(Boolean),
234
+ ));
235
+ }
236
+
237
+ /** Mirror of hasTrustedIndexes in buildShelfSnapshotPollScript — keep in sync */
238
+ function getTrustedIndexedBookIds(snapshot: WebShelfSnapshot): string[] {
239
+ const rawBookIds = getUniqueRawBookIds(snapshot);
240
+ if (rawBookIds.length === 0) return [];
241
+
242
+ const rawBookIdSet = new Set(rawBookIds);
243
+ const projectedIndexedBookIds = Array.from(new Set(
244
+ snapshot.shelfIndexes
245
+ .filter((entry) => Number.isFinite(entry?.idx))
246
+ .sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
247
+ .map((entry) => String(entry?.bookId || '').trim())
248
+ .filter((bookId) => rawBookIdSet.has(bookId)),
249
+ ));
250
+
251
+ return projectedIndexedBookIds.length === rawBookIds.length ? projectedIndexedBookIds : [];
252
+ }
253
+
254
+ /**
255
+ * Build stable shelf records from the web cache plus optional rendered reader URLs.
256
+ * We only trust shelfIndexes when it fully covers the same bookId set as rawBooks;
257
+ * otherwise we keep rawBooks order to avoid partial hydration reordering entries.
258
+ */
259
+ export function buildWebShelfEntries(snapshot: WebShelfSnapshot, readerUrls: string[] = []): WebShelfEntry[] {
260
+ const rawBookIds = getUniqueRawBookIds(snapshot);
261
+ const trustedIndexedBookIds = getTrustedIndexedBookIds(snapshot);
262
+ const orderedBookIds = trustedIndexedBookIds.length > 0 ? trustedIndexedBookIds : rawBookIds;
263
+
264
+ const rawBookById = new Map<string, WebShelfRawBook>();
265
+ for (const book of snapshot.rawBooks) {
266
+ const bookId = String(book?.bookId || '').trim();
267
+ if (!bookId || rawBookById.has(bookId)) continue;
268
+ rawBookById.set(bookId, book);
269
+ }
270
+
271
+ return orderedBookIds.map((bookId, index) => {
272
+ const book = rawBookById.get(bookId);
273
+ return {
274
+ bookId,
275
+ title: String(book?.title || '').trim(),
276
+ author: String(book?.author || '').trim(),
277
+ readerUrl: String(readerUrls[index] || '').trim(),
278
+ };
279
+ });
280
+ }
281
+
282
+ /**
283
+ * Internal: load shelf snapshot and return the currentVid alongside it,
284
+ * so callers like resolveShelfReaderUrl can reuse it without a second getCookies.
285
+ */
286
+ async function loadWebShelfSnapshotWithVid(page: IPage): Promise<{ snapshot: WebShelfSnapshot; currentVid: string }> {
287
+ await page.goto(WEREAD_SHELF_URL);
288
+
289
+ const cookies = await page.getCookies({ domain: WEREAD_DOMAIN });
290
+ const currentVid = getCurrentVid(cookies);
291
+
292
+ if (!currentVid) {
293
+ return { snapshot: { cacheFound: false, rawBooks: [], shelfIndexes: [] }, currentVid: '' };
294
+ }
295
+
296
+ const result = await page.evaluate(
297
+ buildShelfSnapshotPollScript(getWebShelfStorageKeys(currentVid), false),
298
+ );
299
+ return {
300
+ snapshot: normalizeWebShelfSnapshot(result as Partial<WebShelfSnapshot> | null | undefined),
301
+ currentVid,
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Read the structured shelf cache from the WeRead shelf page.
307
+ * The page hydrates localStorage asynchronously, so we poll briefly before
308
+ * giving up and treating the cache as unavailable for the current session.
309
+ */
310
+ export async function loadWebShelfSnapshot(page: IPage): Promise<WebShelfSnapshot> {
311
+ const { snapshot } = await loadWebShelfSnapshotWithVid(page);
312
+ return snapshot;
313
+ }
314
+
315
+ /**
316
+ * `book` needs a trustworthy `bookId -> readerUrl` mapping, which may lag behind
317
+ * the first rawBooks cache hydration. Keep the fast shelf fallback path separate
318
+ * and only wait here, with a bounded poll, when resolving reader URLs.
319
+ */
320
+ async function waitForTrustedWebShelfSnapshot(page: IPage, snapshot: WebShelfSnapshot, currentVid: string): Promise<WebShelfSnapshot> {
321
+ // Cache not available; nothing to wait for
322
+ if (!snapshot.cacheFound) return snapshot;
323
+ // Indexes already fully cover rawBooks; no need to re-poll
324
+ if (getTrustedIndexedBookIds(snapshot).length > 0) return snapshot;
325
+
326
+ if (!currentVid) return snapshot;
327
+
328
+ const result = await page.evaluate(
329
+ buildShelfSnapshotPollScript(getWebShelfStorageKeys(currentVid), true),
330
+ );
331
+ return normalizeWebShelfSnapshot(result as Partial<WebShelfSnapshot> | null | undefined);
332
+ }
333
+
334
+ /**
335
+ * Resolve a shelf bookId to the current web reader URL by pairing structured
336
+ * shelf cache order with the visible shelf links rendered on the page.
337
+ */
338
+ export async function resolveShelfReaderUrl(page: IPage, bookId: string): Promise<string | null> {
339
+ const resolution = await resolveShelfReader(page, bookId);
340
+ return resolution.readerUrl;
341
+ }
342
+
343
+ /**
344
+ * Resolve the current reader URL for a shelf entry and return the parsed shelf
345
+ * snapshot used during resolution, so callers can reuse cached title/author
346
+ * metadata without loading the shelf page twice.
347
+ */
348
+ export async function resolveShelfReader(page: IPage, bookId: string): Promise<WebShelfReaderResolution> {
349
+ const { snapshot: initialSnapshot, currentVid } = await loadWebShelfSnapshotWithVid(page);
350
+ const snapshot = await waitForTrustedWebShelfSnapshot(page, initialSnapshot, currentVid);
351
+ if (!snapshot.cacheFound) {
352
+ return { snapshot, readerUrl: null };
353
+ }
354
+ const rawBookIds = getUniqueRawBookIds(snapshot);
355
+ const trustedIndexedBookIds = getTrustedIndexedBookIds(snapshot);
356
+ const canUseRawOrderFallback = trustedIndexedBookIds.length === 0
357
+ && rawBookIds.length > 0
358
+ && snapshot.shelfIndexes.length === 0;
359
+ if (trustedIndexedBookIds.length === 0 && !canUseRawOrderFallback) {
360
+ return { snapshot, readerUrl: null };
361
+ }
362
+
363
+ const readerUrls = await page.evaluate(`
364
+ (() => Array.from(document.querySelectorAll('a.shelfBook[href]'))
365
+ .map((anchor) => {
366
+ const href = anchor.getAttribute('href') || '';
367
+ return href ? new URL(href, location.origin).toString() : '';
368
+ })
369
+ .filter(Boolean))
370
+ `) as string[];
371
+ const expectedEntryCount = trustedIndexedBookIds.length > 0 ? trustedIndexedBookIds.length : rawBookIds.length;
372
+ if (readerUrls.length !== expectedEntryCount) {
373
+ return { snapshot, readerUrl: null };
374
+ }
375
+ const entries = buildWebShelfEntries(snapshot, readerUrls);
376
+
377
+ const entry = entries.find((candidate) => candidate.bookId === bookId);
378
+ return {
379
+ snapshot,
380
+ readerUrl: entry?.readerUrl || null,
381
+ };
382
+ }
383
+
98
384
  /** Format a Unix timestamp (seconds) to YYYY-MM-DD in UTC+8. Returns '-' for invalid input. */
99
385
  export function formatDate(ts: number | undefined | null): string {
100
386
  if (!Number.isFinite(ts) || (ts as number) <= 0) return '-';