@jackwener/opencli 1.5.5 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +31 -4
  3. package/README.zh-CN.md +40 -5
  4. package/SKILL.md +1 -1
  5. package/dist/browser/cdp.d.ts +1 -0
  6. package/dist/browser/cdp.js +30 -27
  7. package/dist/browser/daemon-client.d.ts +11 -1
  8. package/dist/browser/daemon-client.js +3 -0
  9. package/dist/browser/dom-helpers.js +1 -0
  10. package/dist/browser/dom-helpers.test.js +14 -1
  11. package/dist/browser/mcp.js +18 -13
  12. package/dist/browser/page.d.ts +6 -0
  13. package/dist/browser/page.js +37 -2
  14. package/dist/browser/page.test.d.ts +1 -0
  15. package/dist/browser/page.test.js +44 -0
  16. package/dist/browser/stealth.js +198 -0
  17. package/dist/browser/stealth.test.d.ts +1 -0
  18. package/dist/browser/stealth.test.js +134 -0
  19. package/dist/browser.test.js +1 -1
  20. package/dist/build-manifest.d.ts +1 -0
  21. package/dist/build-manifest.js +5 -1
  22. package/dist/build-manifest.test.js +2 -0
  23. package/dist/cli-manifest.json +1821 -252
  24. package/dist/cli.js +20 -3
  25. package/dist/clis/antigravity/serve.d.ts +1 -1
  26. package/dist/clis/antigravity/serve.js +5 -8
  27. package/dist/clis/band/bands.d.ts +1 -0
  28. package/dist/clis/band/bands.js +72 -0
  29. package/dist/clis/band/mentions.d.ts +1 -0
  30. package/dist/clis/band/mentions.js +127 -0
  31. package/dist/clis/band/post.d.ts +1 -0
  32. package/dist/clis/band/post.js +175 -0
  33. package/dist/clis/band/posts.d.ts +1 -0
  34. package/dist/clis/band/posts.js +94 -0
  35. package/dist/clis/bilibili/subtitle.js +4 -0
  36. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  37. package/dist/clis/bilibili/subtitle.test.js +48 -0
  38. package/dist/clis/chatwise/ask.js +0 -2
  39. package/dist/clis/chatwise/export.js +0 -2
  40. package/dist/clis/chatwise/history.js +0 -2
  41. package/dist/clis/chatwise/model.js +0 -2
  42. package/dist/clis/chatwise/new.js +1 -2
  43. package/dist/clis/chatwise/read.js +0 -2
  44. package/dist/clis/chatwise/screenshot.js +1 -2
  45. package/dist/clis/chatwise/send.js +0 -2
  46. package/dist/clis/chatwise/status.js +1 -2
  47. package/dist/clis/ctrip/search.d.ts +13 -0
  48. package/dist/clis/ctrip/search.js +73 -48
  49. package/dist/clis/ctrip/search.test.d.ts +1 -0
  50. package/dist/clis/ctrip/search.test.js +64 -0
  51. package/dist/clis/doubao/detail.d.ts +1 -0
  52. package/dist/clis/doubao/detail.js +33 -0
  53. package/dist/clis/doubao/detail.test.d.ts +1 -0
  54. package/dist/clis/doubao/detail.test.js +42 -0
  55. package/dist/clis/doubao/history.d.ts +1 -0
  56. package/dist/clis/doubao/history.js +28 -0
  57. package/dist/clis/doubao/history.test.d.ts +1 -0
  58. package/dist/clis/doubao/history.test.js +37 -0
  59. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  60. package/dist/clis/doubao/meeting-summary.js +39 -0
  61. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  62. package/dist/clis/doubao/meeting-transcript.js +36 -0
  63. package/dist/clis/doubao/utils.d.ts +27 -0
  64. package/dist/clis/doubao/utils.js +317 -0
  65. package/dist/clis/doubao/utils.test.d.ts +1 -0
  66. package/dist/clis/doubao/utils.test.js +24 -0
  67. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  68. package/dist/clis/douyin/_shared/public-api.js +29 -0
  69. package/dist/clis/douyin/_shared/sts2.js +8 -2
  70. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  71. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  72. package/dist/clis/douyin/activities.js +4 -2
  73. package/dist/clis/douyin/activities.test.js +34 -1
  74. package/dist/clis/douyin/collections.js +1 -1
  75. package/dist/clis/douyin/collections.test.js +24 -2
  76. package/dist/clis/douyin/draft.d.ts +8 -11
  77. package/dist/clis/douyin/draft.js +302 -185
  78. package/dist/clis/douyin/draft.test.d.ts +1 -1
  79. package/dist/clis/douyin/draft.test.js +357 -2
  80. package/dist/clis/douyin/hashtag.js +9 -2
  81. package/dist/clis/douyin/hashtag.test.js +35 -2
  82. package/dist/clis/douyin/profile.js +1 -1
  83. package/dist/clis/douyin/profile.test.js +36 -1
  84. package/dist/clis/douyin/user-videos.d.ts +5 -0
  85. package/dist/clis/douyin/user-videos.js +74 -0
  86. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  87. package/dist/clis/douyin/user-videos.test.js +108 -0
  88. package/dist/clis/douyin/videos.js +22 -5
  89. package/dist/clis/douyin/videos.test.js +45 -2
  90. package/dist/clis/facebook/search.test.d.ts +5 -0
  91. package/dist/clis/facebook/search.test.js +60 -0
  92. package/dist/clis/facebook/search.yaml +4 -3
  93. package/dist/clis/instagram/download.d.ts +16 -0
  94. package/dist/clis/instagram/download.js +225 -0
  95. package/dist/clis/instagram/download.test.d.ts +1 -0
  96. package/dist/clis/instagram/download.test.js +118 -0
  97. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  98. package/dist/clis/notebooklm/bind-current.js +29 -0
  99. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  100. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  101. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  102. package/dist/clis/notebooklm/binding.test.js +44 -0
  103. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  104. package/dist/clis/notebooklm/compat.test.js +16 -0
  105. package/dist/clis/notebooklm/current.d.ts +1 -0
  106. package/dist/clis/notebooklm/current.js +28 -0
  107. package/dist/clis/notebooklm/get.d.ts +1 -0
  108. package/dist/clis/notebooklm/get.js +37 -0
  109. package/dist/clis/notebooklm/history.d.ts +1 -0
  110. package/dist/clis/notebooklm/history.js +25 -0
  111. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  112. package/dist/clis/notebooklm/history.test.js +58 -0
  113. package/dist/clis/notebooklm/list.d.ts +1 -0
  114. package/dist/clis/notebooklm/list.js +35 -0
  115. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  116. package/dist/clis/notebooklm/note-list.js +28 -0
  117. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  118. package/dist/clis/notebooklm/note-list.test.js +56 -0
  119. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  120. package/dist/clis/notebooklm/notes-get.js +47 -0
  121. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  122. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  123. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  124. package/dist/clis/notebooklm/rpc.js +189 -0
  125. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  126. package/dist/clis/notebooklm/rpc.test.js +105 -0
  127. package/dist/clis/notebooklm/shared.d.ts +87 -0
  128. package/dist/clis/notebooklm/shared.js +3 -0
  129. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  130. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  131. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  132. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  133. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  134. package/dist/clis/notebooklm/source-get.js +40 -0
  135. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  136. package/dist/clis/notebooklm/source-get.test.js +84 -0
  137. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  138. package/dist/clis/notebooklm/source-guide.js +44 -0
  139. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  140. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  141. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  142. package/dist/clis/notebooklm/source-list.js +30 -0
  143. package/dist/clis/notebooklm/status.d.ts +1 -0
  144. package/dist/clis/notebooklm/status.js +31 -0
  145. package/dist/clis/notebooklm/summary.d.ts +1 -0
  146. package/dist/clis/notebooklm/summary.js +30 -0
  147. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  148. package/dist/clis/notebooklm/summary.test.js +78 -0
  149. package/dist/clis/notebooklm/utils.d.ts +37 -0
  150. package/dist/clis/notebooklm/utils.js +739 -0
  151. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  152. package/dist/clis/notebooklm/utils.test.js +390 -0
  153. package/dist/clis/ones/common.d.ts +32 -0
  154. package/dist/clis/ones/common.js +144 -0
  155. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  156. package/dist/clis/ones/enrich-tasks.js +37 -0
  157. package/dist/clis/ones/login.d.ts +1 -0
  158. package/dist/clis/ones/login.js +80 -0
  159. package/dist/clis/ones/logout.d.ts +1 -0
  160. package/dist/clis/ones/logout.js +17 -0
  161. package/dist/clis/ones/me.d.ts +1 -0
  162. package/dist/clis/ones/me.js +30 -0
  163. package/dist/clis/ones/my-tasks.d.ts +1 -0
  164. package/dist/clis/ones/my-tasks.js +120 -0
  165. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  166. package/dist/clis/ones/resolve-labels.js +64 -0
  167. package/dist/clis/ones/task-helpers.d.ts +29 -0
  168. package/dist/clis/ones/task-helpers.js +212 -0
  169. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  170. package/dist/clis/ones/task-helpers.test.js +12 -0
  171. package/dist/clis/ones/task.d.ts +1 -0
  172. package/dist/clis/ones/task.js +66 -0
  173. package/dist/clis/ones/tasks.d.ts +1 -0
  174. package/dist/clis/ones/tasks.js +79 -0
  175. package/dist/clis/ones/token-info.d.ts +1 -0
  176. package/dist/clis/ones/token-info.js +42 -0
  177. package/dist/clis/ones/worklog.d.ts +11 -0
  178. package/dist/clis/ones/worklog.js +267 -0
  179. package/dist/clis/ones/worklog.test.d.ts +1 -0
  180. package/dist/clis/ones/worklog.test.js +20 -0
  181. package/dist/clis/spotify/spotify.d.ts +1 -0
  182. package/dist/clis/spotify/spotify.js +316 -0
  183. package/dist/clis/spotify/utils.d.ts +21 -0
  184. package/dist/clis/spotify/utils.js +66 -0
  185. package/dist/clis/spotify/utils.test.d.ts +1 -0
  186. package/dist/clis/spotify/utils.test.js +67 -0
  187. package/dist/clis/substack/utils.d.ts +4 -0
  188. package/dist/clis/substack/utils.js +8 -2
  189. package/dist/clis/substack/utils.test.d.ts +1 -0
  190. package/dist/clis/substack/utils.test.js +46 -0
  191. package/dist/clis/tieba/commands.test.d.ts +4 -0
  192. package/dist/clis/tieba/commands.test.js +79 -0
  193. package/dist/clis/tieba/hot.d.ts +1 -0
  194. package/dist/clis/tieba/hot.js +48 -0
  195. package/dist/clis/tieba/posts.d.ts +1 -0
  196. package/dist/clis/tieba/posts.js +85 -0
  197. package/dist/clis/tieba/read.d.ts +1 -0
  198. package/dist/clis/tieba/read.js +140 -0
  199. package/dist/clis/tieba/search.d.ts +1 -0
  200. package/dist/clis/tieba/search.js +108 -0
  201. package/dist/clis/tieba/utils.d.ts +101 -0
  202. package/dist/clis/tieba/utils.js +240 -0
  203. package/dist/clis/tieba/utils.test.d.ts +1 -0
  204. package/dist/clis/tieba/utils.test.js +290 -0
  205. package/dist/clis/v2ex/hot.yaml +4 -1
  206. package/dist/clis/v2ex/latest.yaml +4 -1
  207. package/dist/clis/v2ex/topic.yaml +6 -1
  208. package/dist/clis/weixin/download.d.ts +9 -0
  209. package/dist/clis/weixin/download.js +76 -6
  210. package/dist/clis/weread/book.js +206 -13
  211. package/dist/clis/weread/commands.test.js +331 -0
  212. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  213. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  214. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  215. package/dist/clis/weread/search-regression.test.js +407 -0
  216. package/dist/clis/weread/search.js +143 -7
  217. package/dist/clis/weread/shelf.js +13 -95
  218. package/dist/clis/weread/utils.d.ts +56 -0
  219. package/dist/clis/weread/utils.js +234 -7
  220. package/dist/clis/weread/utils.test.js +71 -1
  221. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  222. package/dist/clis/xiaohongshu/comments.js +76 -17
  223. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  224. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  225. package/dist/clis/xiaohongshu/download.js +83 -22
  226. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  227. package/dist/clis/xiaohongshu/download.test.js +75 -0
  228. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  229. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  230. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  231. package/dist/clis/xiaohongshu/note.js +76 -0
  232. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  233. package/dist/clis/xiaohongshu/note.test.js +136 -0
  234. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  235. package/dist/clis/xiaohongshu/publish.js +78 -31
  236. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  237. package/dist/clis/xiaohongshu/search.js +9 -0
  238. package/dist/clis/xiaohongshu/search.test.js +10 -4
  239. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  240. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  241. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  242. package/dist/clis/xueqiu/comments.d.ts +118 -0
  243. package/dist/clis/xueqiu/comments.js +354 -0
  244. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  245. package/dist/clis/xueqiu/comments.test.js +696 -0
  246. package/dist/clis/youtube/search.js +57 -17
  247. package/dist/clis/youtube/transcript.js +2 -4
  248. package/dist/clis/youtube/utils.d.ts +9 -0
  249. package/dist/clis/youtube/utils.js +67 -3
  250. package/dist/clis/youtube/utils.test.d.ts +1 -0
  251. package/dist/clis/youtube/utils.test.js +37 -0
  252. package/dist/clis/youtube/video.js +16 -15
  253. package/dist/clis/zhihu/question.js +19 -17
  254. package/dist/clis/zhihu/question.test.d.ts +1 -0
  255. package/dist/clis/zhihu/question.test.js +54 -0
  256. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  257. package/dist/clis/zsxq/dynamics.js +47 -0
  258. package/dist/clis/zsxq/groups.d.ts +1 -0
  259. package/dist/clis/zsxq/groups.js +32 -0
  260. package/dist/clis/zsxq/search.d.ts +1 -0
  261. package/dist/clis/zsxq/search.js +43 -0
  262. package/dist/clis/zsxq/search.test.d.ts +1 -0
  263. package/dist/clis/zsxq/search.test.js +24 -0
  264. package/dist/clis/zsxq/topic.d.ts +1 -0
  265. package/dist/clis/zsxq/topic.js +47 -0
  266. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  267. package/dist/clis/zsxq/topic.test.js +29 -0
  268. package/dist/clis/zsxq/topics.d.ts +1 -0
  269. package/dist/clis/zsxq/topics.js +25 -0
  270. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  271. package/dist/clis/zsxq/topics.test.js +24 -0
  272. package/dist/clis/zsxq/utils.d.ts +97 -0
  273. package/dist/clis/zsxq/utils.js +230 -0
  274. package/dist/commanderAdapter.js +10 -1
  275. package/dist/commanderAdapter.test.js +64 -0
  276. package/dist/commands/daemon.d.ts +9 -0
  277. package/dist/commands/daemon.js +124 -0
  278. package/dist/commands/daemon.test.d.ts +1 -0
  279. package/dist/commands/daemon.test.js +185 -0
  280. package/dist/completion.js +3 -1
  281. package/dist/constants.d.ts +2 -0
  282. package/dist/constants.js +2 -0
  283. package/dist/daemon.d.ts +1 -1
  284. package/dist/daemon.js +25 -14
  285. package/dist/daemon.test.d.ts +1 -0
  286. package/dist/daemon.test.js +65 -0
  287. package/dist/discovery.d.ts +9 -0
  288. package/dist/discovery.js +47 -2
  289. package/dist/electron-apps.d.ts +29 -0
  290. package/dist/electron-apps.js +65 -0
  291. package/dist/electron-apps.test.d.ts +1 -0
  292. package/dist/electron-apps.test.js +43 -0
  293. package/dist/engine.test.js +41 -9
  294. package/dist/execution.js +20 -16
  295. package/dist/external-clis.yaml +17 -0
  296. package/dist/idle-manager.d.ts +19 -0
  297. package/dist/idle-manager.js +54 -0
  298. package/dist/launcher.d.ts +36 -0
  299. package/dist/launcher.js +152 -0
  300. package/dist/launcher.test.d.ts +1 -0
  301. package/dist/launcher.test.js +57 -0
  302. package/dist/main.js +3 -3
  303. package/dist/registry.d.ts +1 -0
  304. package/dist/registry.js +31 -3
  305. package/dist/registry.test.js +13 -0
  306. package/dist/runtime.d.ts +5 -3
  307. package/dist/runtime.js +12 -5
  308. package/dist/serialization.d.ts +1 -0
  309. package/dist/serialization.js +3 -0
  310. package/dist/serialization.test.js +17 -1
  311. package/dist/tui.d.ts +7 -0
  312. package/dist/tui.js +52 -0
  313. package/dist/tui.test.d.ts +1 -0
  314. package/dist/tui.test.js +19 -0
  315. package/dist/types.d.ts +5 -0
  316. package/dist/weixin-download.test.js +14 -0
  317. package/docs/.vitepress/config.mts +4 -0
  318. package/docs/adapters/browser/band.md +63 -0
  319. package/docs/adapters/browser/notebooklm.md +69 -0
  320. package/docs/adapters/browser/ones.md +59 -0
  321. package/docs/adapters/browser/spotify.md +62 -0
  322. package/docs/adapters/browser/tieba.md +45 -0
  323. package/docs/adapters/browser/xiaohongshu.md +19 -10
  324. package/docs/adapters/browser/xueqiu.md +5 -0
  325. package/docs/adapters/browser/zsxq.md +49 -0
  326. package/docs/adapters/index.md +67 -63
  327. package/docs/adapters-doc/ones.md +32 -0
  328. package/docs/guide/browser-bridge.md +12 -0
  329. package/docs/guide/troubleshooting.md +9 -4
  330. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  331. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  332. package/docs/zh/guide/browser-bridge.md +12 -0
  333. package/extension/dist/background.js +794 -513
  334. package/extension/src/background.test.ts +202 -2
  335. package/extension/src/background.ts +189 -10
  336. package/extension/src/cdp.ts +54 -0
  337. package/extension/src/protocol.ts +11 -5
  338. package/package.json +1 -1
  339. package/scripts/postinstall.js +16 -0
  340. package/src/browser/cdp.ts +24 -17
  341. package/src/browser/daemon-client.ts +11 -1
  342. package/src/browser/dom-helpers.test.ts +15 -1
  343. package/src/browser/dom-helpers.ts +1 -0
  344. package/src/browser/mcp.ts +18 -13
  345. package/src/browser/page.test.ts +58 -0
  346. package/src/browser/page.ts +34 -2
  347. package/src/browser/stealth.test.ts +153 -0
  348. package/src/browser/stealth.ts +198 -0
  349. package/src/browser.test.ts +1 -1
  350. package/src/build-manifest.test.ts +2 -0
  351. package/src/build-manifest.ts +6 -1
  352. package/src/cli.ts +21 -3
  353. package/src/clis/antigravity/SKILL.md +3 -12
  354. package/src/clis/antigravity/serve.ts +5 -10
  355. package/src/clis/band/bands.ts +76 -0
  356. package/src/clis/band/mentions.ts +134 -0
  357. package/src/clis/band/post.ts +187 -0
  358. package/src/clis/band/posts.ts +106 -0
  359. package/src/clis/bilibili/subtitle.test.ts +60 -0
  360. package/src/clis/bilibili/subtitle.ts +4 -0
  361. package/src/clis/chatwise/ask.ts +0 -2
  362. package/src/clis/chatwise/export.ts +0 -2
  363. package/src/clis/chatwise/history.ts +0 -2
  364. package/src/clis/chatwise/model.ts +0 -2
  365. package/src/clis/chatwise/new.ts +1 -2
  366. package/src/clis/chatwise/read.ts +0 -2
  367. package/src/clis/chatwise/screenshot.ts +1 -2
  368. package/src/clis/chatwise/send.ts +0 -2
  369. package/src/clis/chatwise/status.ts +1 -2
  370. package/src/clis/ctrip/search.test.ts +73 -0
  371. package/src/clis/ctrip/search.ts +97 -47
  372. package/src/clis/doubao/detail.test.ts +53 -0
  373. package/src/clis/doubao/detail.ts +41 -0
  374. package/src/clis/doubao/history.test.ts +45 -0
  375. package/src/clis/doubao/history.ts +32 -0
  376. package/src/clis/doubao/meeting-summary.ts +53 -0
  377. package/src/clis/doubao/meeting-transcript.ts +48 -0
  378. package/src/clis/doubao/utils.test.ts +45 -0
  379. package/src/clis/doubao/utils.ts +371 -0
  380. package/src/clis/douyin/_shared/public-api.ts +84 -0
  381. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  382. package/src/clis/douyin/_shared/sts2.ts +11 -3
  383. package/src/clis/douyin/activities.test.ts +41 -1
  384. package/src/clis/douyin/activities.ts +12 -3
  385. package/src/clis/douyin/collections.test.ts +35 -2
  386. package/src/clis/douyin/collections.ts +1 -1
  387. package/src/clis/douyin/draft.test.ts +444 -2
  388. package/src/clis/douyin/draft.ts +382 -218
  389. package/src/clis/douyin/hashtag.test.ts +42 -2
  390. package/src/clis/douyin/hashtag.ts +11 -3
  391. package/src/clis/douyin/profile.test.ts +43 -1
  392. package/src/clis/douyin/profile.ts +9 -2
  393. package/src/clis/douyin/user-videos.test.ts +122 -0
  394. package/src/clis/douyin/user-videos.ts +101 -0
  395. package/src/clis/douyin/videos.test.ts +52 -2
  396. package/src/clis/douyin/videos.ts +49 -15
  397. package/src/clis/facebook/search.test.ts +70 -0
  398. package/src/clis/facebook/search.yaml +4 -3
  399. package/src/clis/instagram/download.test.ts +159 -0
  400. package/src/clis/instagram/download.ts +286 -0
  401. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  402. package/src/clis/notebooklm/bind-current.ts +36 -0
  403. package/src/clis/notebooklm/binding.test.ts +53 -0
  404. package/src/clis/notebooklm/compat.test.ts +19 -0
  405. package/src/clis/notebooklm/current.ts +38 -0
  406. package/src/clis/notebooklm/get.ts +53 -0
  407. package/src/clis/notebooklm/history.test.ts +70 -0
  408. package/src/clis/notebooklm/history.ts +36 -0
  409. package/src/clis/notebooklm/list.ts +40 -0
  410. package/src/clis/notebooklm/note-list.test.ts +64 -0
  411. package/src/clis/notebooklm/note-list.ts +42 -0
  412. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  413. package/src/clis/notebooklm/notes-get.ts +67 -0
  414. package/src/clis/notebooklm/rpc.test.ts +126 -0
  415. package/src/clis/notebooklm/rpc.ts +286 -0
  416. package/src/clis/notebooklm/shared.ts +98 -0
  417. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  418. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  419. package/src/clis/notebooklm/source-get.test.ts +100 -0
  420. package/src/clis/notebooklm/source-get.ts +60 -0
  421. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  422. package/src/clis/notebooklm/source-guide.ts +69 -0
  423. package/src/clis/notebooklm/source-list.ts +45 -0
  424. package/src/clis/notebooklm/status.ts +34 -0
  425. package/src/clis/notebooklm/summary.test.ts +94 -0
  426. package/src/clis/notebooklm/summary.ts +45 -0
  427. package/src/clis/notebooklm/utils.test.ts +446 -0
  428. package/src/clis/notebooklm/utils.ts +893 -0
  429. package/src/clis/ones/common.ts +187 -0
  430. package/src/clis/ones/enrich-tasks.ts +47 -0
  431. package/src/clis/ones/login.ts +103 -0
  432. package/src/clis/ones/logout.ts +19 -0
  433. package/src/clis/ones/me.ts +34 -0
  434. package/src/clis/ones/my-tasks.ts +148 -0
  435. package/src/clis/ones/resolve-labels.ts +80 -0
  436. package/src/clis/ones/task-helpers.test.ts +14 -0
  437. package/src/clis/ones/task-helpers.ts +214 -0
  438. package/src/clis/ones/task.ts +79 -0
  439. package/src/clis/ones/tasks.ts +92 -0
  440. package/src/clis/ones/token-info.ts +46 -0
  441. package/src/clis/ones/worklog.test.ts +24 -0
  442. package/src/clis/ones/worklog.ts +306 -0
  443. package/src/clis/spotify/spotify.ts +328 -0
  444. package/src/clis/spotify/utils.test.ts +87 -0
  445. package/src/clis/spotify/utils.ts +92 -0
  446. package/src/clis/substack/utils.test.ts +54 -0
  447. package/src/clis/substack/utils.ts +10 -2
  448. package/src/clis/tieba/commands.test.ts +86 -0
  449. package/src/clis/tieba/hot.ts +52 -0
  450. package/src/clis/tieba/posts.ts +108 -0
  451. package/src/clis/tieba/read.ts +158 -0
  452. package/src/clis/tieba/search.ts +119 -0
  453. package/src/clis/tieba/utils.test.ts +322 -0
  454. package/src/clis/tieba/utils.ts +348 -0
  455. package/src/clis/v2ex/hot.yaml +4 -1
  456. package/src/clis/v2ex/latest.yaml +4 -1
  457. package/src/clis/v2ex/topic.yaml +6 -1
  458. package/src/clis/weixin/download.ts +95 -6
  459. package/src/clis/weread/book.ts +256 -13
  460. package/src/clis/weread/commands.test.ts +409 -0
  461. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  462. package/src/clis/weread/search-regression.test.ts +440 -0
  463. package/src/clis/weread/search.ts +189 -9
  464. package/src/clis/weread/shelf.ts +20 -122
  465. package/src/clis/weread/utils.test.ts +81 -1
  466. package/src/clis/weread/utils.ts +293 -7
  467. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  468. package/src/clis/xiaohongshu/comments.ts +76 -17
  469. package/src/clis/xiaohongshu/download.test.ts +96 -0
  470. package/src/clis/xiaohongshu/download.ts +83 -22
  471. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  472. package/src/clis/xiaohongshu/note.test.ts +164 -0
  473. package/src/clis/xiaohongshu/note.ts +86 -0
  474. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  475. package/src/clis/xiaohongshu/publish.ts +84 -30
  476. package/src/clis/xiaohongshu/search.test.ts +11 -4
  477. package/src/clis/xiaohongshu/search.ts +13 -0
  478. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  479. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  480. package/src/clis/xueqiu/comments.test.ts +823 -0
  481. package/src/clis/xueqiu/comments.ts +461 -0
  482. package/src/clis/youtube/search.ts +57 -17
  483. package/src/clis/youtube/transcript.ts +2 -4
  484. package/src/clis/youtube/utils.test.ts +43 -0
  485. package/src/clis/youtube/utils.ts +69 -0
  486. package/src/clis/youtube/video.ts +16 -15
  487. package/src/clis/zhihu/question.test.ts +71 -0
  488. package/src/clis/zhihu/question.ts +27 -15
  489. package/src/clis/zsxq/dynamics.ts +60 -0
  490. package/src/clis/zsxq/groups.ts +41 -0
  491. package/src/clis/zsxq/search.test.ts +29 -0
  492. package/src/clis/zsxq/search.ts +54 -0
  493. package/src/clis/zsxq/topic.test.ts +34 -0
  494. package/src/clis/zsxq/topic.ts +68 -0
  495. package/src/clis/zsxq/topics.test.ts +29 -0
  496. package/src/clis/zsxq/topics.ts +36 -0
  497. package/src/clis/zsxq/utils.ts +351 -0
  498. package/src/commanderAdapter.test.ts +77 -0
  499. package/src/commanderAdapter.ts +8 -1
  500. package/src/commands/daemon.test.ts +238 -0
  501. package/src/commands/daemon.ts +135 -0
  502. package/src/completion.ts +2 -1
  503. package/src/constants.ts +3 -0
  504. package/src/daemon.test.ts +88 -0
  505. package/src/daemon.ts +26 -14
  506. package/src/discovery.ts +52 -2
  507. package/src/electron-apps.test.ts +50 -0
  508. package/src/electron-apps.ts +89 -0
  509. package/src/engine.test.ts +45 -9
  510. package/src/execution.ts +24 -19
  511. package/src/external-clis.yaml +17 -0
  512. package/src/idle-manager.ts +60 -0
  513. package/src/launcher.test.ts +67 -0
  514. package/src/launcher.ts +185 -0
  515. package/src/main.ts +3 -2
  516. package/src/registry.test.ts +15 -0
  517. package/src/registry.ts +32 -3
  518. package/src/runtime.ts +13 -7
  519. package/src/serialization.test.ts +19 -1
  520. package/src/serialization.ts +2 -0
  521. package/src/tui.test.ts +23 -0
  522. package/src/tui.ts +65 -0
  523. package/src/types.ts +5 -0
  524. package/src/weixin-download.test.ts +27 -0
  525. package/tests/e2e/band-auth.test.ts +20 -0
  526. package/tests/e2e/browser-auth-helpers.ts +18 -0
  527. package/tests/e2e/browser-auth.test.ts +35 -47
  528. package/tests/e2e/browser-public-extended.test.ts +6 -2
  529. package/tests/e2e/browser-public.test.ts +288 -0
  530. package/tests/e2e/management.test.ts +1 -1
  531. package/tests/e2e/plugin-management.test.ts +1 -1
  532. package/vitest.config.ts +1 -0
  533. package/chatwise-opencli.ps1 +0 -82
  534. package/dist/clis/chatwise/shared.d.ts +0 -2
  535. package/dist/clis/chatwise/shared.js +0 -6
  536. package/dist/weread-private-api-regression.test.d.ts +0 -1
  537. package/dist/weread-search-regression.test.d.ts +0 -1
  538. package/dist/weread-search-regression.test.js +0 -39
  539. package/src/clis/chatwise/shared.ts +0 -8
  540. package/src/weread-search-regression.test.ts +0 -44
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
2
+ import { discoverClis, discoverPlugins, ensureUserCliCompatShims, PLUGINS_DIR } from './discovery.js';
3
3
  import { executeCommand } from './execution.js';
