@jackwener/opencli 1.0.6 → 1.1.1

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 (400) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
  2. package/.github/pull_request_template.md +7 -0
  3. package/.github/workflows/doc-check.yml +36 -0
  4. package/.github/workflows/docs.yml +7 -42
  5. package/CHANGELOG.md +23 -0
  6. package/CLI-EXPLORER.md +9 -8
  7. package/README.md +51 -10
  8. package/README.zh-CN.md +29 -11
  9. package/SKILL.md +102 -33
  10. package/dist/browser/cdp.js +6 -1
  11. package/dist/browser/page.d.ts +4 -1
  12. package/dist/browser/page.js +7 -1
  13. package/dist/build-manifest.js +23 -16
  14. package/dist/cli-manifest.json +951 -296
  15. package/dist/cli.d.ts +6 -0
  16. package/dist/cli.js +225 -148
  17. package/dist/clis/antigravity/serve.js +296 -47
  18. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  19. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  20. package/dist/clis/apple-podcasts/search.js +2 -2
  21. package/dist/clis/apple-podcasts/top.js +9 -2
  22. package/dist/clis/arxiv/paper.js +21 -0
  23. package/dist/clis/arxiv/search.js +24 -0
  24. package/dist/clis/arxiv/utils.d.ts +18 -0
  25. package/dist/clis/arxiv/utils.js +49 -0
  26. package/dist/clis/bilibili/dynamic.js +1 -1
  27. package/dist/clis/bilibili/favorite.js +1 -1
  28. package/dist/clis/bilibili/feed.js +1 -1
  29. package/dist/clis/bilibili/following.js +1 -1
  30. package/dist/clis/bilibili/history.js +1 -1
  31. package/dist/clis/bilibili/me.js +1 -1
  32. package/dist/clis/bilibili/ranking.js +1 -1
  33. package/dist/clis/bilibili/search.js +3 -3
  34. package/dist/clis/bilibili/subtitle.js +1 -1
  35. package/dist/clis/bilibili/user-videos.js +1 -1
  36. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  37. package/dist/clis/bloomberg/businessweek.d.ts +1 -0
  38. package/dist/clis/bloomberg/businessweek.js +17 -0
  39. package/dist/clis/bloomberg/economics.d.ts +1 -0
  40. package/dist/clis/bloomberg/economics.js +17 -0
  41. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  42. package/dist/clis/bloomberg/feeds.js +15 -0
  43. package/dist/clis/bloomberg/industries.d.ts +1 -0
  44. package/dist/clis/bloomberg/industries.js +17 -0
  45. package/dist/clis/bloomberg/main.d.ts +1 -0
  46. package/dist/clis/bloomberg/main.js +17 -0
  47. package/dist/clis/bloomberg/markets.d.ts +1 -0
  48. package/dist/clis/bloomberg/markets.js +17 -0
  49. package/dist/clis/bloomberg/news.d.ts +1 -0
  50. package/dist/clis/bloomberg/news.js +105 -0
  51. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  52. package/dist/clis/bloomberg/opinions.js +17 -0
  53. package/dist/clis/bloomberg/politics.d.ts +1 -0
  54. package/dist/clis/bloomberg/politics.js +17 -0
  55. package/dist/clis/bloomberg/tech.d.ts +1 -0
  56. package/dist/clis/bloomberg/tech.js +17 -0
  57. package/dist/clis/bloomberg/utils.d.ts +34 -0
  58. package/dist/clis/bloomberg/utils.js +364 -0
  59. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  60. package/dist/clis/bloomberg/utils.test.js +129 -0
  61. package/dist/clis/boss/batchgreet.d.ts +1 -0
  62. package/dist/clis/boss/batchgreet.js +147 -0
  63. package/dist/clis/boss/chatlist.js +2 -2
  64. package/dist/clis/boss/detail.js +2 -2
  65. package/dist/clis/boss/exchange.d.ts +1 -0
  66. package/dist/clis/boss/exchange.js +111 -0
  67. package/dist/clis/boss/greet.d.ts +1 -0
  68. package/dist/clis/boss/greet.js +175 -0
  69. package/dist/clis/boss/invite.d.ts +1 -0
  70. package/dist/clis/boss/invite.js +158 -0
  71. package/dist/clis/boss/joblist.d.ts +1 -0
  72. package/dist/clis/boss/joblist.js +55 -0
  73. package/dist/clis/boss/mark.d.ts +1 -0
  74. package/dist/clis/boss/mark.js +141 -0
  75. package/dist/clis/boss/recommend.d.ts +1 -0
  76. package/dist/clis/boss/recommend.js +83 -0
  77. package/dist/clis/boss/search.js +1 -1
  78. package/dist/clis/boss/send.js +1 -1
  79. package/dist/clis/boss/stats.d.ts +1 -0
  80. package/dist/clis/boss/stats.js +116 -0
  81. package/dist/clis/chaoxing/assignments.js +1 -1
  82. package/dist/clis/chaoxing/exams.js +1 -1
  83. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  84. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  85. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  86. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  87. package/dist/clis/chatgpt/read.js +1 -1
  88. package/dist/clis/chatwise/export.js +1 -1
  89. package/dist/clis/chatwise/model.js +2 -2
  90. package/dist/clis/chatwise/screenshot.js +1 -1
  91. package/dist/clis/codex/export.js +1 -1
  92. package/dist/clis/codex/model.js +2 -2
  93. package/dist/clis/codex/screenshot.js +1 -1
  94. package/dist/clis/coupang/add-to-cart.js +3 -4
  95. package/dist/clis/coupang/search.js +2 -4
  96. package/dist/clis/coupang/utils.test.d.ts +1 -0
  97. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  98. package/dist/clis/ctrip/search.js +1 -1
  99. package/dist/clis/cursor/export.js +1 -1
  100. package/dist/clis/cursor/model.js +2 -2
  101. package/dist/clis/cursor/screenshot.js +1 -1
  102. package/dist/clis/jike/comment.js +2 -3
  103. package/dist/clis/jike/create.js +1 -2
  104. package/dist/clis/jike/feed.js +0 -1
  105. package/dist/clis/jike/like.js +1 -2
  106. package/dist/clis/jike/notifications.js +0 -1
  107. package/dist/clis/jike/post.yaml +1 -0
  108. package/dist/clis/jike/repost.js +1 -2
  109. package/dist/clis/jike/search.js +2 -3
  110. package/dist/clis/jike/topic.yaml +1 -0
  111. package/dist/clis/jike/user.yaml +1 -0
  112. package/dist/clis/jimeng/history.yaml +0 -1
  113. package/dist/clis/linkedin/search.js +7 -7
  114. package/dist/clis/linux-do/category.yaml +1 -0
  115. package/dist/clis/linux-do/search.yaml +4 -3
  116. package/dist/clis/linux-do/topic.yaml +1 -0
  117. package/dist/clis/notion/export.js +1 -1
  118. package/dist/clis/reddit/comment.js +3 -4
  119. package/dist/clis/reddit/read.js +4 -5
  120. package/dist/clis/reddit/save.js +2 -3
  121. package/dist/clis/reddit/saved.js +0 -1
  122. package/dist/clis/reddit/search.yaml +1 -0
  123. package/dist/clis/reddit/subscribe.js +0 -1
  124. package/dist/clis/reddit/upvote.js +2 -3
  125. package/dist/clis/reddit/upvoted.js +0 -1
  126. package/dist/clis/reddit/user-comments.yaml +1 -0
  127. package/dist/clis/reddit/user-posts.yaml +1 -0
  128. package/dist/clis/reddit/user.yaml +1 -0
  129. package/dist/clis/reuters/search.js +1 -1
  130. package/dist/clis/sinafinance/news.d.ts +7 -0
  131. package/dist/clis/sinafinance/news.js +61 -0
  132. package/dist/clis/smzdm/search.js +2 -3
  133. package/dist/clis/stackoverflow/search.yaml +1 -0
  134. package/dist/clis/steam/top-sellers.yaml +29 -0
  135. package/dist/clis/twitter/accept.js +2 -2
  136. package/dist/clis/twitter/article.js +2 -2
  137. package/dist/clis/twitter/block.d.ts +1 -0
  138. package/dist/clis/twitter/block.js +88 -0
  139. package/dist/clis/twitter/delete.js +1 -1
  140. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  141. package/dist/clis/twitter/hide-reply.js +66 -0
  142. package/dist/clis/twitter/like.js +1 -1
  143. package/dist/clis/twitter/post.js +1 -1
  144. package/dist/clis/twitter/reply-dm.js +1 -1
  145. package/dist/clis/twitter/reply.js +2 -2
  146. package/dist/clis/twitter/search.js +1 -1
  147. package/dist/clis/twitter/thread.js +2 -2
  148. package/dist/clis/twitter/trending.d.ts +1 -0
  149. package/dist/clis/twitter/trending.js +91 -0
  150. package/dist/clis/twitter/unblock.d.ts +1 -0
  151. package/dist/clis/twitter/unblock.js +71 -0
  152. package/dist/clis/v2ex/topic.yaml +1 -0
  153. package/dist/clis/weibo/hot.js +0 -1
  154. package/dist/clis/weread/book.js +1 -1
  155. package/dist/clis/weread/highlights.js +1 -1
  156. package/dist/clis/weread/notes.js +1 -1
  157. package/dist/clis/weread/search.js +1 -1
  158. package/dist/clis/wikipedia/search.d.ts +1 -0
  159. package/dist/clis/wikipedia/search.js +30 -0
  160. package/dist/clis/wikipedia/summary.d.ts +1 -0
  161. package/dist/clis/wikipedia/summary.js +28 -0
  162. package/dist/clis/wikipedia/utils.d.ts +8 -0
  163. package/dist/clis/wikipedia/utils.js +18 -0
  164. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +79 -5
  165. package/dist/clis/xiaohongshu/creator-note-detail.js +323 -70
  166. package/dist/clis/xiaohongshu/creator-note-detail.test.d.ts +1 -0
  167. package/dist/clis/xiaohongshu/creator-note-detail.test.js +258 -0
  168. package/dist/clis/xiaohongshu/creator-notes-summary.d.ts +28 -0
  169. package/dist/clis/xiaohongshu/creator-notes-summary.js +92 -0
  170. package/dist/clis/xiaohongshu/creator-notes-summary.test.d.ts +1 -0
  171. package/dist/clis/xiaohongshu/creator-notes-summary.test.js +49 -0
  172. package/dist/clis/xiaohongshu/creator-notes.d.ts +18 -5
  173. package/dist/clis/xiaohongshu/creator-notes.js +189 -71
  174. package/dist/clis/xiaohongshu/creator-notes.test.d.ts +1 -0
  175. package/dist/clis/xiaohongshu/creator-notes.test.js +191 -0
  176. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  177. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  178. package/dist/clis/xiaohongshu/download.js +2 -3
  179. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  180. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  181. package/dist/clis/xiaohongshu/search.js +2 -2
  182. package/dist/clis/xiaohongshu/user.js +1 -2
  183. package/dist/clis/yahoo-finance/quote.js +0 -1
  184. package/dist/clis/youtube/search.js +1 -1
  185. package/dist/clis/youtube/transcript.js +1 -1
  186. package/dist/clis/youtube/video.js +1 -1
  187. package/dist/clis/zhihu/download.js +1 -2
  188. package/dist/clis/zhihu/question.js +1 -1
  189. package/dist/clis/zhihu/search.yaml +4 -3
  190. package/dist/commanderAdapter.d.ts +21 -0
  191. package/dist/commanderAdapter.js +111 -0
  192. package/dist/{engine.d.ts → discovery.d.ts} +0 -6
  193. package/dist/{engine.js → discovery.js} +1 -98
  194. package/dist/download/index.d.ts +2 -6
  195. package/dist/download/index.js +19 -46
  196. package/dist/engine.test.d.ts +1 -1
  197. package/dist/engine.test.js +8 -7
  198. package/dist/execution.d.ts +22 -0
  199. package/dist/execution.js +129 -0
  200. package/dist/explore.js +121 -107
  201. package/dist/external-clis.yaml +48 -0
  202. package/dist/external.d.ts +25 -0
  203. package/dist/external.js +156 -0
  204. package/dist/main.js +1 -1
  205. package/dist/pipeline/steps/browser.js +8 -2
  206. package/dist/registry.d.ts +2 -0
  207. package/dist/registry.js +2 -0
  208. package/dist/runtime.d.ts +5 -0
  209. package/dist/runtime.js +8 -0
  210. package/dist/serialization.d.ts +34 -0
  211. package/dist/serialization.js +63 -0
  212. package/dist/types.d.ts +4 -1
  213. package/docs/.vitepress/config.mts +14 -3
  214. package/docs/adapters/browser/arxiv.md +27 -0
  215. package/docs/adapters/browser/barchart.md +32 -0
  216. package/docs/adapters/browser/bloomberg.md +70 -0
  217. package/docs/adapters/browser/chaoxing.md +39 -0
  218. package/docs/adapters/browser/grok.md +35 -0
  219. package/docs/adapters/browser/hf.md +42 -0
  220. package/docs/adapters/browser/jike.md +45 -0
  221. package/docs/adapters/browser/jimeng.md +39 -0
  222. package/docs/adapters/browser/linux-do.md +45 -0
  223. package/docs/adapters/browser/sinafinance.md +35 -0
  224. package/docs/adapters/browser/stackoverflow.md +35 -0
  225. package/docs/adapters/browser/steam.md +26 -0
  226. package/docs/adapters/browser/twitter.md +3 -0
  227. package/docs/adapters/browser/weread.md +48 -0
  228. package/docs/adapters/browser/wikipedia.md +30 -0
  229. package/docs/adapters/browser/xiaohongshu.md +5 -1
  230. package/docs/adapters/desktop/chatgpt.md +3 -3
  231. package/docs/adapters/index.md +13 -0
  232. package/docs/advanced/download.md +4 -4
  233. package/docs/developer/architecture.md +17 -4
  234. package/package.json +1 -1
  235. package/scripts/check-doc-coverage.sh +69 -0
  236. package/scripts/copy-yaml.cjs +7 -0
  237. package/src/browser/cdp.ts +9 -4
  238. package/src/browser/page.ts +7 -1
  239. package/src/build-manifest.ts +25 -19
  240. package/src/cli.ts +253 -119
  241. package/src/clis/antigravity/serve.ts +323 -50
  242. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  243. package/src/clis/apple-podcasts/search.ts +2 -2
  244. package/src/clis/apple-podcasts/top.ts +12 -2
  245. package/src/clis/arxiv/paper.ts +21 -0
  246. package/src/clis/arxiv/search.ts +24 -0
  247. package/src/clis/arxiv/utils.ts +63 -0
  248. package/src/clis/bilibili/dynamic.ts +1 -1
  249. package/src/clis/bilibili/favorite.ts +1 -1
  250. package/src/clis/bilibili/feed.ts +1 -1
  251. package/src/clis/bilibili/following.ts +1 -1
  252. package/src/clis/bilibili/history.ts +1 -1
  253. package/src/clis/bilibili/me.ts +1 -1
  254. package/src/clis/bilibili/ranking.ts +1 -1
  255. package/src/clis/bilibili/search.ts +3 -3
  256. package/src/clis/bilibili/subtitle.ts +1 -1
  257. package/src/clis/bilibili/user-videos.ts +1 -1
  258. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  259. package/src/clis/bloomberg/businessweek.ts +18 -0
  260. package/src/clis/bloomberg/economics.ts +18 -0
  261. package/src/clis/bloomberg/feeds.ts +16 -0
  262. package/src/clis/bloomberg/industries.ts +18 -0
  263. package/src/clis/bloomberg/main.ts +18 -0
  264. package/src/clis/bloomberg/markets.ts +18 -0
  265. package/src/clis/bloomberg/news.ts +136 -0
  266. package/src/clis/bloomberg/opinions.ts +18 -0
  267. package/src/clis/bloomberg/politics.ts +18 -0
  268. package/src/clis/bloomberg/tech.ts +18 -0
  269. package/src/clis/bloomberg/utils.test.ts +135 -0
  270. package/src/clis/bloomberg/utils.ts +429 -0
  271. package/src/clis/boss/batchgreet.ts +167 -0
  272. package/src/clis/boss/chatlist.ts +2 -2
  273. package/src/clis/boss/detail.ts +2 -2
  274. package/src/clis/boss/exchange.ts +126 -0
  275. package/src/clis/boss/greet.ts +198 -0
  276. package/src/clis/boss/invite.ts +177 -0
  277. package/src/clis/boss/joblist.ts +63 -0
  278. package/src/clis/boss/mark.ts +155 -0
  279. package/src/clis/boss/recommend.ts +94 -0
  280. package/src/clis/boss/search.ts +1 -1
  281. package/src/clis/boss/send.ts +1 -1
  282. package/src/clis/boss/stats.ts +130 -0
  283. package/src/clis/chaoxing/assignments.ts +1 -1
  284. package/src/clis/chaoxing/exams.ts +1 -1
  285. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  286. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  287. package/src/clis/chatgpt/README.zh-CN.md +3 -3
  288. package/src/clis/chatgpt/read.ts +1 -1
  289. package/src/clis/chatwise/export.ts +1 -1
  290. package/src/clis/chatwise/model.ts +2 -2
  291. package/src/clis/chatwise/screenshot.ts +1 -1
  292. package/src/clis/codex/export.ts +1 -1
  293. package/src/clis/codex/model.ts +2 -2
  294. package/src/clis/codex/screenshot.ts +1 -1
  295. package/src/clis/coupang/add-to-cart.ts +3 -4
  296. package/src/clis/coupang/search.ts +2 -4
  297. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  298. package/src/clis/ctrip/search.ts +1 -1
  299. package/src/clis/cursor/export.ts +1 -1
  300. package/src/clis/cursor/model.ts +2 -2
  301. package/src/clis/cursor/screenshot.ts +1 -1
  302. package/src/clis/jike/comment.ts +2 -3
  303. package/src/clis/jike/create.ts +1 -2
  304. package/src/clis/jike/feed.ts +0 -1
  305. package/src/clis/jike/like.ts +1 -2
  306. package/src/clis/jike/notifications.ts +0 -1
  307. package/src/clis/jike/post.yaml +1 -0
  308. package/src/clis/jike/repost.ts +1 -2
  309. package/src/clis/jike/search.ts +2 -3
  310. package/src/clis/jike/topic.yaml +1 -0
  311. package/src/clis/jike/user.yaml +1 -0
  312. package/src/clis/jimeng/history.yaml +0 -1
  313. package/src/clis/linkedin/search.ts +7 -7
  314. package/src/clis/linux-do/category.yaml +1 -0
  315. package/src/clis/linux-do/search.yaml +4 -3
  316. package/src/clis/linux-do/topic.yaml +1 -0
  317. package/src/clis/notion/export.ts +1 -1
  318. package/src/clis/reddit/comment.ts +3 -4
  319. package/src/clis/reddit/read.ts +4 -5
  320. package/src/clis/reddit/save.ts +2 -3
  321. package/src/clis/reddit/saved.ts +0 -1
  322. package/src/clis/reddit/search.yaml +1 -0
  323. package/src/clis/reddit/subscribe.ts +0 -1
  324. package/src/clis/reddit/upvote.ts +2 -3
  325. package/src/clis/reddit/upvoted.ts +0 -1
  326. package/src/clis/reddit/user-comments.yaml +1 -0
  327. package/src/clis/reddit/user-posts.yaml +1 -0
  328. package/src/clis/reddit/user.yaml +1 -0
  329. package/src/clis/reuters/search.ts +1 -1
  330. package/src/clis/sinafinance/news.ts +76 -0
  331. package/src/clis/smzdm/search.ts +2 -3
  332. package/src/clis/stackoverflow/search.yaml +1 -0
  333. package/src/clis/steam/top-sellers.yaml +29 -0
  334. package/src/clis/twitter/accept.ts +2 -2
  335. package/src/clis/twitter/article.ts +2 -2
  336. package/src/clis/twitter/block.ts +92 -0
  337. package/src/clis/twitter/delete.ts +1 -1
  338. package/src/clis/twitter/hide-reply.ts +70 -0
  339. package/src/clis/twitter/like.ts +1 -1
  340. package/src/clis/twitter/post.ts +1 -1
  341. package/src/clis/twitter/reply-dm.ts +1 -1
  342. package/src/clis/twitter/reply.ts +2 -2
  343. package/src/clis/twitter/search.ts +1 -1
  344. package/src/clis/twitter/thread.ts +2 -2
  345. package/src/clis/twitter/trending.ts +113 -0
  346. package/src/clis/twitter/unblock.ts +75 -0
  347. package/src/clis/v2ex/topic.yaml +1 -0
  348. package/src/clis/weibo/hot.ts +0 -1
  349. package/src/clis/weread/book.ts +1 -1
  350. package/src/clis/weread/highlights.ts +1 -1
  351. package/src/clis/weread/notes.ts +1 -1
  352. package/src/clis/weread/search.ts +1 -1
  353. package/src/clis/wikipedia/search.ts +32 -0
  354. package/src/clis/wikipedia/summary.ts +28 -0
  355. package/src/clis/wikipedia/utils.ts +20 -0
  356. package/src/clis/xiaohongshu/creator-note-detail.test.ts +272 -0
  357. package/src/clis/xiaohongshu/creator-note-detail.ts +425 -73
  358. package/src/clis/xiaohongshu/creator-notes-summary.test.ts +54 -0
  359. package/src/clis/xiaohongshu/creator-notes-summary.ts +120 -0
  360. package/src/clis/xiaohongshu/creator-notes.test.ts +211 -0
  361. package/src/clis/xiaohongshu/creator-notes.ts +254 -75
  362. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  363. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  364. package/src/clis/xiaohongshu/download.ts +2 -3
  365. package/src/clis/xiaohongshu/feed.yaml +0 -1
  366. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  367. package/src/clis/xiaohongshu/search.ts +2 -2
  368. package/src/clis/xiaohongshu/user.ts +1 -2
  369. package/src/clis/yahoo-finance/quote.ts +0 -1
  370. package/src/clis/youtube/search.ts +1 -1
  371. package/src/clis/youtube/transcript.ts +1 -1
  372. package/src/clis/youtube/video.ts +1 -1
  373. package/src/clis/zhihu/download.ts +1 -2
  374. package/src/clis/zhihu/question.ts +1 -1
  375. package/src/clis/zhihu/search.yaml +4 -3
  376. package/src/commanderAdapter.ts +113 -0
  377. package/src/daemon.ts +3 -3
  378. package/src/{engine.ts → discovery.ts} +1 -108
  379. package/src/download/index.ts +21 -54
  380. package/src/engine.test.ts +8 -7
  381. package/src/execution.ts +138 -0
  382. package/src/explore.ts +135 -109
  383. package/src/external-clis.yaml +48 -0
  384. package/src/external.ts +185 -0
  385. package/src/main.ts +1 -1
  386. package/src/pipeline/steps/browser.ts +7 -2
  387. package/src/registry.ts +5 -0
  388. package/src/runtime.ts +9 -0
  389. package/src/serialization.ts +79 -0
  390. package/src/types.ts +1 -1
  391. package/tests/e2e/browser-public.test.ts +25 -0
  392. package/tests/e2e/public-commands.test.ts +55 -1
  393. package/dist/clis/twitter/trending.yaml +0 -46
  394. package/src/clis/twitter/trending.yaml +0 -46
  395. /package/dist/{chaoxing.test.d.ts → clis/arxiv/paper.d.ts} +0 -0
  396. /package/dist/{coupang.test.d.ts → clis/arxiv/search.d.ts} +0 -0
  397. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  398. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  399. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  400. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
