@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
@@ -140,7 +140,7 @@ describe('BrowserBridge state', () => {
140
140
 
141
141
  const mcp = new BrowserBridge();
142
142
 
143
- await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
143
+ await expect(mcp.connect({ timeout: 0.1 })).rejects.toThrow('Browser Extension is not connected');
144
144
  });
145
145
  });
146
146
 
@@ -84,6 +84,7 @@ describe('manifest helper rules', () => {
84
84
  description: 'dynamic command',
85
85
  strategy: Strategy.PUBLIC,
86
86
  browser: false,
87
+ aliases: ['metadata'],
87
88
  args: [
88
89
  {
89
90
  name: 'model',
@@ -109,6 +110,7 @@ describe('manifest helper rules', () => {
109
110
  domain: 'localhost',
110
111
  strategy: 'public',
111
112
  browser: false,
113
+ aliases: ['metadata'],
112
114
  args: [
113
115
  {
114
116
  name: 'model',
@@ -23,6 +23,7 @@ const OUTPUT = path.resolve(__dirname, '..', 'dist', 'cli-manifest.json');
23
23
  export interface ManifestEntry {
24
24
  site: string;
25
25
  name: string;
26
+ aliases?: string[];
26
27
  description: string;
27
28
  domain?: string;
28
29
  strategy: string;
@@ -84,6 +85,7 @@ function toManifestEntry(cmd: CliCommand, modulePath: string): ManifestEntry {
84
85
  return {
85
86
  site: cmd.site,
86
87
  name: cmd.name,
88
+ aliases: cmd.aliases,
87
89
  description: cmd.description ?? '',
88
90
  domain: cmd.domain,
89
91
  strategy: (cmd.strategy ?? 'public').toString().toLowerCase(),
@@ -119,6 +121,9 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
119
121
  domain: cliDef.domain,
120
122
  strategy: strategy.toLowerCase(),
121
123
  browser,
124
+ aliases: isRecord(cliDef) && Array.isArray((cliDef as Record<string, unknown>).aliases)
125
+ ? ((cliDef as Record<string, unknown>).aliases as unknown[]).filter((value): value is string => typeof value === 'string')
126
+ : undefined,
122
127
  args,
123
128
  columns: cliDef.columns,
124
129
  pipeline: cliDef.pipeline,
@@ -230,7 +235,7 @@ export async function buildManifest(): Promise<ManifestEntry[]> {
230
235
  }
231
236
  }
232
237
 
233
- return [...manifest.values()];
238
+ return [...manifest.values()].sort((a, b) => a.site.localeCompare(b.site) || a.name.localeCompare(b.name));
234
239
  }
235
240
 
236
241
  async function main(): Promise<void> {
package/src/cli.ts CHANGED
@@ -16,6 +16,7 @@ import { printCompletionScript } from './completion.js';
16
16
  import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
17
17
  import { registerAllCommands } from './commanderAdapter.js';
18
18
  import { EXIT_CODES, getErrorMessage } from './errors.js';
19
+ import { daemonStatus, daemonStop, daemonRestart } from './commands/daemon.js';
19
20
 
20
21
  export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
21
22
  const program = new Command();
@@ -36,7 +37,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
36
37
  .option('--json', 'JSON output (deprecated)')
37
38
  .action((opts) => {
38
39
  const registry = getRegistry();
39
- const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
40
+ const commands = [...new Set(registry.values())].sort((a, b) => fullName(a).localeCompare(fullName(b)));
40
41
  const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
41
42
  const isStructured = fmt === 'json' || fmt === 'yaml';
42
43
 
@@ -47,6 +48,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
47
48
  command: fullName(c),
48
49
  site: c.site,
49
50
  name: c.name,
51
+ aliases: c.aliases?.join(', ') ?? '',
50
52
  description: c.description,
51
53
  strategy: strategyLabel(c),
52
54
  browser: !!c.browser,
@@ -54,7 +56,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
54
56
  }));
55
57
  renderOutput(rows, {
56
58
  fmt,
57
- columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args',
59
+ columns: ['command', 'site', 'name', 'aliases', 'description', 'strategy', 'browser', 'args',
58
60
  ...(isStructured ? ['columns', 'domain'] : [])],
59
61
  title: 'opencli/list',
60
62
  source: 'opencli list',
@@ -80,7 +82,8 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
80
82
  const tag = label === 'public'
81
83
  ? chalk.green('[public]')
82
84
  : chalk.yellow(`[${label}]`);
83
- console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` ${cmd.description}`) : ''}`);
85
+ const aliases = cmd.aliases?.length ? chalk.dim(` (aliases: ${cmd.aliases.join(', ')})`) : '';
86
+ console.log(` ${cmd.name} ${tag}${aliases}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
84
87
  }
85
88
  console.log();
86
89
  }
@@ -442,6 +445,21 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
442
445
  }
443
446
  });
444
447
 
448
+ // ── Built-in: daemon ──────────────────────────────────────────────────────
449
+ const daemonCmd = program.command('daemon').description('Manage the opencli daemon');
450
+ daemonCmd
451
+ .command('status')
452
+ .description('Show daemon status')
453
+ .action(async () => { await daemonStatus(); });
454
+ daemonCmd
455
+ .command('stop')
456
+ .description('Stop the daemon')
457
+ .action(async () => { await daemonStop(); });
458
+ daemonCmd
459
+ .command('restart')
460
+ .description('Restart the daemon')
461
+ .action(async () => { await daemonRestart(); });
462
+
445
463
  // ── External CLIs ─────────────────────────────────────────────────────────
446
464
 
447
465
  const externalClis = loadExternalClis();
@@ -7,17 +7,10 @@ description: How to automate Antigravity using OpenCLI
7
7
  This skill allows AI agents to control the [Antigravity](https://github.com/chengazhen/Antigravity) desktop app (and any Electron app with CDP enabled) programmatically via OpenCLI.
8
8
 
9
9
  ## Requirements
10
- The target Electron application MUST be launched with the remote-debugging-port flag:
11
- \`\`\`bash
12
- /Applications/Antigravity.app/Contents/MacOS/Electron --remote-debugging-port=9224
13
- \`\`\`
14
-
15
- The agent must configure the endpoint environment variable locally before invoking standard commands:
16
- \`\`\`bash
17
- export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
18
- \`\`\`
10
+ opencli automatically detects, launches (with `--remote-debugging-port=9234`), and connects to Antigravity.
11
+ If Antigravity is already running without CDP, opencli will prompt to restart it.
19
12
 
20
- If the endpoint exposes multiple inspectable targets, also set:
13
+ If the endpoint exposes multiple inspectable targets, set:
21
14
  \`\`\`bash
22
15
  export OPENCLI_CDP_TARGET="antigravity"
23
16
  \`\`\`
@@ -33,7 +26,6 @@ export OPENCLI_CDP_TARGET="antigravity"
33
26
 
34
27
  ### Generating and Saving Code
35
28
  \`\`\`bash
36
- export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
37
29
  opencli antigravity send "Write a python script to fetch HN top stories"
38
30
  # wait ~10-15 seconds for output to render
39
31
  opencli antigravity extract-code > hn_fetcher.py
@@ -42,6 +34,5 @@ opencli antigravity extract-code > hn_fetcher.py
42
34
  ### Reading Real-time Logs
43
35
  Agents can run long-running streaming watch instances:
44
36
  \`\`\`bash
45
- export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
46
37
  opencli antigravity watch
47
38
  \`\`\`
@@ -6,13 +6,14 @@
6
6
  * and returns it in Anthropic format.
7
7
  *
8
8
  * Usage:
9
- * OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve --port 8082
9
+ * opencli antigravity serve --port 8082
10
10
  * ANTHROPIC_BASE_URL=http://localhost:8082 claude
11
11
  */
12
12
 
13
13
  import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
14
14
  import { CDPBridge } from '../../browser/cdp.js';
15
15
  import type { IPage } from '../../types.js';
16
+ import { resolveElectronEndpoint } from '../../launcher.js';
16
17
  import { EXIT_CODES, getErrorMessage } from '../../errors.js';
17
18
 
18
19
  // ─── Types ───────────────────────────────────────────────────────────
@@ -436,13 +437,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
436
437
  }
437
438
  }
438
439
 
439
- const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
440
- if (!endpoint) {
441
- throw new Error(
442
- 'OPENCLI_CDP_ENDPOINT is not set.\n' +
443
- 'Usage: OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve'
444
- );
445
- }
440
+ const endpoint = await resolveElectronEndpoint('antigravity');
446
441
 
447
442
  // Note: Antigravity chat panel lives inside editor windows, not in Launchpad.
448
443
  // If multiple editor windows are open, set OPENCLI_CDP_TARGET to the window title.
@@ -461,7 +456,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
461
456
  console.error(`[serve] Connecting via CDP (target pattern: "${process.env.OPENCLI_CDP_TARGET}")...`);
462
457
  cdp = new CDPBridge();
463
458
  try {
464
- page = await cdp.connect({ timeout: 15_000 });
459
+ page = await cdp.connect({ timeout: 15_000, cdpEndpoint: endpoint });
465
460
  } catch (err: unknown) {
466
461
  cdp = null;
467
462
  const errMsg = getErrorMessage(err);
@@ -471,7 +466,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
471
466
  isRefused
472
467
  ? `Cannot connect to Antigravity at ${endpoint}.\n` +
473
468
  ' 1. Make sure Antigravity is running\n' +
474
- ' 2. Launch with: --remote-debugging-port=9224'
469
+ ' 2. Launch with: --remote-debugging-port=9234'
475
470
  : `CDP connection failed: ${errMsg}`
476
471
  );
477
472
  }
@@ -0,0 +1,76 @@
1
+ import { AuthRequiredError, EmptyResultError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+
4
+ /**
5
+ * band bands — List all Bands you belong to.
6
+ *
7
+ * Band.us renders the full band list in the left sidebar of the home page for
8
+ * logged-in users, so we can extract everything we need from the DOM without
9
+ * XHR interception or any secondary navigation.
10
+ *
11
+ * Each sidebar item is an <a href="/band/{band_no}/..."> link whose text and
12
+ * data attributes carry the band name and member count.
13
+ */
14
+ cli({
15
+ site: 'band',
16
+ name: 'bands',
17
+ description: 'List all Bands you belong to',
18
+ domain: 'www.band.us',
19
+ strategy: Strategy.COOKIE,
20
+ browser: true,
21
+ args: [],
22
+ columns: ['band_no', 'name', 'members'],
23
+
24
+ func: async (page, _kwargs) => {
25
+ const cookies = await page.getCookies({ domain: 'band.us' });
26
+ const isLoggedIn = cookies.some(c => c.name === 'band_session');
27
+ if (!isLoggedIn) throw new AuthRequiredError('band.us', 'Not logged in to Band');
28
+
29
+ // Extract the band list from the sidebar. Poll until at least one band card
30
+ // appears (React hydration may take a moment after navigation).
31
+ // Sidebar band cards use class "bandCover _link" with hrefs like /band/{id}/post.
32
+ const bands: { band_no: number; name: string; members: number }[] = await page.evaluate(`
33
+ (async () => {
34
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
35
+
36
+ // Wait up to 9 s for sidebar band cards to render.
37
+ for (let i = 0; i < 30; i++) {
38
+ if (document.querySelector('a.bandCover._link')) break;
39
+ await sleep(300);
40
+ }
41
+
42
+ const norm = s => (s || '').replace(/\\s+/g, ' ').trim();
43
+ const seen = new Set();
44
+ const results = [];
45
+
46
+ for (const a of Array.from(document.querySelectorAll('a.bandCover._link'))) {
47
+ // Extract band_no from href: /band/{id} or /band/{id}/post only.
48
+ const m = (a.getAttribute('href') || '').match(/^\\/band\\/(\\d+)(?:\\/post)?\\/?$/);
49
+ if (!m) continue;
50
+ const bandNo = Number(m[1]);
51
+ if (seen.has(bandNo)) continue;
52
+ seen.add(bandNo);
53
+
54
+ // Band name lives in p.uriText inside div.bandName.
55
+ const nameEl = a.querySelector('p.uriText');
56
+ const name = nameEl ? norm(nameEl.textContent) : '';
57
+ if (!name) continue;
58
+
59
+ // Member count is the <em> inside span.member.
60
+ const memberEl = a.querySelector('span.member em');
61
+ const members = memberEl ? parseInt((memberEl.textContent || '').replace(/[^0-9]/g, ''), 10) || 0 : 0;
62
+
63
+ results.push({ band_no: bandNo, name, members });
64
+ }
65
+
66
+ return results;
67
+ })()
68
+ `);
69
+
70
+ if (!bands || bands.length === 0) {
71
+ throw new EmptyResultError('band bands', 'No bands found in sidebar — are you logged in?');
72
+ }
73
+
74
+ return bands;
75
+ },
76
+ });
@@ -0,0 +1,134 @@
1
+ import { AuthRequiredError, EmptyResultError, SelectorError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+
4
+ /**
5
+ * band mentions — Show Band notifications where you were @mentioned.
6
+ *
7
+ * Band.us signs every API request with a per-request HMAC (`md` header) generated
8
+ * by its own JavaScript, so we cannot replicate it externally. Instead we use
9
+ * Strategy.INTERCEPT: install an XHR interceptor, open the notification panel by
10
+ * clicking the bell to trigger the get_news XHR call, then apply client-side
11
+ * filtering to extract notifications matching the requested filter/unread options.
12
+ */
13
+ cli({
14
+ site: 'band',
15
+ name: 'mentions',
16
+ description: 'Show Band notifications where you are @mentioned',
17
+ domain: 'www.band.us',
18
+ strategy: Strategy.INTERCEPT,
19
+ browser: true,
20
+ args: [
21
+ {
22
+ name: 'filter',
23
+ default: 'mentioned',
24
+ choices: ['mentioned', 'all', 'post', 'comment'],
25
+ help: 'Filter: mentioned (default) | all | post | comment',
26
+ },
27
+ { name: 'limit', type: 'int', default: 20, help: 'Max results' },
28
+ { name: 'unread', type: 'bool', default: false, help: 'Show only unread notifications' },
29
+ ],
30
+ columns: ['time', 'band', 'type', 'from', 'text', 'url'],
31
+
32
+ func: async (page, kwargs) => {
33
+ const filter = kwargs.filter as string;
34
+ const limit = kwargs.limit as number;
35
+ const unreadOnly = kwargs.unread as boolean;
36
+
37
+ // Navigate with a timestamp param to force a fresh page load each run.
38
+ // Without this, same-URL navigation may skip the reload (preserving the JS context
39
+ // and leaving the notification panel open from a previous run).
40
+ await page.goto(`https://www.band.us/?_=${Date.now()}`);
41
+
42
+ const cookies = await page.getCookies({ domain: 'band.us' });
43
+ const isLoggedIn = cookies.some(c => c.name === 'band_session');
44
+ if (!isLoggedIn) throw new AuthRequiredError('band.us', 'Not logged in to Band');
45
+
46
+ // Install XHR interceptor before any clicks so all get_news responses are captured.
47
+ await page.installInterceptor('get_news');
48
+
49
+ // Wait for the bell button to appear (React hydration) instead of a fixed sleep.
50
+ let bellReady = false;
51
+ for (let i = 0; i < 20; i++) {
52
+ const exists = await page.evaluate(`() => !!document.querySelector('button._btnWidgetIcon')`);
53
+ if (exists) { bellReady = true; break; }
54
+ await page.wait(0.5);
55
+ }
56
+ if (!bellReady) {
57
+ throw new SelectorError('button._btnWidgetIcon', 'Notification bell not found. The Band.us UI may have changed.');
58
+ }
59
+
60
+ // Poll until a capture containing result_data.news arrives, up to maxSecs seconds.
61
+ // getInterceptedRequests() clears the array on each call, so captures are accumulated
62
+ // locally. The interceptor pattern 'get_news' also matches 'get_news_count' responses
63
+ // which don't have result_data.news — keep polling until the real news response arrives.
64
+ const waitForOneCapture = async (maxSecs = 8): Promise<any[]> => {
65
+ const captures: any[] = [];
66
+ for (let i = 0; i < maxSecs * 2; i++) {
67
+ await page.wait(0.5); // 0.5 seconds per iteration (page.wait takes seconds)
68
+ const reqs = await page.getInterceptedRequests();
69
+ if (reqs.length > 0) {
70
+ captures.push(...reqs);
71
+ if (captures.some((r: any) => Array.isArray(r?.result_data?.news))) return captures;
72
+ }
73
+ }
74
+ return captures;
75
+ };
76
+
77
+ // Click the bell. Guard against the element disappearing between the readiness
78
+ // check and the click (e.g. due to a React re-render) to surface a clear error.
79
+ const bellClicked = await page.evaluate(`() => {
80
+ const el = document.querySelector('button._btnWidgetIcon');
81
+ if (!el) return false;
82
+ el.click();
83
+ return true;
84
+ }`);
85
+ if (!bellClicked) {
86
+ throw new SelectorError('button._btnWidgetIcon', 'Notification bell disappeared before click. The Band.us UI may have changed.');
87
+ }
88
+
89
+ const requests = await waitForOneCapture();
90
+
91
+ // Find the get_news response (has result_data.news); get_news_count responses do not.
92
+ const newsReq = requests.find((r: any) => Array.isArray(r?.result_data?.news)) as any;
93
+ if (!newsReq) {
94
+ throw new EmptyResultError('band mentions', 'Failed to capture get_news response from Band.us. Try running the command again.');
95
+ }
96
+ let items: any[] = newsReq.result_data.news ?? [];
97
+
98
+ if (items.length === 0) {
99
+ throw new EmptyResultError('band mentions', 'No notifications found');
100
+ }
101
+
102
+ // Apply filters client-side from the full notification list.
103
+ if (unreadOnly) {
104
+ items = items.filter((n: any) => n.is_new === true);
105
+ }
106
+ if (filter === 'mentioned') {
107
+ // 'filters' is Band's server-side tag array; 'referred' means you were @mentioned.
108
+ items = items.filter((n: any) => n.filters?.includes('referred'));
109
+ } else if (filter === 'post') {
110
+ items = items.filter((n: any) => n.category === 'post');
111
+ } else if (filter === 'comment') {
112
+ items = items.filter((n: any) => n.category === 'comment');
113
+ }
114
+
115
+ // Band markup tags (<band:mention uid="...">, <band:sticker>, etc.) appear in
116
+ // notification text; strip them to get plain readable content.
117
+ const stripBandTags = (s: string) => s.replace(/<\/?band:[^>]+>/g, '');
118
+
119
+ return items.slice(0, limit).map((n: any) => {
120
+ const ts = n.created_at ? new Date(n.created_at) : null;
121
+ return {
122
+ time: ts
123
+ ? ts.toLocaleString('ja-JP', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
124
+ : '',
125
+ band: n.band?.name ?? '',
126
+ // 'filters' is Band's server-side tag array; 'referred' means you were @mentioned.
127
+ type: n.filters?.includes('referred') ? '@mention' : n.category ?? '',
128
+ from: n.actor?.name ?? '',
129
+ text: stripBandTags(n.subtext ?? '').slice(0, 100),
130
+ url: n.action?.pc ?? '',
131
+ };
132
+ });
133
+ },
134
+ });
@@ -0,0 +1,187 @@
1
+ import { AuthRequiredError, EmptyResultError } from '../../errors.js';
2
+ import { formatCookieHeader } from '../../download/index.js';
3
+ import { downloadMedia } from '../../download/media-download.js';
4
+ import { cli, Strategy } from '../../registry.js';
5
+
6
+ /**
7
+ * band post — Export full content of a Band post: body, comments, and optional photo download.
8
+ *
9
+ * Navigates directly to the post URL and extracts everything from the DOM.
10
+ * No XHR interception needed — Band renders the full post for logged-in users.
11
+ *
12
+ * Output rows:
13
+ * type=post → the post itself (author, date, body text)
14
+ * type=comment → top-level comment
15
+ * type=reply → reply to a comment (nested under its parent)
16
+ *
17
+ * Photo thumbnail URLs carry a ?type=sNNN suffix; stripping it yields full-res.
18
+ */
19
+ cli({
20
+ site: 'band',
21
+ name: 'post',
22
+ description: 'Export full content of a post including comments',
23
+ domain: 'www.band.us',
24
+ strategy: Strategy.COOKIE,
25
+ navigateBefore: false,
26
+ browser: true,
27
+ args: [
28
+ { name: 'band_no', positional: true, required: true, type: 'int', help: 'Band number' },
29
+ { name: 'post_no', positional: true, required: true, type: 'int', help: 'Post number' },
30
+ { name: 'output', type: 'str', default: '', help: 'Directory to save attached photos' },
31
+ { name: 'comments', type: 'bool', default: true, help: 'Include comments (default: true)' },
32
+ ],
33
+ columns: ['type', 'author', 'date', 'text'],
34
+
35
+ func: async (page, kwargs) => {
36
+ const bandNo = Number(kwargs.band_no);
37
+ const postNo = Number(kwargs.post_no);
38
+ const outputDir = kwargs.output as string;
39
+ const withComments = kwargs.comments as boolean;
40
+
41
+ await page.goto(`https://www.band.us/band/${bandNo}/post/${postNo}`);
42
+
43
+ const cookies = await page.getCookies({ domain: 'band.us' });
44
+ const isLoggedIn = cookies.some(c => c.name === 'band_session');
45
+ if (!isLoggedIn) throw new AuthRequiredError('band.us', 'Not logged in to Band');
46
+
47
+ const data: {
48
+ author: string;
49
+ date: string;
50
+ text: string;
51
+ photos: string[];
52
+ comments: { depth: number; author: string; date: string; text: string }[];
53
+ } = await page.evaluate(`
54
+ (async () => {
55
+ const withComments = ${withComments};
56
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
57
+ const norm = s => (s || '').replace(/\\s+/g, ' ').trim();
58
+ // Band embeds <band:mention>, <band:sticker>, etc. in content — strip to plain text.
59
+ const stripTags = s => s.replace(/<\\/?band:[^>]+>/g, '');
60
+
61
+ // Wait up to 9 s for the post content to render (poll for the author link,
62
+ // which appears after React hydration fills the post header).
63
+ for (let i = 0; i < 30; i++) {
64
+ if (document.querySelector('._postWrapper a.text')) break;
65
+ await sleep(300);
66
+ }
67
+
68
+ const postCard = document.querySelector('._postWrapper');
69
+ const commentSection = postCard?.querySelector('.dPostCommentMainView');
70
+
71
+ // Author and date live in the post header, above the comment section.
72
+ // Exclude any matches inside the comment section to avoid picking up comment authors.
73
+ let author = '', date = '';
74
+ for (const el of (postCard?.querySelectorAll('a.text') || [])) {
75
+ if (!commentSection?.contains(el)) { author = norm(el.textContent); break; }
76
+ }
77
+ for (const el of (postCard?.querySelectorAll('time.time') || [])) {
78
+ if (!commentSection?.contains(el)) { date = norm(el.textContent); break; }
79
+ }
80
+
81
+ const bodyEl = postCard?.querySelector('.postText._postText');
82
+ const text = bodyEl ? stripTags(norm(bodyEl.innerText || bodyEl.textContent)) : '';
83
+
84
+ // Photo thumbnails have a ?type=sNNN query param; strip it for full-res URL.
85
+ // Use location.href as base so protocol-relative or relative URLs resolve correctly.
86
+ const photos = Array.from(postCard?.querySelectorAll('img._imgRecentPhoto, img._imgPhoto') || [])
87
+ .map(img => {
88
+ const src = img.getAttribute('src') || '';
89
+ if (!src) return '';
90
+ try { const u = new URL(src, location.href); return u.origin + u.pathname; }
91
+ catch { return ''; }
92
+ })
93
+ .filter(Boolean);
94
+
95
+ if (!withComments) return { author, date, text, photos, comments: [] };
96
+
97
+ // Wait up to 6 s for the comment list container to render.
98
+ // Wait for the container itself (not .cComment) so posts with zero comments
99
+ // don't incur a fixed 6s delay waiting for an element that never appears.
100
+ for (let i = 0; i < 20; i++) {
101
+ if (postCard?.querySelector('.sCommentList._heightDetectAreaForComment')) break;
102
+ await sleep(300);
103
+ }
104
+
105
+ // Recursively collect comments and their replies.
106
+ // Replies live in .sReplyList > .sCommentList, not in ._replyRegion.
107
+ function extractComments(container, depth) {
108
+ const results = [];
109
+ for (const el of container.querySelectorAll(':scope > .cComment')) {
110
+ results.push({
111
+ depth,
112
+ author: norm(el.querySelector('strong.name')?.textContent),
113
+ date: norm(el.querySelector('time.time')?.textContent),
114
+ text: stripTags(norm(el.querySelector('p.txt._commentContent')?.innerText || '')),
115
+ });
116
+ const replyList = el.querySelector('.sReplyList .sCommentList._heightDetectAreaForComment');
117
+ if (replyList) results.push(...extractComments(replyList, depth + 1));
118
+ }
119
+ return results;
120
+ }
121
+
122
+ const commentList = postCard?.querySelector('.sCommentList._heightDetectAreaForComment');
123
+ const comments = commentList ? extractComments(commentList, 0) : [];
124
+
125
+ return { author, date, text, photos, comments };
126
+ })()
127
+ `);
128
+
129
+ if (!data?.text && !data?.comments?.length && !data?.photos?.length) {
130
+ throw new EmptyResultError('band post', 'Post not found or not accessible');
131
+ }
132
+
133
+ const photos: string[] = data.photos ?? [];
134
+
135
+ // Download photos when --output is specified, using the shared downloadMedia utility
136
+ // which handles redirects, timeouts, and stream errors correctly.
137
+ // Pass browser cookies so Band's login-protected photo URLs don't fail with 401/403.
138
+ if (outputDir && photos.length > 0) {
139
+ // Only send Band cookies to Band-hosted URLs; avoid leaking auth cookies to third-party CDNs.
140
+ // Use a global index across both batches so filenames don't collide (photo_1, photo_2, ...).
141
+ const cookieHeader = formatCookieHeader(await page.getCookies({ url: 'https://www.band.us' }));
142
+ const isBandUrl = (u: string) => { try { const h = new URL(u).hostname; return h === 'band.us' || h.endsWith('.band.us'); } catch { return false; } };
143
+ // Derive extension from URL path so downloaded files have correct extensions (e.g. photo_1.jpg).
144
+ const urlExt = (u: string) => { try { return new URL(u).pathname.match(/\.(\w+)$/)?.[1] ?? 'jpg'; } catch { return 'jpg'; } };
145
+ let globalIndex = 1;
146
+ const bandPhotos = photos.filter(isBandUrl);
147
+ const otherPhotos = photos.filter(u => !isBandUrl(u));
148
+ if (bandPhotos.length > 0) {
149
+ await downloadMedia(
150
+ bandPhotos.map(url => ({ type: 'image' as const, url, filename: `photo_${globalIndex++}.${urlExt(url)}` })),
151
+ { output: outputDir, verbose: false, cookies: cookieHeader },
152
+ );
153
+ }
154
+ if (otherPhotos.length > 0) {
155
+ await downloadMedia(
156
+ otherPhotos.map(url => ({ type: 'image' as const, url, filename: `photo_${globalIndex++}.${urlExt(url)}` })),
157
+ { output: outputDir, verbose: false },
158
+ );
159
+ }
160
+ }
161
+
162
+ const rows: Record<string, string>[] = [];
163
+
164
+ // Post row — append photo URLs inline when not downloading to disk.
165
+ rows.push({
166
+ type: 'post',
167
+ author: data.author ?? '',
168
+ date: data.date ?? '',
169
+ text: [
170
+ data.text ?? '',
171
+ ...(outputDir ? [] : photos.map((u, i) => `[photo${i + 1}] ${u}`)),
172
+ ].filter(Boolean).join('\n'),
173
+ });
174
+
175
+ // Comment rows — depth=0 → type 'comment', depth≥1 → type 'reply'.
176
+ for (const c of data.comments ?? []) {
177
+ rows.push({
178
+ type: c.depth === 0 ? 'comment' : 'reply',
179
+ author: c.author ?? '',
180
+ date: c.date ?? '',
181
+ text: c.depth > 0 ? ' '.repeat(c.depth) + '└ ' + (c.text ?? '') : (c.text ?? ''),
182
+ });
183
+ }
184
+
185
+ return rows;
186
+ },
187
+ });