4
4
  import { getRegistry, cli, Strategy } from './registry.js';
5
5
  import { clearAllHooks, onAfterExecute } from './hooks.js';
@@ -71,6 +71,36 @@ cli({
71
71
  await fs.promises.rm(tempBuildRoot, { recursive: true, force: true });
72
72
  }
73
73
  });
74
+ it('loads legacy user TS CLI modules via compatibility shims', async () => {
75
+ const tempOpencliRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-user-clis-'));
76
+ const userClisDir = path.join(tempOpencliRoot, 'clis');
77
+ const siteDir = path.join(userClisDir, 'legacy-site');
78
+ const commandPath = path.join(siteDir, 'hello.ts');
79
+ try {
80
+ await ensureUserCliCompatShims(tempOpencliRoot);
81
+ await fs.promises.mkdir(siteDir, { recursive: true });
82
+ await fs.promises.writeFile(commandPath, `
83
+ import { cli, Strategy } from '../../registry';
84
+ import { CommandExecutionError } from '../../errors';
85
+
86
+ cli({
87
+ site: 'legacy-site',
88
+ name: 'hello',
89
+ description: 'hello command',
90
+ strategy: Strategy.PUBLIC,
91
+ browser: false,
92
+ func: async () => [{ ok: true, errorName: new CommandExecutionError('boom').name }],
93
+ });
94
+ `);
95
+ await discoverClis(userClisDir);
96
+ const cmd = getRegistry().get('legacy-site/hello');
97
+ expect(cmd).toBeDefined();
98
+ await expect(executeCommand(cmd, {})).resolves.toEqual([{ ok: true, errorName: 'CommandExecutionError' }]);
99
+ }
100
+ finally {
101
+ await fs.promises.rm(tempOpencliRoot, { recursive: true, force: true });
102
+ }
103
+ });
74
104
  });