package/src/cli.ts CHANGED
@@ -1,62 +1,119 @@
1
+ /**
2
+ * CLI entry point: registers built-in commands and wires up Commander.
3
+ *
4
+ * Built-in commands are registered inline here (list, validate, explore, etc.).
5
+ * Dynamic adapter commands are registered via commanderAdapter.ts.
6
+ */
7
+
1
8
  import { Command } from 'commander';
2
9
  import chalk from 'chalk';
3
- import { executeCommand } from './engine.js';
4
- import { Strategy, type CliCommand, fullName, getRegistry, strategyLabel } from './registry.js';
10
+ import { type CliCommand, fullName, getRegistry, strategyLabel } from './registry.js';
11
+ import { serializeCommand, formatArgSummary } from './serialization.js';
5
12
  import { render as renderOutput } from './output.js';
6
- import { BrowserBridge, CDPBridge } from './browser/index.js';
7
- import { browserSession, DEFAULT_BROWSER_COMMAND_TIMEOUT, runWithTimeout } from './runtime.js';
13
+ import { getBrowserFactory, browserSession } from './runtime.js';
8
14
  import { PKG_VERSION } from './version.js';
9
15
  import { printCompletionScript } from './completion.js';
10
- import { CliError } from './errors.js';
11
- import { shouldUseBrowserSession } from './capabilityRouting.js';
16
+ import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
17
+ import { registerAllCommands } from './commanderAdapter.js';
12
18
 
