@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
@@ -0,0 +1,351 @@
1
+ import { ArgumentError, AuthRequiredError, CliError } from '../../errors.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export interface ZsxqUser {
5
+ user_id?: number;
6
+ name?: string;
7
+ avatar_url?: string;
8
+ }
9
+
10
+ export interface ZsxqGroup {
11
+ group_id?: number;
12
+ name?: string;
13
+ description?: string;
14
+ background_url?: string;
15
+ owner?: ZsxqUser;
16
+ statistics?: {
17
+ topics_count?: number;
18
+ answers_count?: number;
19
+ comments_count?: number;
20
+ likes_count?: number;
21
+ subscriptions_count?: number;
22
+ };
23
+ category?: {
24
+ title?: string;
25
+ };
26
+ user_specific?: {
27
+ join_time?: string;
28
+ validity?: {
29
+ end_time?: string;
30
+ };
31
+ };
32
+ }
33
+
34
+ export interface ZsxqComment {
35
+ comment_id?: number;
36
+ create_time?: string;
37
+ text?: string;
38
+ owner?: ZsxqUser;
39
+ likes_count?: number;
40
+ rewards_count?: number;
41
+ repliee?: ZsxqUser;
42
+ }
43
+
44
+ export interface ZsxqTopic {
45
+ topic_id?: number;
46
+ create_time?: string;
47
+ comments_count?: number;
48
+ likes_count?: number;
49
+ readers_count?: number;
50
+ reading_count?: number;
51
+ rewards_count?: number;
52
+ title?: string;
53
+ type?: string;
54
+ group?: ZsxqGroup;
55
+ owner?: ZsxqUser;
56
+ user_specific?: Record<string, unknown>;
57
+ talk?: {
58
+ owner?: ZsxqUser;
59
+ text?: string;
60
+ };
61
+ question?: {
62
+ owner?: ZsxqUser;
63
+ text?: string;
64
+ };
65
+ answer?: {
66
+ owner?: ZsxqUser;
67
+ text?: string;
68
+ };
69
+ task?: {
70
+ owner?: ZsxqUser;
71
+ text?: string;
72
+ };
73
+ solution?: {
74
+ owner?: ZsxqUser;
75
+ text?: string;
76
+ };
77
+ show_comments?: ZsxqComment[];
78
+ comments?: ZsxqComment[];
79
+ }
80
+
81
+ export interface BrowserFetchResult {
82
+ ok: boolean;
83
+ url?: string;
84
+ status?: number;
85
+ error?: string;
86
+ data?: unknown;
87
+ }
88
+
89
+ const SITE_DOMAIN = 'wx.zsxq.com';
90
+ const SITE_URL = 'https://wx.zsxq.com';
91
+
92
+ function asRecord(value: unknown): Record<string, unknown> | null {
93
+ return value && typeof value === 'object' && !Array.isArray(value)
94
+ ? value as Record<string, unknown>
95
+ : null;
96
+ }
97
+
98
+ function pickArray<T>(...values: unknown[]): T[] {
99
+ for (const value of values) {
100
+ if (Array.isArray(value)) {
101
+ return value as T[];
102
+ }
103
+ }
104
+ return [];
105
+ }
106
+
107
+ export async function ensureZsxqPage(page: IPage): Promise<void> {
108
+ await page.goto(SITE_URL);
109
+ }
110
+
111
+ export async function ensureZsxqAuth(page: IPage): Promise<void> {
112
+ // zsxq uses httpOnly cookies that may be on different subdomains.
113
+ // Verify auth by attempting a lightweight API call instead of checking cookies.
114
+ try {
115
+ const result = await page.evaluate(`
116
+ (async () => {
117
+ try {
118
+ const r = await new Promise((resolve, reject) => {
119
+ const xhr = new XMLHttpRequest();
120
+ xhr.open('GET', 'https://api.zsxq.com/v2/groups', true);
121
+ xhr.withCredentials = true;
122
+ xhr.setRequestHeader('accept', 'application/json');
123
+ xhr.onload = () => {
124
+ if (xhr.status >= 200 && xhr.status < 300) {
125
+ try { resolve(JSON.parse(xhr.responseText)); }
126
+ catch { resolve(null); }
127
+ } else { resolve(null); }
128
+ };
129
+ xhr.onerror = () => resolve(null);
130
+ xhr.send();
131
+ });
132
+ return r !== null;
133
+ } catch { return false; }
134
+ })()
135
+ `);
136
+ if (!result) {
137
+ throw new AuthRequiredError('zsxq.com');
138
+ }
139
+ } catch (err) {
140
+ if (err instanceof AuthRequiredError) throw err;
141
+ throw new AuthRequiredError('zsxq.com');
142
+ }
143
+ }
144
+
145
+ export async function getCookieValue(page: IPage, name: string): Promise<string | undefined> {
146
+ const cookies = await page.getCookies({ domain: SITE_DOMAIN });
147
+ return cookies.find(cookie => cookie.name === name)?.value;
148
+ }
149
+
150
+ export async function getActiveGroupId(page: IPage): Promise<string> {
151
+ const groupId = await page.evaluate(`
152
+ (() => {
153
+ const target = localStorage.getItem('target_group');
154
+ if (target) {
155
+ try {
156
+ const parsed = JSON.parse(target);
157
+ if (parsed.group_id) return String(parsed.group_id);
158
+ } catch {}
159
+ }
160
+ return null;
161
+ })()
162
+ `);
163
+ if (groupId) return groupId;
164
+
165
+ throw new ArgumentError(
166
+ 'Cannot determine active group_id',
167
+ 'Pass --group_id <id> or open the target 知识星球 page in Chrome first',
168
+ );
169
+ }
170
+
171
+ export async function browserJsonRequest(page: IPage, path: string): Promise<BrowserFetchResult> {
172
+ return await page.evaluate(`
173
+ (async () => {
174
+ const path = ${JSON.stringify(path)};
175
+
176
+ try {
177
+ return await new Promise((resolve) => {
178
+ const xhr = new XMLHttpRequest();
179
+ xhr.open('GET', path, true);
180
+ xhr.withCredentials = true;
181
+ xhr.setRequestHeader('accept', 'application/json, text/plain, */*');
182
+ xhr.onload = () => {
183
+ let parsed = null;
184
+ if (xhr.responseText) {
185
+ try { parsed = JSON.parse(xhr.responseText); }
186
+ catch {}
187
+ }
188
+
189
+ resolve({
190
+ ok: xhr.status >= 200 && xhr.status < 300,
191
+ url: path,
192
+ status: xhr.status,
193
+ data: parsed,
194
+ error: xhr.status >= 200 && xhr.status < 300 ? undefined : 'HTTP ' + xhr.status,
195
+ });
196
+ };
197
+ xhr.onerror = () => resolve({
198
+ ok: false,
199
+ url: path,
200
+ error: 'Network error',
201
+ });
202
+ xhr.send();
203
+ });
204
+ } catch (error) {
205
+ return {
206
+ ok: false,
207
+ url: path,
208
+ error: error instanceof Error ? error.message : String(error),
209
+ };
210
+ }
211
+ })()
212
+ `) as BrowserFetchResult;
213
+ }
214
+
215
+ export async function fetchFirstJson(page: IPage, paths: string[]): Promise<BrowserFetchResult> {
216
+ let lastFailure: BrowserFetchResult | null = null;
217
+
218
+ for (const path of paths) {
219
+ const result = await browserJsonRequest(page, path);
220
+ if (result.ok) {
221
+ return result;
222
+ }
223
+ lastFailure = result;
224
+ }
225
+
226
+ if (!lastFailure) {
227
+ throw new CliError(
228
+ 'FETCH_ERROR',
229
+ 'No candidate endpoint returned JSON',
230
+ `Checked endpoints: ${paths.join(', ')}`,
231
+ );
232
+ }
233
+
234
+ throw new CliError(
235
+ 'FETCH_ERROR',
236
+ lastFailure.error || 'Failed to fetch ZSXQ API',
237
+ `Checked endpoints: ${paths.join(', ')}`,
238
+ );
239
+ }
240
+
241
+ export function unwrapRespData<T>(payload: unknown): T {
242
+ const record = asRecord(payload);
243
+ if (!record) {
244
+ throw new CliError('PARSE_ERROR', 'Invalid ZSXQ API response');
245
+ }
246
+
247
+ if (record.succeeded === false) {
248
+ const code = typeof record.code === 'number' ? String(record.code) : 'API_ERROR';
249
+ const message = typeof record.info === 'string'
250
+ ? record.info
251
+ : typeof record.error === 'string'
252
+ ? record.error
253
+ : 'ZSXQ API returned an error';
254
+ throw new CliError(code, message);
255
+ }
256
+
257
+ return (record.resp_data ?? record.data ?? payload) as T;
258
+ }
259
+
260
+ export function getTopicsFromResponse(payload: unknown): ZsxqTopic[] {
261
+ const data = unwrapRespData<Record<string, unknown> | ZsxqTopic[]>(payload);
262
+ if (Array.isArray(data)) return data;
263
+ return pickArray<ZsxqTopic>(
264
+ data.topics,
265
+ data.list,
266
+ data.records,
267
+ data.items,
268
+ data.search_result,
269
+ );
270
+ }
271
+
272
+ export function getCommentsFromResponse(payload: unknown): ZsxqComment[] {
273
+ const data = unwrapRespData<Record<string, unknown> | ZsxqComment[]>(payload);
274
+ if (Array.isArray(data)) return data;
275
+ return pickArray<ZsxqComment>(data.comments, data.list, data.items);
276
+ }
277
+
278
+ export function getGroupsFromResponse(payload: unknown): ZsxqGroup[] {
279
+ const data = unwrapRespData<Record<string, unknown> | ZsxqGroup[]>(payload);
280
+ if (Array.isArray(data)) return data;
281
+ return pickArray<ZsxqGroup>(data.groups, data.list, data.items);
282
+ }
283
+
284
+ export function getTopicFromResponse(payload: unknown): ZsxqTopic | null {
285
+ const data = unwrapRespData<Record<string, unknown> | ZsxqTopic>(payload);
286
+ if (Array.isArray(data)) return data[0] ?? null;
287
+ if (typeof data.topic_id === 'number') return data;
288
+ const record = asRecord(data);
289
+ if (!record) return null;
290
+ const topic = record.topic;
291
+ return topic && typeof topic === 'object' ? topic as ZsxqTopic : null;
292
+ }
293
+
294
+ export function getTopicAuthor(topic: ZsxqTopic): string {
295
+ return (
296
+ topic.owner?.name ||
297
+ topic.talk?.owner?.name ||
298
+ topic.question?.owner?.name ||
299
+ topic.answer?.owner?.name ||
300
+ topic.task?.owner?.name ||
301
+ topic.solution?.owner?.name ||
302
+ ''
303
+ );
304
+ }
305
+
306
+ export function getTopicText(topic: ZsxqTopic): string {
307
+ const primary = [
308
+ topic.title,
309
+ topic.talk?.text,
310
+ topic.question?.text,
311
+ topic.answer?.text,
312
+ topic.task?.text,
313
+ topic.solution?.text,
314
+ ].find(value => typeof value === 'string' && value.trim());
315
+ return (primary || '').replace(/\s+/g, ' ').trim();
316
+ }
317
+
318
+ export function getTopicUrl(topicId: number | string | undefined): string {
319
+ return topicId ? `${SITE_URL}/topic/${topicId}` : SITE_URL;
320
+ }
321
+
322
+ export function summarizeComments(comments: ZsxqComment[], limit: number = 3): string {
323
+ return comments
324
+ .slice(0, limit)
325
+ .map((comment) => {
326
+ const author = comment.owner?.name || '匿名';
327
+ const target = comment.repliee?.name ? ` -> ${comment.repliee.name}` : '';
328
+ const text = (comment.text || '').replace(/\s+/g, ' ').trim();
329
+ return `${author}${target}: ${text}`;
330
+ })
331
+ .join(' | ');
332
+ }
333
+
334
+ export function toTopicRow(topic: ZsxqTopic): Record<string, unknown> {
335
+ const topicId = topic.topic_id ?? '';
336
+ const comments = pickArray<ZsxqComment>(topic.show_comments, topic.comments);
337
+ return {
338
+ topic_id: topicId,
339
+ type: topic.type || '',
340
+ group: topic.group?.name || '',
341
+ author: getTopicAuthor(topic),
342
+ title: getTopicText(topic).slice(0, 120),
343
+ content: getTopicText(topic),
344
+ comments: topic.comments_count ?? comments.length ?? 0,
345
+ likes: topic.likes_count ?? 0,
346
+ readers: topic.readers_count ?? topic.reading_count ?? 0,
347
+ time: topic.create_time || '',
348
+ comment_preview: summarizeComments(comments),
349
+ url: getTopicUrl(topicId),
350
+ };
351
+ }
@@ -76,3 +76,80 @@ describe('commanderAdapter arg passing', () => {
76
76
  expect(mockExecuteCommand).not.toHaveBeenCalled();
77
77
  });