75
105
  describe('discoverPlugins', () => {
76
106
  const testPluginDir = path.join(PLUGINS_DIR, '__test-plugin__');
@@ -245,21 +275,23 @@ describe('executeCommand', () => {
245
275
  expect(seen[0].error.message).toBe('boom');
246
276
  expect(typeof seen[0].finishedAt).toBe('number');
247
277
  });
248
- it('fails fast for chatwise commands when OPENCLI_CDP_ENDPOINT is missing', async () => {
278
+ it('uses launcher for registered Electron apps (chatwise)', async () => {
279
+ // Mock the launcher to return a fake endpoint (avoids real HTTP/process calls)
280
+ const launcher = await import('./launcher.js');
281
+ const spy = vi.spyOn(launcher, 'resolveElectronEndpoint')
282
+ .mockResolvedValue('http://127.0.0.1:9228');
249
283
  const cmd = cli({
250
284
  site: 'chatwise',
251
285
  name: 'status',
252
286
  description: 'chatwise status',
253
287
  browser: true,
254
288
  strategy: Strategy.PUBLIC,
255
- requiredEnv: [
256
- {
257
- name: 'OPENCLI_CDP_ENDPOINT',
258
- help: 'Set OPENCLI_CDP_ENDPOINT before running chatwise commands.',
259
- },
260
- ],
261
289
  func: async () => [{ ok: true }],
262
290
  });
263
- await expect(executeCommand(cmd, {})).rejects.toThrow('requires environment variable OPENCLI_CDP_ENDPOINT');
291
+ // CDPBridge.connect() will fail (no actual CDP server), but the launcher
292
+ // should have been called with 'chatwise'.
293
+ await expect(executeCommand(cmd, {})).rejects.toThrow();
294
+ expect(spy).toHaveBeenCalledWith('chatwise');
295
+ spy.mockRestore();
264
296
  });
265
297
  });
package/dist/execution.js CHANGED
@@ -18,6 +18,8 @@ import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMM
18
18
  import { emitHook } from './hooks.js';
19
19
  import { checkDaemonStatus } from './browser/discover.js';
20
20
  import { log } from './logger.js';
21
+ import { isElectronApp } from './electron-apps.js';
22
+ import { resolveElectronEndpoint } from './launcher.js';
21
23
  const _loadedModules = new Set();
22
24
  export function coerceAndValidateArgs(cmdArgs, kwargs) {
23
25
  const result = { ...kwargs };
@@ -147,21 +149,25 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
147
149
  let result;
148
150
  try {
149
151
  if (shouldUseBrowserSession(cmd)) {
150
- // ── Fail-fast: only when daemon is UP but extension is not connected ──
151
- // If daemon is not running, let browserSession() handle auto-start as usual.
152
- // We only short-circuit when the daemon confirms the extension is missing —
153
- // that's a clear setup gap, not a transient startup state.
154
- // Use a short timeout: localhost responds in <50ms when running.
155
- // 300ms avoids a full 2s wait on cold-start (daemon not yet running).
156
- const status = await checkDaemonStatus({ timeout: 300 });
157
- if (status.running && !status.extensionConnected) {
158
- throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
159
- ' 1. Download: https://github.com/jackwener/opencli/releases\n' +
160
- ' 2. chrome://extensions Developer Mode → Load unpacked\n' +
161
- ' Then run: opencli doctor');
152
+ const electron = isElectronApp(cmd.site);
153
+ let cdpEndpoint;
154
+ if (electron) {
155
+ // Electron apps: auto-detect, prompt restart if needed, launch with CDP
156
+ cdpEndpoint = await resolveElectronEndpoint(cmd.site);
157
+ }
158
+ else {
159
+ // Browser Bridge: fail-fast when daemon is up but extension is missing.
160
+ // 300ms timeout avoids a full 2s wait on cold-start.
161
+ const status = await checkDaemonStatus({ timeout: 300 });
162
+ if (status.running && !status.extensionConnected) {
163
+ throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
164
+ ' 1. Download: https://github.com/jackwener/opencli/releases\n' +
165
+ ' 2. chrome://extensions → Developer Mode → Load unpacked\n' +
166
+ ' Then run: opencli doctor');
167
+ }
162
168
  }
163
169
  ensureRequiredEnv(cmd);
164
- const BrowserFactory = getBrowserFactory();
170
+ const BrowserFactory = getBrowserFactory(cmd.site);
165
171
  result = await browserSession(BrowserFactory, async (page) => {
166
172
  const preNavUrl = resolvePreNav(cmd);
167
173
  if (preNavUrl) {
@@ -172,8 +178,6 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
172
178
  }
173
179
  else {
174
180
  try {
175
- // goto() already includes smart DOM-settle detection (waitForDomStable).
176
- // No additional fixed sleep needed.
177
181
  await page.goto(preNavUrl);
178
182
  }
179
183
  catch (err) {
@@ -186,7 +190,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
186
190
  timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
187
191
  label: fullName(cmd),
188
192
  });
189
- }, { workspace: `site:${cmd.site}` });
193
+ }, { workspace: `site:${cmd.site}`, cdpEndpoint });
190
194
  }
191
195
  else {
192
196
  // Non-browser commands: apply timeout only when explicitly configured.
@@ -30,6 +30,23 @@
30
30
  install:
31
31
  default: "npm install -g @larksuite/cli"
32
32
 
33
+ - name: dws
34
+ binary: dws
35
+ description: "DingTalk Workspace CLI — messages, docs, calendar, contacts and more for humans and AI agents"
36
+ homepage: "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
37
+ tags: [dingtalk, collaboration, productivity, ai-agent]
38
+ install:
39
+ mac: "curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh"
40
+ linux: "curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh"
41
+
42
+ - name: wecom-cli
43
+ binary: wecom-cli
44
+ description: "WeCom/企业微信 CLI — contacts, todos, meetings, messages, calendar, docs and smart sheets for AI agents"
45
+ homepage: "https://github.com/WecomTeam/wecom-cli"
46
+ tags: [wecom, wechat-work, collaboration, productivity, ai-agent]
47
+ install:
48
+ default: "npm install -g @wecom/cli"
49
+
33
50
  - name: vercel
34
51
  binary: vercel
35
52
  description: "Vercel CLI — deploy projects, manage domains, env vars, logs and serverless functions"
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Manages daemon idle timeout with dual-condition logic:
3
+ * exits only when BOTH CLI is idle AND Extension is disconnected.
4
+ */
5
+ export declare class IdleManager {
6
+ private _timer;
7
+ private _lastCliRequestTime;
8
+ private _extensionConnected;
9
+ private _timeoutMs;
10
+ private _onExit;
11
+ constructor(timeoutMs: number, onExit: () => void);
12
+ get lastCliRequestTime(): number;
13
+ /** Call when an HTTP request arrives from CLI */
14
+ onCliRequest(): void;
15
+ /** Call when Extension WebSocket connects or disconnects */
16
+ setExtensionConnected(connected: boolean): void;
17
+ private _clearTimer;
18
+ private _resetTimer;
19
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Manages daemon idle timeout with dual-condition logic:
3
+ * exits only when BOTH CLI is idle AND Extension is disconnected.
4
+ */
5
+ export class IdleManager {
6
+ _timer = null;
7
+ _lastCliRequestTime = Date.now();
8
+ _extensionConnected = false;
9
+ _timeoutMs;
10
+ _onExit;
11
+ constructor(timeoutMs, onExit) {
12
+ this._timeoutMs = timeoutMs;
13
+ this._onExit = onExit;
14
+ }
15
+ get lastCliRequestTime() {
16
+ return this._lastCliRequestTime;
17
+ }
18
+ /** Call when an HTTP request arrives from CLI */
19
+ onCliRequest() {
20
+ this._lastCliRequestTime = Date.now();
21
+ this._resetTimer();
22
+ }
23
+ /** Call when Extension WebSocket connects or disconnects */
24
+ setExtensionConnected(connected) {
25
+ this._extensionConnected = connected;
26
+ if (connected) {
27
+ this._clearTimer();
28
+ }
29
+ else {
30
+ this._resetTimer();
31
+ }
32
+ }
33
+ _clearTimer() {
34
+ if (this._timer) {
35
+ clearTimeout(this._timer);
36
+ this._timer = null;
37
+ }
38
+ }
39
+ _resetTimer() {
40
+ this._clearTimer();
41
+ if (this._timeoutMs <= 0)
42
+ return;
43
+ if (this._extensionConnected)
44
+ return;
45
+ const elapsed = Date.now() - this._lastCliRequestTime;
46
+ if (elapsed >= this._timeoutMs) {
47
+ this._onExit();
48
+ return;
49
+ }
50
+ this._timer = setTimeout(() => {
51
+ this._onExit();
52
+ }, this._timeoutMs - elapsed);
53
+ }
54
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Electron app launcher — auto-detect, confirm, launch, and connect.
3
+ *
4
+ * Flow:
5
+ * 1. Probe CDP port → already running with debug? connect directly
6
+ * 2. Detect process → running without CDP? prompt to restart
7
+ * 3. Discover app path → not installed? error
8
+ * 4. Launch with --remote-debugging-port
9
+ * 5. Poll /json until ready
10
+ */
11
+ /**
12
+ * Probe whether a CDP endpoint is listening on the given port.
13
+ * Returns true if http://127.0.0.1:{port}/json responds successfully.
14
+ */
15
+ export declare function probeCDP(port: number, timeoutMs?: number): Promise<boolean>;
16
+ /**
17
+ * Check if a process with the given name is running.
18
+ * Uses pgrep on macOS/Linux.
19
+ */
20
+ export declare function detectProcess(processName: string): boolean;
21
+ /**
22
+ * Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
23
+ */
24
+ export declare function killProcess(processName: string): void;
25
+ /**
26
+ * Discover the app installation path on macOS.
27
+ * Uses osascript to resolve the app name to a POSIX path.
28
+ * Returns null if the app is not installed.
29
+ */
30
+ export declare function discoverAppPath(displayName: string): string | null;
31
+ /**
32
+ * Main entry point: resolve an Electron app to a CDP endpoint URL.
33
+ *
34
+ * Returns the endpoint URL: http://127.0.0.1:{port}
35
+ */
36
+ export declare function resolveElectronEndpoint(site: string): Promise<string>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Electron app launcher — auto-detect, confirm, launch, and connect.
3
+ *
4
+ * Flow:
5
+ * 1. Probe CDP port → already running with debug? connect directly
6
+ * 2. Detect process → running without CDP? prompt to restart
7
+ * 3. Discover app path → not installed? error
8
+ * 4. Launch with --remote-debugging-port
9
+ * 5. Poll /json until ready
10
+ */
11
+ import { execFileSync, spawn } from 'node:child_process';
12
+ import { request as httpRequest } from 'node:http';
13
+ import { getElectronApp } from './electron-apps.js';
14
+ import { confirmPrompt } from './tui.js';
15
+ import { CommandExecutionError } from './errors.js';
16
+ import { log } from './logger.js';
17
+ const POLL_INTERVAL_MS = 500;
18
+ const POLL_TIMEOUT_MS = 15_000;
19
+ const PROBE_TIMEOUT_MS = 2_000;
20
+ const KILL_GRACE_MS = 3_000;
21
+ /**
22
+ * Probe whether a CDP endpoint is listening on the given port.
23
+ * Returns true if http://127.0.0.1:{port}/json responds successfully.
24
+ */
25
+ export function probeCDP(port, timeoutMs = PROBE_TIMEOUT_MS) {
26
+ return new Promise((resolve) => {
27
+ const req = httpRequest({ hostname: '127.0.0.1', port, path: '/json', method: 'GET', timeout: timeoutMs }, (res) => {
28
+ res.resume();
29
+ resolve(res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode < 300);
30
+ });
31
+ req.on('error', () => resolve(false));
32
+ req.on('timeout', () => { req.destroy(); resolve(false); });
33
+ req.end();
34
+ });
35
+ }
36
+ /**
37
+ * Check if a process with the given name is running.
38
+ * Uses pgrep on macOS/Linux.
39
+ */
40
+ export function detectProcess(processName) {
41
+ try {
42
+ execFileSync('pgrep', ['-x', processName], { encoding: 'utf-8', stdio: 'pipe' });
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
51
+ */
52
+ export function killProcess(processName) {
53
+ try {
54
+ execFileSync('pkill', ['-x', processName], { stdio: 'pipe' });
55
+ }
56
+ catch {
57
+ // Process may have already exited
58
+ }
59
+ const deadline = Date.now() + KILL_GRACE_MS;
60
+ while (Date.now() < deadline) {
61
+ if (!detectProcess(processName))
62
+ return;
63
+ execFileSync('sleep', ['0.2'], { stdio: 'pipe' });
64
+ }
65
+ try {
66
+ execFileSync('pkill', ['-9', '-x', processName], { stdio: 'pipe' });
67
+ }
68
+ catch {
69
+ // Ignore
70
+ }
71
+ }
72
+ /**
73
+ * Discover the app installation path on macOS.
74
+ * Uses osascript to resolve the app name to a POSIX path.
75
+ * Returns null if the app is not installed.
76
+ */
77
+ export function discoverAppPath(displayName) {
78
+ if (process.platform !== 'darwin') {
79
+ return null;
80
+ }
81
+ try {
82
+ const result = execFileSync('osascript', [
83
+ '-e', `POSIX path of (path to application "${displayName}")`,
84
+ ], { encoding: 'utf-8', stdio: 'pipe', timeout: 5_000 });
85
+ return result.trim().replace(/\/$/, '');
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ function resolveExecutable(appPath, processName) {
92
+ return `${appPath}/Contents/MacOS/${processName}`;
93
+ }
94
+ async function pollForReady(port) {
95
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
96
+ while (Date.now() < deadline) {
97
+ if (await probeCDP(port, 1_000))
98
+ return;
99
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
100
+ }
101
+ throw new CommandExecutionError(`App launched but CDP not available on port ${port} after ${POLL_TIMEOUT_MS / 1000}s`, 'The app may be slow to start. Try running the command again.');
102
+ }
103
+ /**
104
+ * Main entry point: resolve an Electron app to a CDP endpoint URL.
105
+ *
106
+ * Returns the endpoint URL: http://127.0.0.1:{port}
107
+ */
108
+ export async function resolveElectronEndpoint(site) {
109
+ const app = getElectronApp(site);
110
+ if (!app) {
111
+ throw new CommandExecutionError(`No Electron app registered for site "${site}"`, 'Register the app in ~/.opencli/apps.yaml or check the site name.');
112
+ }
113
+ const { port, processName, displayName } = app;
114
+ const label = displayName ?? processName;
115
+ const endpoint = `http://127.0.0.1:${port}`;
116
+ // Step 1: Already running with CDP?
117
+ log.debug(`[launcher] Probing CDP on port ${port}...`);
118
+ if (await probeCDP(port)) {
119
+ log.debug(`[launcher] CDP already available on port ${port}`);
120
+ return endpoint;
121
+ }
122
+ // Step 2: Running without CDP?
123
+ const isRunning = detectProcess(processName);
124
+ if (isRunning) {
125
+ log.debug(`[launcher] ${label} is running but CDP not available`);
126
+ const confirmed = await confirmPrompt(`${label} is running but CDP is not enabled. Restart with debug port?`, true);
127
+ if (!confirmed) {
128
+ throw new CommandExecutionError(`${label} needs to be restarted with CDP enabled.`, `Manually restart: kill the app and relaunch with --remote-debugging-port=${port}`);
129
+ }
130
+ process.stderr.write(` Restarting ${label}...\n`);
131
+ killProcess(processName);
132
+ }
133
+ // Step 3: Discover path
134
+ const appPath = discoverAppPath(label);
135
+ if (!appPath) {
136
+ throw new CommandExecutionError(`Could not find ${label} on this machine.`, `Install ${label} or register a custom path in ~/.opencli/apps.yaml`);
137
+ }
138
+ // Step 4: Launch
139
+ const executable = resolveExecutable(appPath, processName);
140
+ const args = [`--remote-debugging-port=${port}`, ...(app.extraArgs ?? [])];
141
+ log.debug(`[launcher] Launching: ${executable} ${args.join(' ')}`);
142
+ const child = spawn(executable, args, {
143
+ detached: true,
144
+ stdio: 'ignore',
145
+ });
146
+ child.unref();
147
+ // Step 5: Poll for readiness
148
+ process.stderr.write(` Waiting for ${label} on port ${port}...\n`);
149
+ await pollForReady(port);
150
+ process.stderr.write(` Connected to ${label} on port ${port}.\n`);
151
+ return endpoint;
152
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { probeCDP, detectProcess, discoverAppPath } from './launcher.js';
3
+ vi.mock('node:child_process', () => ({
4
+ execFileSync: vi.fn(),
5
+ spawn: vi.fn(() => ({
6
+ unref: vi.fn(),
7
+ pid: 12345,
8
+ on: vi.fn(),
9
+ })),
10
+ }));
11
+ const cp = vi.mocked(await import('node:child_process'));
12
+ describe('probeCDP', () => {
13
+ it('returns false when CDP endpoint is unreachable', async () => {
14
+ const result = await probeCDP(59999, 500);
15
+ expect(result).toBe(false);
16
+ });
17
+ });
18
+ describe('detectProcess', () => {
19
+ beforeEach(() => {
20
+ vi.restoreAllMocks();
21
+ });
22
+ it('returns false when pgrep finds no process', () => {
23
+ cp.execFileSync.mockImplementation(() => {
24
+ const err = new Error('exit 1');
25
+ err.status = 1;
26
+ throw err;
27
+ });
28
+ const result = detectProcess('NonExistentApp');
29
+ expect(result).toBe(false);
30
+ });
31
+ it('returns true when pgrep finds a process', () => {
32
+ cp.execFileSync.mockReturnValue('12345\n');
33
+ const result = detectProcess('Cursor');
34
+ expect(result).toBe(true);
35
+ });
36
+ });
37
+ describe('discoverAppPath', () => {
38
+ beforeEach(() => {
39
+ vi.restoreAllMocks();
40
+ });
41
+ it.skipIf(process.platform !== 'darwin')('returns path when osascript succeeds', () => {
42
+ cp.execFileSync.mockReturnValue('/Applications/Cursor.app/\n');
43
+ const result = discoverAppPath('Cursor');
44
+ expect(result).toBe('/Applications/Cursor.app');
45
+ });
46
+ it.skipIf(process.platform !== 'darwin')('returns null when osascript fails', () => {
47
+ cp.execFileSync.mockImplementation(() => {
48
+ throw new Error('app not found');
49
+ });
50
+ const result = discoverAppPath('NonExistent');
51
+ expect(result).toBeNull();
52
+ });
53
+ it.skipIf(process.platform === 'darwin')('returns null on non-darwin platform', () => {
54
+ const result = discoverAppPath('Cursor');
55
+ expect(result).toBeNull();
56
+ });
57
+ });
package/dist/main.js CHANGED
@@ -12,10 +12,9 @@ if (process.platform !== 'win32') {
12
12
  cur.add(p);
13
13
  process.env.PATH = [...cur].join(':');
14
14
  }
15
- import * as os from 'node:os';
16
15
  import * as path from 'node:path';
17
16
  import { fileURLToPath } from 'node:url';
18
- import { discoverClis, discoverPlugins } from './discovery.js';
17
+ import { discoverClis, discoverPlugins, ensureUserCliCompatShims, USER_CLIS_DIR } from './discovery.js';
19
18
  import { getCompletions } from './completion.js';
20
19
  import { runCli } from './cli.js';
21
20
  import { emitHook } from './hooks.js';
@@ -26,8 +25,9 @@ installNodeNetwork();
26
25
  const __filename = fileURLToPath(import.meta.url);
27
26
  const __dirname = path.dirname(__filename);
28
27
  const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
29
- const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
28
+ const USER_CLIS = USER_CLIS_DIR;
30
29
  // Sequential: plugins must run after built-in discovery so they can override built-in commands.
30
+ await ensureUserCliCompatShims();
31
31
  await discoverClis(BUILTIN_CLIS, USER_CLIS);
32
32
  await discoverPlugins();
33
33
  // Register exit hook: notice appears after command output (same as npm/gh/yarn)
@@ -26,6 +26,7 @@ export type CommandArgs = Record<string, any>;
26
26
  export interface CliCommand {
27
27
  site: string;
28
28
  name: string;
29
+ aliases?: string[];
29
30
  description: string;
30
31
  domain?: string;
31
32
  strategy?: Strategy;
package/dist/registry.js CHANGED
@@ -13,9 +13,11 @@ const _registry = globalThis.__opencli_registry__ ??= new Map();
13
13
  export function cli(opts) {
14
14
  const strategy = opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
15
15
  const browser = opts.browser ?? (strategy !== Strategy.PUBLIC);
16
+ const aliases = normalizeAliases(opts.aliases, opts.name);
16
17
  const cmd = {
17
18
  site: opts.site,
18
19
  name: opts.name,
20
+ aliases,
19
21
  description: opts.description ?? '',
20
22
  domain: opts.domain,
21
23
  strategy,
@@ -31,8 +33,7 @@ export function cli(opts) {
31
33
  replacedBy: opts.replacedBy,
32
34
  navigateBefore: opts.navigateBefore,
33
35
  };
34
- const key = fullName(cmd);
35
- _registry.set(key, cmd);
36
+ registerCommand(cmd);
36
37
  return cmd;
37
38
  }
38
39
  export function getRegistry() {
@@ -45,5 +46,32 @@ export function strategyLabel(cmd) {
45
46
  return cmd.strategy ?? Strategy.PUBLIC;
46
47
  }
47
48
  export function registerCommand(cmd) {
48
- _registry.set(fullName(cmd), cmd);
49
+ const canonicalKey = fullName(cmd);
50
+ const existing = _registry.get(canonicalKey);
51
+ if (existing) {
52
+ for (const [key, value] of _registry.entries()) {
53
+ if (value === existing && key !== canonicalKey)
54
+ _registry.delete(key);
55
+ }
56
+ }
57
+ const aliases = normalizeAliases(cmd.aliases, cmd.name);
58
+ cmd.aliases = aliases.length > 0 ? aliases : undefined;
59
+ _registry.set(canonicalKey, cmd);
60
+ for (const alias of aliases) {
61
+ _registry.set(`${cmd.site}/${alias}`, cmd);
62
+ }
63
+ }
64
+ function normalizeAliases(aliases, commandName) {
65
+ if (!Array.isArray(aliases) || aliases.length === 0)
66
+ return [];
67
+ const seen = new Set();
68
+ const normalized = [];
69
+ for (const alias of aliases) {
70
+ const value = typeof alias === 'string' ? alias.trim() : '';
71
+ if (!value || value === commandName || seen.has(value))
72
+ continue;
73
+ seen.add(value);
74
+ normalized.push(value);
75
+ }
76
+ return normalized;
49
77
  }
@@ -49,6 +49,19 @@ describe('cli() registration', () => {
49
49
  const reg = getRegistry();
50
50
  expect(reg.get('test-registry/overwrite')?.description).toBe('v2');
51
51
  });
52
+ it('registers aliases as alternate registry keys for the same command', () => {
53
+ const cmd = cli({
54
+ site: 'test-registry',
55
+ name: 'canonical',
56
+ description: 'test aliases',
57
+ aliases: ['compat', 'legacy-name'],
58
+ });
59
+ const registry = getRegistry();
60
+ expect(cmd.aliases).toEqual(['compat', 'legacy-name']);
61
+ expect(registry.get('test-registry/canonical')).toBe(cmd);
62
+ expect(registry.get('test-registry/compat')).toBe(cmd);
63
+ expect(registry.get('test-registry/legacy-name')).toBe(cmd);
64
+ });
52
65
  });
53
66
  describe('fullName', () => {
54
67
  it('returns site/name', () => {
package/dist/runtime.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { IPage } from './types.js';
2
2
  /**
3
- * Returns the appropriate browser factory based on environment config.
4
- * Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
3
+ * Returns the appropriate browser factory based on site type.
4
+ * Uses CDPBridge for registered Electron apps, otherwise BrowserBridge.
5
5
  */
6
- export declare function getBrowserFactory(): new () => IBrowserFactory;
6
+ export declare function getBrowserFactory(site?: string): new () => IBrowserFactory;
7
7
  export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
8
8
  export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
9
9
  export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;
@@ -26,9 +26,11 @@ export interface IBrowserFactory {
26
26
  connect(opts?: {
27
27
  timeout?: number;
28
28
  workspace?: string;
29
+ cdpEndpoint?: string;
29
30
  }): Promise<IPage>;
30
31
  close(): Promise<void>;
31
32
  }
32
33
  export declare function browserSession<T>(BrowserFactory: new () => IBrowserFactory, fn: (page: IPage) => Promise<T>, opts?: {
33
34
  workspace?: string;
35
+ cdpEndpoint?: string;
34
36
  }): Promise<T>;
package/dist/runtime.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { BrowserBridge, CDPBridge } from './browser/index.js';
2
2
  import { TimeoutError } from './errors.js';
3
+ import { isElectronApp } from './electron-apps.js';
3
4
  /**
4
- * Returns the appropriate browser factory based on environment config.
5
- * Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
5
+ * Returns the appropriate browser factory based on site type.
6
+ * Uses CDPBridge for registered Electron apps, otherwise BrowserBridge.
6
7
  */
7
- export function getBrowserFactory() {
8
- return process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
8
+ export function getBrowserFactory(site) {
9
+ if (site && isElectronApp(site))
10
+ return CDPBridge;
11
+ return BrowserBridge;
9
12
  }
10
13
  function parseEnvTimeout(envVar, fallback) {
11
14
  const raw = process.env[envVar];
@@ -45,7 +48,11 @@ export function withTimeoutMs(promise, timeoutMs, makeError = 'Operation timed o
45
48
  export async function browserSession(BrowserFactory, fn, opts = {}) {
46
49
  const mcp = new BrowserFactory();
47
50
  try {
48
- const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT, workspace: opts.workspace });
51
+ const page = await mcp.connect({
52
+ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT,
53
+ workspace: opts.workspace,
54
+ cdpEndpoint: opts.cdpEndpoint,
55
+ });
49
56
  return await fn(page);
50
57
  }
51
58
  finally {
@@ -21,6 +21,7 @@ export declare function serializeCommand(cmd: CliCommand): {
21
21
  command: string;
22
22
  site: string;
23
23
  name: string;
24
+ aliases: string[];
24
25
  description: string;
25
26
  strategy: string;
26
27
  browser: boolean;