13
19
  export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
14
20
  const program = new Command();
15
- program.name('opencli').description('Make any website your CLI. Zero setup. AI-powered.').version(PKG_VERSION);
21
+ // enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
22
+ // prerequisite for passThroughOptions to forward --help/--version to external binaries
23
+ program
24
+ .name('opencli')
25
+ .description('Make any website your CLI. Zero setup. AI-powered.')
26
+ .version(PKG_VERSION)
27
+ .enablePositionalOptions();
16
28
 
17
- // ── Built-in commands ──────────────────────────────────────────────────────
29
+ // ── Built-in: list ────────────────────────────────────────────────────────
18
30
 
19
- program.command('list').description('List all available CLI commands').option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('--json', 'JSON output (deprecated)')
31
+ program
32
+ .command('list')
33
+ .description('List all available CLI commands')
34
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
35
+ .option('--json', 'JSON output (deprecated)')
20
36
  .action((opts) => {
21
37
  const registry = getRegistry();
22
38
  const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
23
- const rows = commands.map(c => ({
24
- command: fullName(c),
25
- site: c.site,
26
- name: c.name,
27
- description: c.description,
28
- strategy: strategyLabel(c),
29
- browser: c.browser,
30
- args: c.args.map(a => a.name).join(', '),
31
- }));
32
39
  const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
40
+ const isStructured = fmt === 'json' || fmt === 'yaml';
41
+
33
42
  if (fmt !== 'table') {
43
+ const rows = isStructured
44
+ ? commands.map(serializeCommand)
45
+ : commands.map(c => ({
46
+ command: fullName(c),
47
+ site: c.site,
48
+ name: c.name,
49
+ description: c.description,
50
+ strategy: strategyLabel(c),
51
+ browser: !!c.browser,
52
+ args: formatArgSummary(c.args),
53
+ }));
34
54
  renderOutput(rows, {
35
55
  fmt,
36
- columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args'],
56
+ columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args',
57
+ ...(isStructured ? ['columns', 'domain'] : [])],
37
58
  title: 'opencli/list',
38
59
  source: 'opencli list',
39
60
  });
40
61
  return;
41
62
  }
63
+
64
+ // Table (default) — grouped by site
42
65
  const sites = new Map<string, CliCommand[]>();
43
- for (const cmd of commands) { const g = sites.get(cmd.site) ?? []; g.push(cmd); sites.set(cmd.site, g); }
44
- console.log(); console.log(chalk.bold(' opencli') + chalk.dim(' — available commands')); console.log();
66
+ for (const cmd of commands) {
67
+ const g = sites.get(cmd.site) ?? [];
68
+ g.push(cmd);
69
+ sites.set(cmd.site, g);
70
+ }
71
+
72
+ console.log();
73
+ console.log(chalk.bold(' opencli') + chalk.dim(' — available commands'));
74
+ console.log();
45
75
  for (const [site, cmds] of sites) {
46
76
  console.log(chalk.bold.cyan(` ${site}`));
47
- for (const cmd of cmds) { const tag = strategyLabel(cmd) === 'public' ? chalk.green('[public]') : chalk.yellow(`[${strategyLabel(cmd)}]`); console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`); }
77
+ for (const cmd of cmds) {
78
+ const tag = strategyLabel(cmd) === 'public'
79
+ ? chalk.green('[public]')
80
+ : chalk.yellow(`[${strategyLabel(cmd)}]`);
81
+ console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
82
+ }
48
83
  console.log();
49
84
  }
50
- console.log(chalk.dim(` ${commands.length} commands across ${sites.size} sites`)); console.log();
85
+
86
+ const externalClis = loadExternalClis();
87
+ if (externalClis.length > 0) {
88
+ console.log(chalk.bold.cyan(' external CLIs'));
89
+ for (const ext of externalClis) {
90
+ const isInstalled = isBinaryInstalled(ext.binary);
91
+ const tag = isInstalled ? chalk.green('[installed]') : chalk.yellow('[auto-install]');
92
+ console.log(` ${ext.name} ${tag}${ext.description ? chalk.dim(` — ${ext.description}`) : ''}`);
93
+ }
94
+ console.log();
95
+ }
96
+
97
+ console.log(chalk.dim(` ${commands.length} built-in commands across ${sites.size} sites, ${externalClis.length} external CLIs`));
98
+ console.log();
51
99
  });
52
100
 
53
- program.command('validate').description('Validate CLI definitions').argument('[target]', 'site or site/name')
101
+ // ── Built-in: validate / verify ───────────────────────────────────────────
102
+
103
+ program
104
+ .command('validate')
105
+ .description('Validate CLI definitions')
106
+ .argument('[target]', 'site or site/name')
54
107
  .action(async (target) => {
55
108
  const { validateClisWithTarget, renderValidationReport } = await import('./validate.js');
56
109
  console.log(renderValidationReport(validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target)));
57
110
  });
58
111
 
59
- program.command('verify').description('Validate + smoke test').argument('[target]').option('--smoke', 'Run smoke tests', false)
112
+ program
113
+ .command('verify')
114
+ .description('Validate + smoke test')
115
+ .argument('[target]')
116
+ .option('--smoke', 'Run smoke tests', false)
60
117
  .action(async (target, opts) => {
61
118
  const { verifyClis, renderVerifyReport } = await import('./verify.js');
62
119
  const r = await verifyClis({ builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, target, smoke: opts.smoke });
@@ -64,28 +121,91 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
64
121
  process.exitCode = r.ok ? 0 : 1;
65
122
  });
66
123
 
67
- program.command('explore').alias('probe').description('Explore a website: discover APIs, stores, and recommend strategies').argument('<url>').option('--site <name>').option('--goal <text>').option('--wait <s>', '', '3').option('--auto', 'Enable interactive fuzzing (simulate clicks to trigger lazy APIs)').option('--click <labels>', 'Comma-separated labels to click before fuzzing (e.g. "字幕,CC,评论")')
68
- .action(async (url, opts) => { const { exploreUrl, renderExploreSummary } = await import('./explore.js'); const clickLabels = opts.click ? opts.click.split(',').map((s: string) => s.trim()) : undefined; const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; const workspace = `explore:${opts.site ?? (() => { try { return new URL(url).host; } catch { return 'default'; } })()}`; console.log(renderExploreSummary(await exploreUrl(url, { BrowserFactory: BrowserFactory as any, site: opts.site, goal: opts.goal, waitSeconds: parseFloat(opts.wait), auto: opts.auto, clickLabels, workspace }))); });
124
+ // ── Built-in: explore / synthesize / generate / cascade ───────────────────
69
125
 
70
- program.command('synthesize').description('Synthesize CLIs from explore').argument('<target>').option('--top <n>', '', '3')
71
- .action(async (target, opts) => { const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js'); console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) }))); });
126
+ program
127
+ .command('explore')
128
+ .alias('probe')
129
+ .description('Explore a website: discover APIs, stores, and recommend strategies')
130
+ .argument('<url>')
131
+ .option('--site <name>')
132
+ .option('--goal <text>')
133
+ .option('--wait <s>', '', '3')
134
+ .option('--auto', 'Enable interactive fuzzing')
135
+ .option('--click <labels>', 'Comma-separated labels to click before fuzzing')
136
+ .action(async (url, opts) => {
137
+ const { exploreUrl, renderExploreSummary } = await import('./explore.js');
138
+ const clickLabels = opts.click
139
+ ? opts.click.split(',').map((s: string) => s.trim())
140
+ : undefined;
141
+ const workspace = `explore:${inferHost(url, opts.site)}`;
142
+ const result = await exploreUrl(url, {
143
+ BrowserFactory: getBrowserFactory() as any,
144
+ site: opts.site,
145
+ goal: opts.goal,
146
+ waitSeconds: parseFloat(opts.wait),
147
+ auto: opts.auto,
148
+ clickLabels,
149
+ workspace,
150
+ });
151
+ console.log(renderExploreSummary(result));
152
+ });
153
+
154
+ program
155
+ .command('synthesize')
156
+ .description('Synthesize CLIs from explore')
157
+ .argument('<target>')
158
+ .option('--top <n>', '', '3')
159
+ .action(async (target, opts) => {
160
+ const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js');
161
+ console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) })));
162
+ });
72
163
 
73
- program.command('generate').description('One-shot: explore → synthesize → register').argument('<url>').option('--goal <text>').option('--site <name>')
74
- .action(async (url, opts) => { const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js'); const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; const workspace = `generate:${opts.site ?? (() => { try { return new URL(url).host; } catch { return 'default'; } })()}`; const r = await generateCliFromUrl({ url, BrowserFactory: BrowserFactory as any, builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, goal: opts.goal, site: opts.site, workspace }); console.log(renderGenerateSummary(r)); process.exitCode = r.ok ? 0 : 1; });
164
+ program
165
+ .command('generate')
166
+ .description('One-shot: explore → synthesize → register')
167
+ .argument('<url>')
168
+ .option('--goal <text>')
169
+ .option('--site <name>')
170
+ .action(async (url, opts) => {
171
+ const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js');
172
+ const workspace = `generate:${inferHost(url, opts.site)}`;
173
+ const r = await generateCliFromUrl({
174
+ url,
175
+ BrowserFactory: getBrowserFactory() as any,
176
+ builtinClis: BUILTIN_CLIS,
177
+ userClis: USER_CLIS,
178
+ goal: opts.goal,
179
+ site: opts.site,
180
+ workspace,
181
+ });
182
+ console.log(renderGenerateSummary(r));
183
+ process.exitCode = r.ok ? 0 : 1;
184
+ });
75
185
 
76
- program.command('cascade').description('Strategy cascade: find simplest working strategy').argument('<url>').option('--site <name>')
186
+ program
187
+ .command('cascade')
188
+ .description('Strategy cascade: find simplest working strategy')
189
+ .argument('<url>')
190
+ .option('--site <name>')
77
191
  .action(async (url, opts) => {
78
192
  const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
79
- const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
80
- const result = await browserSession(BrowserFactory as any, async (page) => {
81
- // Navigate to the site first for cookie context
82
- try { const siteUrl = new URL(url); await page.goto(`${siteUrl.protocol}//${siteUrl.host}`); await page.wait(2); } catch {}
193
+ const workspace = `cascade:${inferHost(url, opts.site)}`;
194
+ const result = await browserSession(getBrowserFactory(), async (page) => {
195
+ try {
196
+ const siteUrl = new URL(url);
197
+ await page.goto(`${siteUrl.protocol}//${siteUrl.host}`);
198
+ await page.wait(2);
199
+ } catch {}
83
200
  return cascadeProbe(page, url);
84
- }, { workspace: `cascade:${opts.site ?? (() => { try { return new URL(url).host; } catch { return 'default'; } })()}` });
201
+ }, { workspace });
85
202
  console.log(renderCascadeResult(result));
86
203
  });
