@jackwener/opencli 1.5.6 → 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 (334) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +4 -2
  3. package/README.zh-CN.md +4 -1
  4. package/SKILL.md +879 -0
  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 +7 -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.js +22 -2
  13. package/dist/browser/page.test.d.ts +1 -0
  14. package/dist/browser/page.test.js +44 -0
  15. package/dist/browser/stealth.js +198 -0
  16. package/dist/browser/stealth.test.d.ts +1 -0
  17. package/dist/browser/stealth.test.js +134 -0
  18. package/dist/browser.test.js +1 -1
  19. package/dist/build-manifest.d.ts +1 -0
  20. package/dist/build-manifest.js +5 -1
  21. package/dist/build-manifest.test.js +2 -0
  22. package/dist/cli-manifest.json +544 -137
  23. package/dist/cli.js +20 -3
  24. package/dist/clis/antigravity/serve.d.ts +1 -1
  25. package/dist/clis/antigravity/serve.js +5 -8
  26. package/dist/clis/bilibili/subtitle.js +4 -0
  27. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  28. package/dist/clis/bilibili/subtitle.test.js +48 -0
  29. package/dist/clis/chatwise/ask.js +0 -2
  30. package/dist/clis/chatwise/export.js +0 -2
  31. package/dist/clis/chatwise/history.js +0 -2
  32. package/dist/clis/chatwise/model.js +0 -2
  33. package/dist/clis/chatwise/new.js +1 -2
  34. package/dist/clis/chatwise/read.js +0 -2
  35. package/dist/clis/chatwise/screenshot.js +1 -2
  36. package/dist/clis/chatwise/send.js +0 -2
  37. package/dist/clis/chatwise/status.js +1 -2
  38. package/dist/clis/ctrip/search.d.ts +13 -0
  39. package/dist/clis/ctrip/search.js +73 -48
  40. package/dist/clis/ctrip/search.test.d.ts +1 -0
  41. package/dist/clis/ctrip/search.test.js +64 -0
  42. package/dist/clis/douyin/_shared/sts2.js +8 -2
  43. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  44. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  45. package/dist/clis/douyin/activities.js +4 -2
  46. package/dist/clis/douyin/activities.test.js +34 -1
  47. package/dist/clis/douyin/collections.js +1 -1
  48. package/dist/clis/douyin/collections.test.js +24 -2
  49. package/dist/clis/douyin/draft.d.ts +8 -11
  50. package/dist/clis/douyin/draft.js +302 -185
  51. package/dist/clis/douyin/draft.test.d.ts +1 -1
  52. package/dist/clis/douyin/draft.test.js +357 -2
  53. package/dist/clis/douyin/hashtag.js +9 -2
  54. package/dist/clis/douyin/hashtag.test.js +35 -2
  55. package/dist/clis/douyin/profile.js +1 -1
  56. package/dist/clis/douyin/profile.test.js +36 -1
  57. package/dist/clis/douyin/videos.js +22 -5
  58. package/dist/clis/douyin/videos.test.js +45 -2
  59. package/dist/clis/facebook/search.test.d.ts +5 -0
  60. package/dist/clis/facebook/search.test.js +60 -0
  61. package/dist/clis/facebook/search.yaml +4 -3
  62. package/dist/clis/instagram/download.d.ts +16 -0
  63. package/dist/clis/instagram/download.js +225 -0
  64. package/dist/clis/instagram/download.test.d.ts +1 -0
  65. package/dist/clis/instagram/download.test.js +118 -0
  66. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  67. package/dist/clis/notebooklm/bind-current.js +29 -0
  68. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  69. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  70. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  71. package/dist/clis/notebooklm/binding.test.js +44 -0
  72. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  73. package/dist/clis/notebooklm/compat.test.js +16 -0
  74. package/dist/clis/notebooklm/current.d.ts +1 -0
  75. package/dist/clis/notebooklm/current.js +28 -0
  76. package/dist/clis/notebooklm/get.d.ts +1 -0
  77. package/dist/clis/notebooklm/get.js +37 -0
  78. package/dist/clis/notebooklm/history.d.ts +1 -0
  79. package/dist/clis/notebooklm/history.js +25 -0
  80. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  81. package/dist/clis/notebooklm/history.test.js +58 -0
  82. package/dist/clis/notebooklm/list.d.ts +1 -0
  83. package/dist/clis/notebooklm/list.js +35 -0
  84. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  85. package/dist/clis/notebooklm/note-list.js +28 -0
  86. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  87. package/dist/clis/notebooklm/note-list.test.js +56 -0
  88. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  89. package/dist/clis/notebooklm/notes-get.js +47 -0
  90. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  91. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  92. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  93. package/dist/clis/notebooklm/rpc.js +189 -0
  94. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  95. package/dist/clis/notebooklm/rpc.test.js +105 -0
  96. package/dist/clis/notebooklm/shared.d.ts +87 -0
  97. package/dist/clis/notebooklm/shared.js +3 -0
  98. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  99. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  100. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  101. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  102. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  103. package/dist/clis/notebooklm/source-get.js +40 -0
  104. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  105. package/dist/clis/notebooklm/source-get.test.js +84 -0
  106. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  107. package/dist/clis/notebooklm/source-guide.js +44 -0
  108. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  109. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  110. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  111. package/dist/clis/notebooklm/source-list.js +30 -0
  112. package/dist/clis/notebooklm/status.d.ts +1 -0
  113. package/dist/clis/notebooklm/status.js +31 -0
  114. package/dist/clis/notebooklm/summary.d.ts +1 -0
  115. package/dist/clis/notebooklm/summary.js +30 -0
  116. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  117. package/dist/clis/notebooklm/summary.test.js +78 -0
  118. package/dist/clis/notebooklm/utils.d.ts +37 -0
  119. package/dist/clis/notebooklm/utils.js +739 -0
  120. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  121. package/dist/clis/notebooklm/utils.test.js +390 -0
  122. package/dist/clis/substack/utils.d.ts +4 -0
  123. package/dist/clis/substack/utils.js +8 -2
  124. package/dist/clis/substack/utils.test.d.ts +1 -0
  125. package/dist/clis/substack/utils.test.js +46 -0
  126. package/dist/clis/v2ex/hot.yaml +4 -1
  127. package/dist/clis/v2ex/latest.yaml +4 -1
  128. package/dist/clis/v2ex/topic.yaml +6 -1
  129. package/dist/clis/weixin/download.d.ts +9 -0
  130. package/dist/clis/weixin/download.js +76 -6
  131. package/dist/clis/weread/book.js +108 -2
  132. package/dist/clis/weread/commands.test.js +262 -152
  133. package/dist/clis/weread/utils.d.ts +10 -0
  134. package/dist/clis/weread/utils.js +27 -7
  135. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  136. package/dist/clis/xiaohongshu/comments.js +76 -17
  137. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  138. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  139. package/dist/clis/xiaohongshu/download.js +83 -22
  140. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  141. package/dist/clis/xiaohongshu/download.test.js +75 -0
  142. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  143. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  144. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  145. package/dist/clis/xiaohongshu/note.js +76 -0
  146. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  147. package/dist/clis/xiaohongshu/note.test.js +136 -0
  148. package/dist/clis/xiaohongshu/search.js +9 -0
  149. package/dist/clis/xiaohongshu/search.test.js +10 -4
  150. package/dist/clis/youtube/search.js +57 -17
  151. package/dist/clis/zhihu/question.js +19 -17
  152. package/dist/clis/zhihu/question.test.d.ts +1 -0
  153. package/dist/clis/zhihu/question.test.js +54 -0
  154. package/dist/commanderAdapter.js +9 -0
  155. package/dist/commanderAdapter.test.js +25 -0
  156. package/dist/commands/daemon.d.ts +9 -0
  157. package/dist/commands/daemon.js +124 -0
  158. package/dist/commands/daemon.test.d.ts +1 -0
  159. package/dist/commands/daemon.test.js +185 -0
  160. package/dist/completion.js +3 -1
  161. package/dist/constants.d.ts +2 -0
  162. package/dist/constants.js +2 -0
  163. package/dist/daemon.d.ts +1 -1
  164. package/dist/daemon.js +25 -14
  165. package/dist/daemon.test.d.ts +1 -0
  166. package/dist/daemon.test.js +65 -0
  167. package/dist/discovery.d.ts +9 -0
  168. package/dist/discovery.js +47 -2
  169. package/dist/electron-apps.d.ts +29 -0
  170. package/dist/electron-apps.js +65 -0
  171. package/dist/electron-apps.test.d.ts +1 -0
  172. package/dist/electron-apps.test.js +43 -0
  173. package/dist/engine.test.js +41 -9
  174. package/dist/execution.js +20 -16
  175. package/dist/idle-manager.d.ts +19 -0
  176. package/dist/idle-manager.js +54 -0
  177. package/dist/launcher.d.ts +36 -0
  178. package/dist/launcher.js +152 -0
  179. package/dist/launcher.test.d.ts +1 -0
  180. package/dist/launcher.test.js +57 -0
  181. package/dist/main.js +3 -3
  182. package/dist/registry.d.ts +1 -0
  183. package/dist/registry.js +31 -3
  184. package/dist/registry.test.js +13 -0
  185. package/dist/runtime.d.ts +5 -3
  186. package/dist/runtime.js +12 -5
  187. package/dist/serialization.d.ts +1 -0
  188. package/dist/serialization.js +3 -0
  189. package/dist/serialization.test.js +17 -1
  190. package/dist/tui.d.ts +7 -0
  191. package/dist/tui.js +52 -0
  192. package/dist/tui.test.d.ts +1 -0
  193. package/dist/tui.test.js +19 -0
  194. package/dist/weixin-download.test.js +14 -0
  195. package/docs/.vitepress/config.mts +1 -0
  196. package/docs/adapters/browser/notebooklm.md +69 -0
  197. package/docs/adapters/browser/xiaohongshu.md +19 -10
  198. package/docs/adapters/index.md +67 -66
  199. package/docs/guide/browser-bridge.md +12 -0
  200. package/docs/guide/troubleshooting.md +9 -4
  201. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  202. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  203. package/docs/zh/guide/browser-bridge.md +12 -0
  204. package/extension/dist/background.js +794 -513
  205. package/extension/src/background.test.ts +202 -2
  206. package/extension/src/background.ts +174 -10
  207. package/extension/src/cdp.ts +12 -0
  208. package/extension/src/protocol.ts +7 -5
  209. package/package.json +1 -1
  210. package/src/browser/cdp.ts +24 -17
  211. package/src/browser/daemon-client.ts +7 -1
  212. package/src/browser/dom-helpers.test.ts +15 -1
  213. package/src/browser/dom-helpers.ts +1 -0
  214. package/src/browser/mcp.ts +18 -13
  215. package/src/browser/page.test.ts +58 -0
  216. package/src/browser/page.ts +18 -2
  217. package/src/browser/stealth.test.ts +153 -0
  218. package/src/browser/stealth.ts +198 -0
  219. package/src/browser.test.ts +1 -1
  220. package/src/build-manifest.test.ts +2 -0
  221. package/src/build-manifest.ts +6 -1
  222. package/src/cli.ts +21 -3
  223. package/src/clis/antigravity/SKILL.md +3 -12
  224. package/src/clis/antigravity/serve.ts +5 -10
  225. package/src/clis/bilibili/subtitle.test.ts +60 -0
  226. package/src/clis/bilibili/subtitle.ts +4 -0
  227. package/src/clis/chatwise/ask.ts +0 -2
  228. package/src/clis/chatwise/export.ts +0 -2
  229. package/src/clis/chatwise/history.ts +0 -2
  230. package/src/clis/chatwise/model.ts +0 -2
  231. package/src/clis/chatwise/new.ts +1 -2
  232. package/src/clis/chatwise/read.ts +0 -2
  233. package/src/clis/chatwise/screenshot.ts +1 -2
  234. package/src/clis/chatwise/send.ts +0 -2
  235. package/src/clis/chatwise/status.ts +1 -2
  236. package/src/clis/ctrip/search.test.ts +73 -0
  237. package/src/clis/ctrip/search.ts +97 -47
  238. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  239. package/src/clis/douyin/_shared/sts2.ts +11 -3
  240. package/src/clis/douyin/activities.test.ts +41 -1
  241. package/src/clis/douyin/activities.ts +12 -3
  242. package/src/clis/douyin/collections.test.ts +35 -2
  243. package/src/clis/douyin/collections.ts +1 -1
  244. package/src/clis/douyin/draft.test.ts +444 -2
  245. package/src/clis/douyin/draft.ts +382 -218
  246. package/src/clis/douyin/hashtag.test.ts +42 -2
  247. package/src/clis/douyin/hashtag.ts +11 -3
  248. package/src/clis/douyin/profile.test.ts +43 -1
  249. package/src/clis/douyin/profile.ts +9 -2
  250. package/src/clis/douyin/videos.test.ts +52 -2
  251. package/src/clis/douyin/videos.ts +49 -15
  252. package/src/clis/facebook/search.test.ts +70 -0
  253. package/src/clis/facebook/search.yaml +4 -3
  254. package/src/clis/instagram/download.test.ts +159 -0
  255. package/src/clis/instagram/download.ts +286 -0
  256. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  257. package/src/clis/notebooklm/bind-current.ts +36 -0
  258. package/src/clis/notebooklm/binding.test.ts +53 -0
  259. package/src/clis/notebooklm/compat.test.ts +19 -0
  260. package/src/clis/notebooklm/current.ts +38 -0
  261. package/src/clis/notebooklm/get.ts +53 -0
  262. package/src/clis/notebooklm/history.test.ts +70 -0
  263. package/src/clis/notebooklm/history.ts +36 -0
  264. package/src/clis/notebooklm/list.ts +40 -0
  265. package/src/clis/notebooklm/note-list.test.ts +64 -0
  266. package/src/clis/notebooklm/note-list.ts +42 -0
  267. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  268. package/src/clis/notebooklm/notes-get.ts +67 -0
  269. package/src/clis/notebooklm/rpc.test.ts +126 -0
  270. package/src/clis/notebooklm/rpc.ts +286 -0
  271. package/src/clis/notebooklm/shared.ts +98 -0
  272. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  273. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  274. package/src/clis/notebooklm/source-get.test.ts +100 -0
  275. package/src/clis/notebooklm/source-get.ts +60 -0
  276. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  277. package/src/clis/notebooklm/source-guide.ts +69 -0
  278. package/src/clis/notebooklm/source-list.ts +45 -0
  279. package/src/clis/notebooklm/status.ts +34 -0
  280. package/src/clis/notebooklm/summary.test.ts +94 -0
  281. package/src/clis/notebooklm/summary.ts +45 -0
  282. package/src/clis/notebooklm/utils.test.ts +446 -0
  283. package/src/clis/notebooklm/utils.ts +893 -0
  284. package/src/clis/substack/utils.test.ts +54 -0
  285. package/src/clis/substack/utils.ts +10 -2
  286. package/src/clis/v2ex/hot.yaml +4 -1
  287. package/src/clis/v2ex/latest.yaml +4 -1
  288. package/src/clis/v2ex/topic.yaml +6 -1
  289. package/src/clis/weixin/download.ts +95 -6
  290. package/src/clis/weread/book.ts +142 -2
  291. package/src/clis/weread/commands.test.ts +314 -154
  292. package/src/clis/weread/utils.ts +33 -4
  293. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  294. package/src/clis/xiaohongshu/comments.ts +76 -17
  295. package/src/clis/xiaohongshu/download.test.ts +96 -0
  296. package/src/clis/xiaohongshu/download.ts +83 -22
  297. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  298. package/src/clis/xiaohongshu/note.test.ts +164 -0
  299. package/src/clis/xiaohongshu/note.ts +86 -0
  300. package/src/clis/xiaohongshu/search.test.ts +11 -4
  301. package/src/clis/xiaohongshu/search.ts +13 -0
  302. package/src/clis/youtube/search.ts +57 -17
  303. package/src/clis/zhihu/question.test.ts +71 -0
  304. package/src/clis/zhihu/question.ts +27 -15
  305. package/src/commanderAdapter.test.ts +30 -0
  306. package/src/commanderAdapter.ts +7 -0
  307. package/src/commands/daemon.test.ts +238 -0
  308. package/src/commands/daemon.ts +135 -0
  309. package/src/completion.ts +2 -1
  310. package/src/constants.ts +3 -0
  311. package/src/daemon.test.ts +88 -0
  312. package/src/daemon.ts +26 -14
  313. package/src/discovery.ts +52 -2
  314. package/src/electron-apps.test.ts +50 -0
  315. package/src/electron-apps.ts +89 -0
  316. package/src/engine.test.ts +45 -9
  317. package/src/execution.ts +24 -19
  318. package/src/idle-manager.ts +60 -0
  319. package/src/launcher.test.ts +67 -0
  320. package/src/launcher.ts +185 -0
  321. package/src/main.ts +3 -2
  322. package/src/registry.test.ts +15 -0
  323. package/src/registry.ts +32 -3
  324. package/src/runtime.ts +13 -7
  325. package/src/serialization.test.ts +19 -1
  326. package/src/serialization.ts +2 -0
  327. package/src/tui.test.ts +23 -0
  328. package/src/tui.ts +65 -0
  329. package/src/weixin-download.test.ts +27 -0
  330. package/tests/e2e/browser-public-extended.test.ts +6 -2
  331. package/chatwise-opencli.ps1 +0 -82
  332. package/dist/clis/chatwise/shared.d.ts +0 -2
  333. package/dist/clis/chatwise/shared.js +0 -6
  334. package/src/clis/chatwise/shared.ts +0 -8