78
78
  });
79
+
80
+ describe('commanderAdapter boolean alias support', () => {
81
+ const cmd: CliCommand = {
82
+ site: 'reddit',
83
+ name: 'save',
84
+ description: 'Save a post',
85
+ browser: false,
86
+ args: [
87
+ { name: 'post-id', positional: true, required: true, help: 'Post ID' },
88
+ { name: 'undo', type: 'boolean', default: false, help: 'Unsave instead of save' },
89
+ ],
90
+ func: vi.fn(),
91
+ };
92
+
93
+ beforeEach(() => {
94
+ mockExecuteCommand.mockReset();
95
+ mockExecuteCommand.mockResolvedValue([]);
96
+ mockRenderOutput.mockReset();
97
+ delete process.env.OPENCLI_VERBOSE;
98
+ process.exitCode = undefined;
99
+ });
100
+
101
+ it('coerces default false for boolean args to a real boolean', async () => {
102
+ const program = new Command();
103
+ const siteCmd = program.command('reddit');
104
+ registerCommandToProgram(siteCmd, cmd);
105
+
106
+ await program.parseAsync(['node', 'opencli', 'reddit', 'save', 't3_abc123']);
107
+
108
+ expect(mockExecuteCommand).toHaveBeenCalled();
109
+ const kwargs = mockExecuteCommand.mock.calls[0][1];
110
+ expect(kwargs['post-id']).toBe('t3_abc123');
111
+ expect(kwargs.undo).toBe(false);
112
+ });
113
+
114
+ it('coerces explicit false for boolean args to a real boolean', async () => {
115
+ const program = new Command();
116
+ const siteCmd = program.command('reddit');
117
+ registerCommandToProgram(siteCmd, cmd);
118
+
119
+ await program.parseAsync(['node', 'opencli', 'reddit', 'save', 't3_abc123', '--undo', 'false']);
120
+
121
+ expect(mockExecuteCommand).toHaveBeenCalled();
122
+ const kwargs = mockExecuteCommand.mock.calls[0][1];
123
+ expect(kwargs.undo).toBe(false);
124
+ });
125
+ });
126
+
127
+ describe('commanderAdapter command aliases', () => {
128
+ const cmd: CliCommand = {
129
+ site: 'notebooklm',
130
+ name: 'get',
131
+ aliases: ['metadata'],
132
+ description: 'Get notebook metadata',
133
+ browser: false,
134
+ args: [],
135
+ func: vi.fn(),
136
+ };
137
+
138
+ beforeEach(() => {
139
+ mockExecuteCommand.mockReset();
140
+ mockExecuteCommand.mockResolvedValue([]);
141
+ mockRenderOutput.mockReset();
142
+ delete process.env.OPENCLI_VERBOSE;
143
+ process.exitCode = undefined;
144
+ });
145
+
146
+ it('registers aliases with Commander so compatibility names execute the same command', async () => {
147
+ const program = new Command();
148
+ const siteCmd = program.command('notebooklm');
149
+ registerCommandToProgram(siteCmd, cmd);
150
+
151
+ await program.parseAsync(['node', 'opencli', 'notebooklm', 'metadata']);
152
+
153
+ expect(mockExecuteCommand).toHaveBeenCalledWith(cmd, {}, false);
154
+ });
155
+ });
@@ -33,7 +33,7 @@ import {
33
33
  import { checkDaemonStatus } from './browser/discover.js';
34
34
 
35
35
  export function normalizeArgValue(argType: string | undefined, value: unknown, name: string): unknown {
36
- if (argType !== 'bool') return value;
36
+ if (argType !== 'bool' && argType !== 'boolean') return value;
37
37
  if (typeof value === 'boolean') return value;
38
38
  if (value == null || value === '') return false;
39
39
 
@@ -52,6 +52,7 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
52
52
 
53
53
  const deprecatedSuffix = cmd.deprecated ? ' [deprecated]' : '';
54
54
  const subCmd = siteCmd.command(cmd.name).description(`${cmd.description}${deprecatedSuffix}`);
55
+ if (cmd.aliases?.length) subCmd.aliases(cmd.aliases);
55
56
 
56
57
  // Register positional args first, then named options
57
58
  const positionalArgs: typeof cmd.args = [];
@@ -103,6 +104,9 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
103
104
  }
104
105
 
105
106
  const result = await executeCommand(cmd, kwargs, verbose);
107
+ if (result === null || result === undefined) {
108
+ return;
109
+ }
106
110
 
107
111
  if (verbose && (!result || (Array.isArray(result) && result.length === 0))) {
108
112
  console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
@@ -293,7 +297,10 @@ export function registerAllCommands(
293
297
  program: Command,
294
298
  siteGroups: Map<string, Command>,
295
299
  ): void {
300
+ const seen = new Set<CliCommand>();
296
301
  for (const [, cmd] of getRegistry()) {
302
+ if (seen.has(cmd)) continue;
303
+ seen.add(cmd);
297
304
  let siteCmd = siteGroups.get(cmd.site);
298
305
  if (!siteCmd) {
299
306
  siteCmd = program.command(cmd.site).description(`${cmd.site} commands`);
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ vi.mock('chalk', () => ({
4
+ default: {
5
+ green: (s: string) => s,
6
+ yellow: (s: string) => s,
7
+ red: (s: string) => s,
8
+ dim: (s: string) => s,
9
+ },
10
+ }));
11
+
12
+ const mockConnect = vi.fn();
13
+ vi.mock('../browser/mcp.js', () => ({
14
+ BrowserBridge: class {
15
+ connect = mockConnect;
16
+ },
17
+ }));
18
+
19
+ import { daemonStatus, daemonStop, daemonRestart } from './daemon.js';
20
+
21
+ describe('daemon commands', () => {
22
+ let logSpy: ReturnType<typeof vi.spyOn>;
23
+ let errorSpy: ReturnType<typeof vi.spyOn>;
24
+
25
+ beforeEach(() => {
26
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
27
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
28
+ });
29
+
30
+ afterEach(() => {
31
+ vi.restoreAllMocks();
32
+ mockConnect.mockReset();
33
+ });
34
+
35
+ describe('daemonStatus', () => {
36
+ it('shows "not running" when daemon is unreachable', async () => {
37
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));
38
+
39
+ await daemonStatus();
40
+
41
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
42
+ });
43
+
44
+ it('shows "not running" when daemon returns non-ok response', async () => {
45
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }));
46
+
47
+ await daemonStatus();
48
+
49
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
50
+ });
51
+
52
+ it('shows daemon info when running', async () => {
53
+ const status = {
54
+ ok: true,
55
+ pid: 12345,
56
+ uptime: 3661,
57
+ extensionConnected: true,
58
+ pending: 0,
59
+ lastCliRequestTime: Date.now() - 30_000,
60
+ memoryMB: 64,
61
+ port: 19825,
62
+ };
63
+
64
+ vi.stubGlobal(
65
+ 'fetch',
66
+ vi.fn().mockResolvedValue({
67
+ ok: true,
68
+ json: () => Promise.resolve(status),
69
+ }),
70
+ );
71
+
72
+ await daemonStatus();
73
+
74
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('running'));
75
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('PID 12345'));
76
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('1h 1m'));
77
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('connected'));
78
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('64 MB'));
79
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('19825'));
80
+ });
81
+
82
+ it('shows disconnected when extension is not connected', async () => {
83
+ const status = {
84
+ ok: true,
85
+ pid: 99,
86
+ uptime: 120,
87
+ extensionConnected: false,
88
+ pending: 0,
89
+ lastCliRequestTime: Date.now() - 5000,
90
+ memoryMB: 32,
91
+ port: 19825,
92
+ };
93
+
94
+ vi.stubGlobal(
95
+ 'fetch',
96
+ vi.fn().mockResolvedValue({
97
+ ok: true,
98
+ json: () => Promise.resolve(status),
99
+ }),
100
+ );
101
+
102
+ await daemonStatus();
103
+
104
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('disconnected'));
105
+ });
106
+ });
107
+
108
+ describe('daemonStop', () => {
109
+ it('reports "not running" when daemon is unreachable', async () => {
110
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));
111
+
112
+ await daemonStop();
113
+
114
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
115
+ });
116
+
117
+ it('sends shutdown and reports success', async () => {
118
+ const statusResponse = {
119
+ ok: true,
120
+ json: () =>
121
+ Promise.resolve({
122
+ ok: true,
123
+ pid: 12345,
124
+ uptime: 100,
125
+ extensionConnected: true,
126
+ pending: 0,
127
+ lastCliRequestTime: Date.now(),
128
+ memoryMB: 50,
129
+ port: 19825,
130
+ }),
131
+ };
132
+ const shutdownResponse = { ok: true };
133
+
134
+ const mockFetch = vi.fn()
135
+ .mockResolvedValueOnce(statusResponse)
136
+ .mockResolvedValueOnce(shutdownResponse);
137
+ vi.stubGlobal('fetch', mockFetch);
138
+
139
+ await daemonStop();
140
+
141
+ // Verify shutdown was called with POST
142
+ expect(mockFetch).toHaveBeenCalledTimes(2);
143
+ const shutdownCall = mockFetch.mock.calls[1];
144
+ expect(shutdownCall[0]).toContain('/shutdown');
145
+ expect(shutdownCall[1]).toMatchObject({ method: 'POST' });
146
+
147
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon stopped'));
148
+ });
149
+
150
+ it('reports failure when shutdown request fails', async () => {
151
+ const statusResponse = {
152
+ ok: true,
153
+ json: () =>
154
+ Promise.resolve({
155
+ ok: true,
156
+ pid: 12345,
157
+ uptime: 100,
158
+ extensionConnected: true,
159
+ pending: 0,
160
+ lastCliRequestTime: Date.now(),
161
+ memoryMB: 50,
162
+ port: 19825,
163
+ }),
164
+ };
165
+ const shutdownResponse = { ok: false };
166
+
167
+ const mockFetch = vi.fn()
168
+ .mockResolvedValueOnce(statusResponse)
169
+ .mockResolvedValueOnce(shutdownResponse);
170
+ vi.stubGlobal('fetch', mockFetch);
171
+
172
+ await daemonStop();
173
+
174
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to stop daemon'));
175
+ });
176
+ });
177
+
178
+ describe('daemonRestart', () => {
179
+ const statusData = {
180
+ ok: true,
181
+ pid: 12345,
182
+ uptime: 100,
183
+ extensionConnected: true,
184
+ pending: 0,
185
+ lastCliRequestTime: Date.now(),
186
+ memoryMB: 50,
187
+ port: 19825,
188
+ };
189
+
190
+ it('starts daemon directly when not running', async () => {
191
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));
192
+ mockConnect.mockResolvedValue(undefined);
193
+
194
+ await daemonRestart();
195
+
196
+ expect(mockConnect).toHaveBeenCalledWith({ timeout: 10 });
197
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon restarted'));
198
+ });
199
+
200
+ it('stops then starts when daemon is running', async () => {
201
+ const mockFetch = vi.fn()
202
+ // First call: fetchStatus in daemonRestart — daemon is running
203
+ .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(statusData) })
204
+ // Second call: requestShutdown — success
205
+ .mockResolvedValueOnce({ ok: true })
206
+ // Subsequent calls: polling fetchStatus until unreachable
207
+ .mockRejectedValue(new Error('ECONNREFUSED'));
208
+
209
+ vi.stubGlobal('fetch', mockFetch);
210
+ mockConnect.mockResolvedValue(undefined);
211
+
212
+ await daemonRestart();
213
+
214
+ // Verify shutdown was called
215
+ const shutdownCall = mockFetch.mock.calls[1];
216
+ expect(shutdownCall[0]).toContain('/shutdown');
217
+ expect(shutdownCall[1]).toMatchObject({ method: 'POST' });
218
+
219
+ expect(mockConnect).toHaveBeenCalledWith({ timeout: 10 });
220
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon restarted'));
221
+ });
222
+
223
+ it('aborts when shutdown fails', async () => {
224
+ const mockFetch = vi.fn()
225
+ // fetchStatus — daemon is running
226
+ .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(statusData) })
227
+ // requestShutdown — failure
228
+ .mockResolvedValueOnce({ ok: false });
229
+
230
+ vi.stubGlobal('fetch', mockFetch);
231
+
232
+ await daemonRestart();
233
+
234
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to stop daemon'));
235
+ expect(mockConnect).not.toHaveBeenCalled();
236
+ });
237
+ });
238
+ });