87
204
 
88
- program.command('doctor')
205
+ // ── Built-in: doctor / setup / completion ─────────────────────────────────
206
+
207
+ program
208
+ .command('doctor')
89
209
  .description('Diagnose opencli browser bridge connectivity')
90
210
  .option('--live', 'Test browser connectivity (requires Chrome running)', false)
91
211
  .option('--sessions', 'Show active automation sessions', false)
@@ -95,24 +215,81 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
95
215
  console.log(renderBrowserDoctorReport(report));
96
216
  });
97
217
 
98
- program.command('setup')
218
+ program
219
+ .command('setup')
99
220
  .description('Interactive setup: verify browser bridge connectivity')
100
221
  .action(async () => {
101
222
  const { runSetup } = await import('./setup.js');
102
223
  await runSetup({ cliVersion: PKG_VERSION });
103
224
  });
104
225
 
105
- program.command('completion')
226
+ program
227
+ .command('completion')
106
228
  .description('Output shell completion script')
107
229
  .argument('<shell>', 'Shell type: bash, zsh, or fish')
108
230
  .action((shell) => {
109
231
  printCompletionScript(shell);
110
232
  });
111
233
 
112
- // ── Antigravity serve (built-in, long-running) ──────────────────────────────
234
+ // ── External CLIs ─────────────────────────────────────────────────────────
235
+
236
+ const externalClis = loadExternalClis();
237
+
238
+ program
239
+ .command('install')
240
+ .description('Install an external CLI')
241
+ .argument('<name>', 'Name of the external CLI')
242
+ .action((name: string) => {
243
+ const ext = externalClis.find(e => e.name === name);
244
+ if (!ext) {
245
+ console.error(chalk.red(`External CLI '${name}' not found in registry.`));
246
+ process.exitCode = 1;
247
+ return;
248
+ }
249
+ installExternalCli(ext);
250
+ });
251
+
252
+ program
253
+ .command('register')
254
+ .description('Register an external CLI')
255
+ .argument('<name>', 'Name of the CLI')
256
+ .option('--binary <bin>', 'Binary name if different from name')
257
+ .option('--install <cmd>', 'Auto-install command')
258
+ .option('--desc <text>', 'Description')
259
+ .action((name, opts) => {
260
+ registerExternalCli(name, { binary: opts.binary, install: opts.install, description: opts.desc });
261
+ });
262
+
263
+ function passthroughExternal(name: string, parsedArgs?: string[]) {
264
+ const args = parsedArgs ?? (() => {
265
+ const idx = process.argv.indexOf(name);
266
+ return process.argv.slice(idx + 1);
267
+ })();
268
+ try {
269
+ executeExternalCli(name, args, externalClis);
270
+ } catch (err: any) {
271
+ console.error(chalk.red(`Error: ${err.message}`));
272
+ process.exitCode = 1;
273
+ }
274
+ }
275
+
276
+ for (const ext of externalClis) {
277
+ if (program.commands.some(c => c.name() === ext.name)) continue;
278
+ program
279
+ .command(ext.name)
280
+ .description(`(External) ${ext.description || ext.name}`)
281
+ .argument('[args...]')
282
+ .allowUnknownOption()
283
+ .passThroughOptions()
284
+ .helpOption(false)
285
+ .action((args: string[]) => passthroughExternal(ext.name, args));
286
+ }
287
+
288
+ // ── Antigravity serve (long-running, special case) ────────────────────────
113
289
 