@@ -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,60 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { AuthRequiredError, EmptyResultError } from '../../errors.js';
4
+
5
+ const { mockApiGet } = vi.hoisted(() => ({
6
+ mockApiGet: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('./utils.js', () => ({
10
+ apiGet: mockApiGet,
11
+ }));
12
+
13
+ import { getRegistry } from '../../registry.js';
14
+ import './subtitle.js';
15
+
16
+ describe('bilibili subtitle', () => {
17
+ const command = getRegistry().get('bilibili/subtitle');
18
+ const page = {
19
+ goto: vi.fn().mockResolvedValue(undefined),
20
+ evaluate: vi.fn(),
21
+ } as any;
22
+
23
+ beforeEach(() => {
24
+ mockApiGet.mockReset();
25
+ page.goto.mockClear();
26
+ page.evaluate.mockReset();
27
+ });
28
+
29
+ it('throws AuthRequiredError when bilibili hides subtitles behind login', async () => {
30
+ page.evaluate.mockResolvedValueOnce(123456);
31
+ mockApiGet.mockResolvedValueOnce({
32
+ code: 0,
33
+ data: {
34
+ need_login_subtitle: true,
35
+ subtitle: {
36
+ subtitles: [],
37
+ },
38
+ },
39
+ });
40
+
41
+ await expect(command!.func!(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toSatisfy((err: Error) =>
42
+ err instanceof AuthRequiredError && /login|登录/i.test(err.message),
43
+ );
44
+ });
45
+
46
+ it('throws EmptyResultError when a video truly has no subtitles', async () => {
47
+ page.evaluate.mockResolvedValueOnce(123456);
48
+ mockApiGet.mockResolvedValueOnce({
49
+ code: 0,
50
+ data: {
51
+ need_login_subtitle: false,
52
+ subtitle: {
53
+ subtitles: [],
54
+ },
55
+ },
56
+ });
57
+
58
+ await expect(command!.func!(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toThrow(EmptyResultError);
59
+ });
60
+ });
@@ -39,8 +39,12 @@ cli({
39
39
  throw new CommandExecutionError(`获取视频播放信息失败: ${payload.message} (${payload.code})`);
40
40
  }
41
41
 
42
+ const needLoginSubtitle = payload.data?.need_login_subtitle === true;
42
43
  const subtitles = payload.data?.subtitle?.subtitles || [];
43
44
  if (subtitles.length === 0) {
45
+ if (needLoginSubtitle) {
46
+ throw new AuthRequiredError('bilibili.com', 'Bilibili subtitles are hidden behind login for this video. Please log in to bilibili.com in Chrome and retry.');
47
+ }
44
48
  throw new EmptyResultError('bilibili subtitle', '此视频没有发现外挂或智能字幕。');
45
49
  }
46
50
 
@@ -1,7 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import { SelectorError } from '../../errors.js';
3
3
  import type { IPage } from '../../types.js';
4
- import { chatwiseRequiredEnv } from './shared.js';
5
4
 
6
5
  export const askCommand = cli({
7
6
  site: 'chatwise',
@@ -10,7 +9,6 @@ export const askCommand = cli({
10
9
  domain: 'localhost',
11
10
  strategy: Strategy.UI,
12
11
  browser: true,
13
- requiredEnv: chatwiseRequiredEnv,
14
12
  args: [
15
13
  { name: 'text', required: true, positional: true, help: 'Prompt to send' },
16
14
  { name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
@@ -1,7 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import { cli, Strategy } from '../../registry.js';
3
3
  import type { IPage } from '../../types.js';
4
- import { chatwiseRequiredEnv } from './shared.js';
5
4
 
6
5
  export const exportCommand = cli({
7
6
  site: 'chatwise',
@@ -10,7 +9,6 @@ export const exportCommand = cli({
10
9
  domain: 'localhost',
11
10
  strategy: Strategy.UI,
12
11
  browser: true,
13
- requiredEnv: chatwiseRequiredEnv,
14
12
  args: [
15
13
  { name: 'output', required: false, help: 'Output file (default: /tmp/chatwise-export.md)' },
16
14
  ],
@@ -1,6 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import type { IPage } from '../../types.js';
3
- import { chatwiseRequiredEnv } from './shared.js';
4
3
 
5
4
  export const historyCommand = cli({
6
5
  site: 'chatwise',
@@ -9,7 +8,6 @@ export const historyCommand = cli({
9
8
  domain: 'localhost',
10
9
  strategy: Strategy.UI,
11
10
  browser: true,
12
- requiredEnv: chatwiseRequiredEnv,
13
11
  args: [],
14
12
  columns: ['Index', 'Title'],
15
13
  func: async (page: IPage) => {
@@ -1,7 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import { SelectorError } from '../../errors.js';
3
3
  import type { IPage } from '../../types.js';
4
- import { chatwiseRequiredEnv } from './shared.js';
5
4
 
6
5
  export const modelCommand = cli({
7
6
  site: 'chatwise',
@@ -10,7 +9,6 @@ export const modelCommand = cli({
10
9
  domain: 'localhost',
11
10
  strategy: Strategy.UI,
12
11
  browser: true,
13
- requiredEnv: chatwiseRequiredEnv,
14
12
  args: [
15
13
  { name: 'model-name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
16
14
  ],
@@ -1,4 +1,3 @@
1
1
  import { makeNewCommand } from '../_shared/desktop-commands.js';
2
- import { chatwiseRequiredEnv } from './shared.js';
3
2
 
4
- export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation', { requiredEnv: chatwiseRequiredEnv });
3
+ export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation');
@@ -1,6 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import type { IPage } from '../../types.js';
3
- import { chatwiseRequiredEnv } from './shared.js';
4
3
 
5
4
  export const readCommand = cli({
6
5
  site: 'chatwise',
@@ -9,7 +8,6 @@ export const readCommand = cli({
9
8
  domain: 'localhost',
10
9
  strategy: Strategy.UI,
11
10
  browser: true,
12
- requiredEnv: chatwiseRequiredEnv,
13
11
  args: [],
14
12
  columns: ['Content'],
15
13
  func: async (page: IPage) => {
@@ -1,4 +1,3 @@
1
1
  import { makeScreenshotCommand } from '../_shared/desktop-commands.js';
2
- import { chatwiseRequiredEnv } from './shared.js';
3
2
 
4
- export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise', { requiredEnv: chatwiseRequiredEnv });
3
+ export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise');
@@ -1,7 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import { SelectorError } from '../../errors.js';
3
3
  import type { IPage } from '../../types.js';
4
- import { chatwiseRequiredEnv } from './shared.js';
5
4
 
6
5
  export const sendCommand = cli({
7
6
  site: 'chatwise',
@@ -10,7 +9,6 @@ export const sendCommand = cli({
10
9
  domain: 'localhost',
11
10
  strategy: Strategy.UI,
12
11
  browser: true,
13
- requiredEnv: chatwiseRequiredEnv,
14
12
  args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
15
13
  columns: ['Status', 'InjectedText'],
16
14
  func: async (page: IPage, kwargs: any) => {
@@ -1,4 +1,3 @@
1
1
  import { makeStatusCommand } from '../_shared/desktop-commands.js';
2
- import { chatwiseRequiredEnv } from './shared.js';
3
2
 
4
- export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop', { requiredEnv: chatwiseRequiredEnv });
3
+ export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop');
@@ -0,0 +1,73 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import './search.js';
4
+
5
+ describe('ctrip search', () => {
6
+ const command = getRegistry().get('ctrip/search');
7
+
8
+ beforeEach(() => {
9
+ vi.unstubAllGlobals();
10
+ });
11
+
12
+ it('maps live endpoint results into ranked rows', async () => {
13
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
14
+ Response: {
15
+ searchResults: [
16
+ {
17
+ displayName: '苏州, 江苏, 中国',
18
+ displayType: '城市',
19
+ commentScore: 0,
20
+ price: '',
21
+ },
22
+ {
23
+ word: '姑苏区',
24
+ type: '行政区',
25
+ cStar: 4.8,
26
+ minPrice: 320,
27
+ },
28
+ ],
29
+ },
30
+ }), { status: 200 })));
31
+
32
+ const result = await command!.func!(null as any, { query: '苏州', limit: 3 });
33
+ expect(result).toEqual([
34
+ {
35
+ rank: 1,
36
+ name: '苏州, 江苏, 中国',
37
+ type: '城市',
38
+ score: 0,
39
+ price: '',
40
+ url: '',
41
+ },
42
+ {
43
+ rank: 2,
44
+ name: '姑苏区',
45
+ type: '行政区',
46
+ score: 4.8,
47
+ price: 320,
48
+ url: '',
49
+ },
50
+ ]);
51
+ });
52
+
53
+ it('rejects empty queries', async () => {
54
+ await expect(command!.func!(null as any, { query: ' ', limit: 3 })).rejects.toThrow('Search keyword cannot be empty');
55
+ });
56
+
57
+ it('surfaces fetch failures as CliError', async () => {
58
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('{}', { status: 503 })));
59
+
60
+ await expect(command!.func!(null as any, { query: '苏州', limit: 3 })).rejects.toMatchObject({
61
+ code: 'FETCH_ERROR',
62
+ message: 'ctrip search failed with status 503',
63
+ });
64
+ });
65
+
66
+ it('surfaces empty results as EmptyResultError', async () => {
67
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
68
+ Response: { searchResults: [] },
69
+ }), { status: 200 })));
70
+
71
+ await expect(command!.func!(null as any, { query: '苏州', limit: 3 })).rejects.toThrow('ctrip search returned no data');
72
+ });
73
+ });
@@ -1,61 +1,111 @@
1
1
  /**
2
- * 携程旅行搜索 — browser cookie, multi-strategy.
2
+ * 携程旅行搜索 — public destination and hotel suggestion lookup.
3
3
  */
4
+ import { ArgumentError, CliError, EmptyResultError } from '../../errors.js';
4
5
  import { cli, Strategy } from '../../registry.js';
5
6
 
7
+ interface CtripSearchResultItem {
8
+ displayName?: string;
9
+ word?: string;
10
+ cityName?: string;
11
+ displayType?: string;
12
+ type?: string;
13
+ commentScore?: number | string;
14
+ cStar?: number | string;
15
+ price?: number | string;
16
+ minPrice?: number | string;
17
+ }
18
+
19
+ function clampLimit(raw: unknown, fallback = 15): number {
20
+ const parsed = Number(raw);
21
+ if (!Number.isFinite(parsed)) return fallback;
22
+ return Math.max(1, Math.min(Math.floor(parsed), 50));
23
+ }
24
+
25
+ function mapSearchResults(results: unknown[], limit: number) {
26
+ return results
27
+ .filter((item): item is CtripSearchResultItem => !!item && typeof item === 'object')
28
+ .slice(0, limit)
29
+ .map((item, index) => ({
30
+ rank: index + 1,
31
+ name: String(item.displayName || item.word || item.cityName || '').replace(/\s+/g, ' ').trim(),
32
+ type: String(item.displayType || item.type || '').replace(/\s+/g, ' ').trim(),
33
+ score: item.commentScore ?? item.cStar ?? '',
34
+ price: item.price ?? item.minPrice ?? '',
35
+ url: '',
36
+ }))
37
+ .filter((item) => item.name);
38
+ }
39
+
6
40
  cli({
7
41
  site: 'ctrip',
8
42
  name: 'search',
9
- description: '携程旅行搜索',
10
- domain: 'www.ctrip.com',
11
- strategy: Strategy.COOKIE,
43
+ description: '搜索携程目的地、景区和酒店联想结果',
44
+ strategy: Strategy.PUBLIC,
45
+ browser: false,
12
46
  args: [
13
47
  { name: 'query', required: true, positional: true, help: 'Search keyword (city or attraction)' },
14
48
  { name: 'limit', type: 'int', default: 15, help: 'Number of results' },
15
49
  ],
16
50
  columns: ['rank', 'name', 'type', 'score', 'price', 'url'],
17
- func: async (page, kwargs) => {
18
- const limit = kwargs.limit || 15;
19
- await page.goto('https://www.ctrip.com');
20
- await page.wait(2);
21
- const data = await page.evaluate(`
22
- (async () => {
23
- const query = '${kwargs.query.replace(/'/g, "\\'")}';
24
- const limit = ${limit};
25
-
26
- // Strategy 1: Suggestion API
27
- try {
28
- const suggestUrl = 'https://m.ctrip.com/restapi/h5api/searchapp/search?action=onekeyali&keyword=' + encodeURIComponent(query);
29
- const resp = await fetch(suggestUrl, {credentials: 'include'});
30
- if (resp.ok) {
31
- const d = await resp.json();
32
- const raw = d.data || d.result || d;
33
- if (raw && typeof raw === 'object') {
34
- // Flatten all result categories
35
- const items = [];
36
- for (const key of Object.keys(raw)) {
37
- const list = Array.isArray(raw[key]) ? raw[key] : [];
38
- for (const item of list) {
39
- if (items.length >= limit) break;
40
- items.push({
41
- rank: items.length + 1,
42
- name: item.word || item.name || item.title || '',
43
- type: item.type || item.tpName || key,
44
- score: item.score || '',
45
- price: item.price || item.minPrice || '',
46
- url: item.url || item.surl || '',
47
- });
48
- }
49
- }
50
- if (items.length > 0) return items;
51
- }
52
- }
53
- } catch(e) {}
54
-
55
- return {error: 'No results for: ' + query};
56
- })()
57
- `);
58
- if (!Array.isArray(data)) return [];
59
- return data;
51
+ func: async (_page, kwargs) => {
52
+ const query = String(kwargs.query || '').trim();
53
+ if (!query) {
54
+ throw new ArgumentError('Search keyword cannot be empty');
55
+ }
56
+
57
+ const limit = clampLimit(kwargs.limit);
58
+ const response = await fetch('https://m.ctrip.com/restapi/soa2/21881/json/gaHotelSearchEngine', {
59
+ method: 'POST',
60
+ headers: {
61
+ 'content-type': 'application/json',
62
+ },
63
+ body: JSON.stringify({
64
+ keyword: query,
65
+ searchType: 'D',
66
+ platform: 'online',
67
+ pageID: '102001',
68
+ head: {
69
+ Locale: 'zh-CN',
70
+ LocaleController: 'zh_cn',
71
+ Currency: 'CNY',
72
+ PageId: '102001',
73
+ clientID: 'opencli-ctrip-search',
74
+ group: 'ctrip',
75
+ Frontend: {
76
+ sessionID: 1,
77
+ pvid: 1,
78
+ },
79
+ HotelExtension: {
80
+ group: 'CTRIP',
81
+ WebpSupport: false,
82
+ },
83
+ },
84
+ }),
85
+ });
86
+
87
+ if (!response.ok) {
88
+ throw new CliError(
89
+ 'FETCH_ERROR',
90
+ `ctrip search failed with status ${response.status}`,
91
+ 'Retry the command or verify ctrip.com is reachable',
92
+ );
93
+ }
94
+
95
+ const payload = await response.json();
96
+ const rawResults = Array.isArray(payload?.Response?.searchResults) ? payload.Response.searchResults : [];
97
+ const results = mapSearchResults(rawResults, limit);
98
+ if (!results.length) {
99
+ throw new EmptyResultError(
100
+ 'ctrip search',
101
+ 'Try a destination, scenic spot, or hotel keyword such as "苏州" or "朱家尖"',
102
+ );
103
+ }
104
+ return results;
60
105
  },
61
106
  });
107
+
108
+ export const __test__ = {
109
+ clampLimit,
110
+ mapSearchResults,
111
+ };
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { AuthRequiredError } from '../../../errors.js';
3
+ import { getSts2Credentials } from './sts2.js';
4
+
5
+ describe('douyin sts2 credentials', () => {
6
+ it('accepts top-level credential fields returned by creator center', async () => {
7
+ const page = {
8
+ evaluate: async () => ({
9
+ access_key_id: 'ak',
10
+ secret_access_key: 'sk',
11
+ session_token: 'token',
12
+ expired_time: 1_234_567_890,
13
+ }),
14
+ };
15
+
16
+ await expect(getSts2Credentials(page as never)).resolves.toEqual({
17
+ access_key_id: 'ak',
18
+ secret_access_key: 'sk',
19
+ session_token: 'token',
20
+ expired_time: 1_234_567_890,
21
+ });
22
+ });
23
+
24
+ it('still rejects responses without credential fields', async () => {
25
+ const page = {
26
+ evaluate: async () => ({ status_code: 8 }),
27
+ };
28
+
29
+ await expect(getSts2Credentials(page as never)).rejects.toBeInstanceOf(AuthRequiredError);
30
+ });
31
+ });
@@ -12,9 +12,17 @@ const STS2_URL =
12
12
  */
13
13
  export async function getSts2Credentials(page: IPage): Promise<Sts2Credentials> {
14
14
  const js = `fetch(${JSON.stringify(STS2_URL)}, { credentials: 'include' }).then(r => r.json())`;
15
- const res = await page.evaluate(js) as { data: Sts2Credentials };
16
- if (!res?.data?.access_key_id) {
15
+ const res = await page.evaluate(js) as Sts2Credentials | { data?: Sts2Credentials };
16
+ const credentials = (
17
+ typeof res === 'object' &&
18
+ res !== null &&
19
+ 'data' in res &&
20
+ res.data
21
+ )
22
+ ? res.data
23
+ : (res as Sts2Credentials);
24
+ if (!credentials?.access_key_id) {
17
25
  throw new AuthRequiredError('creator.douyin.com', 'STS2 credentials missing');
18
26
  }
19
- return res.data;
27
+ return credentials;
20
28
  }
@@ -1,8 +1,21 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { browserFetchMock } = vi.hoisted(() => ({
4
+ browserFetchMock: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('./_shared/browser-fetch.js', () => ({
8
+ browserFetch: browserFetchMock,
9
+ }));
10
+
2
11
  import { getRegistry } from '../../registry.js';
3
12
  import './activities.js';
4
13
 
5
14
  describe('douyin activities registration', () => {
15
+ beforeEach(() => {
16
+ browserFetchMock.mockReset();
17
+ });
18
+
6
19
  it('registers the activities command', () => {
7
20
  const registry = getRegistry();
8
21
  const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
@@ -22,4 +35,31 @@ describe('douyin activities registration', () => {
22
35
  const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
23
36
  expect(cmd?.strategy).toBe('cookie');
24
37
  });
38
+
39
+ it('maps the current activity payload shape returned by creator center', async () => {
40
+ const registry = getRegistry();
41
+ const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
42
+ expect(cmd?.func).toBeDefined();
43
+ if (!cmd?.func) throw new Error('douyin activities command not registered');
44
+
45
+ browserFetchMock.mockResolvedValueOnce({
46
+ activity_list: [
47
+ {
48
+ activity_id: '200',
49
+ activity_name: '超会玩派对',
50
+ show_end_time: '2026.05.31',
51
+ },
52
+ ],
53
+ });
54
+
55
+ const rows = await cmd.func({} as never, {});
56
+
57
+ expect(rows).toEqual([
58
+ {
59
+ activity_id: '200',
60
+ title: '超会玩派对',
61
+ end_time: '2026.05.31',
62
+ },
63
+ ]);
64
+ });
25
65
  });
@@ -12,12 +12,21 @@ cli({
12
12
  func: async (page, _kwargs) => {
13
13
  const url = 'https://creator.douyin.com/web/api/media/activity/get/?aid=1128';
14
14
  const res = await browserFetch(page, 'GET', url) as {
15
- activity_list: Array<{ activity_id: string; title: string; end_time: number }>
15
+ activity_list: Array<{
16
+ activity_id: string;
17
+ title?: string;
18
+ activity_name?: string;
19
+ end_time?: number;
20
+ show_end_time?: string;
21
+ }>
16
22
  };
17
23
  return (res.activity_list ?? []).map(a => ({
18
24
  activity_id: a.activity_id,
19
- title: a.title,
20
- end_time: new Date(a.end_time * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Tokyo' }),
25
+ title: a.title ?? a.activity_name ?? '',
26
+ end_time:
27
+ typeof a.end_time === 'number'
28
+ ? new Date(a.end_time * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Tokyo' })
29
+ : (a.show_end_time ?? ''),
21
30
  }));
22
31
  },
23
32
  });