114
290
  const antigravityCmd = program.command('antigravity').description('antigravity commands');
115
- antigravityCmd.command('serve')
291
+ antigravityCmd
292
+ .command('serve')
116
293
  .description('Start Anthropic-compatible API proxy for Antigravity')
117
294
  .option('--port <port>', 'Server port (default: 8082)', '8082')
118
295
  .action(async (opts) => {
@@ -120,88 +297,45 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
120
297
  await startServe({ port: parseInt(opts.port) });
121
298
  });
122
299
 
123
- // ── Dynamic site commands ──────────────────────────────────────────────────
300
+ // ── Dynamic adapter commands ──────────────────────────────────────────────
124
301
 
125
- const registry = getRegistry();
126
302
  const siteGroups = new Map<string, Command>();
127
- // Pre-seed with the antigravity command registered above to avoid duplicates
128
303
  siteGroups.set('antigravity', antigravityCmd);
304
+ registerAllCommands(program, siteGroups);
129
305
 
130
- for (const [, cmd] of registry) {
131
- let siteCmd = siteGroups.get(cmd.site);
132
- if (!siteCmd) { siteCmd = program.command(cmd.site).description(`${cmd.site} commands`); siteGroups.set(cmd.site, siteCmd); }
133
- // Skip if this subcommand was already hardcoded (e.g. antigravity serve)
134
- if (siteCmd.commands.some((c: Command) => c.name() === cmd.name)) continue;
135
- const subCmd = siteCmd.command(cmd.name).description(cmd.description);
136
-
137
- // Register positional args first, then named options
138
- const positionalArgs: typeof cmd.args = [];
139
- for (const arg of cmd.args) {
140
- if (arg.positional) {
141
- const bracket = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
142
- subCmd.argument(bracket, arg.help ?? '');
143
- positionalArgs.push(arg);
144
- } else {
145
- const flag = arg.required ? `--${arg.name} <value>` : `--${arg.name} [value]`;
146
- if (arg.required) subCmd.requiredOption(flag, arg.help ?? '');
147
- else if (arg.default != null) subCmd.option(flag, arg.help ?? '', String(arg.default));
148
- else subCmd.option(flag, arg.help ?? '');
149
- }
150
- }
151
- subCmd.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('-v, --verbose', 'Debug output', false);
152
-
153
- subCmd.action(async (...actionArgs: any[]) => {
154
- // Commander passes positional args first, then options object, then the Command
155
- const actionOpts = actionArgs[positionalArgs.length] ?? {};
156
- const startTime = Date.now();
157
- const kwargs: Record<string, any> = {};
158
-
159
- // Collect positional args
160
- for (let i = 0; i < positionalArgs.length; i++) {
161
- const arg = positionalArgs[i];
162
- const v = actionArgs[i];
163
- if (v !== undefined) kwargs[arg.name] = v;
164
- }
165
-
166
- // Collect named options
167
- for (const arg of cmd.args) {
168
- if (arg.positional) continue;
169
- const camelName = arg.name.replace(/-([a-z])/g, (_m, ch: string) => ch.toUpperCase());
170
- const v = actionOpts[arg.name] ?? actionOpts[camelName];
171
- if (v !== undefined) kwargs[arg.name] = v;
172
- }
306
+ // ── Unknown command fallback ──────────────────────────────────────────────
173
307
 
174
- try {
175
- if (actionOpts.verbose) process.env.OPENCLI_VERBOSE = '1';
176
- let result: any;
177
- if (shouldUseBrowserSession(cmd)) {
178
- const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
179
- result = await browserSession(BrowserFactory as any, async (page) => {
180
- // Cookie/header strategies require same-origin context for credentialed fetch.
181
- if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
182
- try { await page.goto(`https://${cmd.domain}`); await page.wait(2); } catch {}
183
- }
184
- return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
185
- }, { workspace: `site:${cmd.site}` });
186
- } else { result = await executeCommand(cmd, null, kwargs, actionOpts.verbose); }
187
- if (actionOpts.verbose && (!result || (Array.isArray(result) && result.length === 0))) {
188
- console.error(chalk.yellow(`[Verbose] Warning: Command returned an empty result. If the website structural API changed or requires authentication, check the network or update the adapter.`));
189
- }
190
- const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
191
- renderOutput(result, { fmt: actionOpts.format, columns: resolved.columns, title: `${resolved.site}/${resolved.name}`, elapsed: (Date.now() - startTime) / 1000, source: fullName(resolved), footerExtra: resolved.footerExtra?.(kwargs) });
192
- } catch (err: any) {
193
- if (err instanceof CliError) {
194
- console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
195
- if (err.hint) console.error(chalk.yellow(`Hint: ${err.hint}`));
196
- } else if (actionOpts.verbose && err.stack) {
197
- console.error(chalk.red(err.stack));
198
- } else {
199
- console.error(chalk.red(`Error: ${err.message ?? err}`));
200
- }
201
- process.exitCode = 1;
202
- }
203
- });
204
- }
308
+ const DENY_LIST = new Set([
309
+ 'rm', 'sudo', 'dd', 'mkfs', 'fdisk', 'shutdown', 'reboot',
310
+ 'kill', 'killall', 'chmod', 'chown', 'passwd', 'su', 'mount',
311
+ 'umount', 'format', 'diskutil',
312
+ ]);
313
+
314
+ program.on('command:*', (operands: string[]) => {
315
+ const binary = operands[0];
316
+ if (DENY_LIST.has(binary)) {
317
+ console.error(chalk.red(`Refusing to register system command '${binary}'.`));
318
+ process.exitCode = 1;
319
+ return;
320
+ }
321
+ if (isBinaryInstalled(binary)) {
322
+ console.log(chalk.cyan(`🔹 Auto-discovered local CLI '${binary}'. Registering...`));
323
+ registerExternalCli(binary);
324
+ passthroughExternal(binary);
325
+ } else {
326
+ console.error(chalk.red(`error: unknown command '${binary}'`));
327
+ program.outputHelp();
328
+ process.exitCode = 1;
329
+ }
330
+ });
205
331
 
206
332
  program.parse();
207
333
  }
334
+
335
+ // ── Helpers ─────────────────────────────────────────────────────────────────
336
+
337
+ /** Infer a workspace-friendly hostname from a URL, with site override. */
338
+ function inferHost(url: string, site?: string): string {
339
+ if (site) return site;
340
+ try { return new URL(url).host; } catch { return 'default'; }
341
+ }