@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/dist/cli.d.ts CHANGED
@@ -1 +1,7 @@
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
+ */
1
7
  export declare function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void;
package/dist/cli.js CHANGED
@@ -1,41 +1,61 @@
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
+ */
1
7
  import { Command } from 'commander';
2
8
  import chalk from 'chalk';
3
- import { executeCommand } from './engine.js';
4
- import { Strategy, fullName, getRegistry, strategyLabel } from './registry.js';
9
+ import { fullName, getRegistry, strategyLabel } from './registry.js';
10
+ import { serializeCommand, formatArgSummary } from './serialization.js';
5
11
  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';
12
+ import { getBrowserFactory, browserSession } from './runtime.js';
8
13
  import { PKG_VERSION } from './version.js';
9
14
  import { printCompletionScript } from './completion.js';
10
- import { CliError } from './errors.js';
11
- import { shouldUseBrowserSession } from './capabilityRouting.js';
15
+ import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
16
+ import { registerAllCommands } from './commanderAdapter.js';
12
17
  export function runCli(BUILTIN_CLIS, USER_CLIS) {
13
18
  const program = new Command();
14
- program.name('opencli').description('Make any website your CLI. Zero setup. AI-powered.').version(PKG_VERSION);
15
- // ── Built-in commands ──────────────────────────────────────────────────────
16
- 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)')
19
+ // enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
20
+ // prerequisite for passThroughOptions to forward --help/--version to external binaries
21
+ program
22
+ .name('opencli')
23
+ .description('Make any website your CLI. Zero setup. AI-powered.')
24
+ .version(PKG_VERSION)
25
+ .enablePositionalOptions();
26
+ // ── Built-in: list ────────────────────────────────────────────────────────
27
+ program
28
+ .command('list')
29
+ .description('List all available CLI commands')
30
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
31
+ .option('--json', 'JSON output (deprecated)')
17
32
  .action((opts) => {
18
33
  const registry = getRegistry();
19
34
  const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
20
- const rows = commands.map(c => ({
21
- command: fullName(c),
22
- site: c.site,
23
- name: c.name,
24
- description: c.description,
25
- strategy: strategyLabel(c),
26
- browser: c.browser,
27
- args: c.args.map(a => a.name).join(', '),
28
- }));
29
35
  const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
36
+ const isStructured = fmt === 'json' || fmt === 'yaml';
30
37
  if (fmt !== 'table') {
38
+ const rows = isStructured
39
+ ? commands.map(serializeCommand)
40
+ : commands.map(c => ({
41
+ command: fullName(c),
42
+ site: c.site,
43
+ name: c.name,
44
+ description: c.description,
45
+ strategy: strategyLabel(c),
46
+ browser: !!c.browser,
47
+ args: formatArgSummary(c.args),
48
+ }));
31
49
  renderOutput(rows, {
32
50
  fmt,
33
- columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args'],
51
+ columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args',
52
+ ...(isStructured ? ['columns', 'domain'] : [])],
34
53
  title: 'opencli/list',
35
54
  source: 'opencli list',
36
55
  });
37
56
  return;
38
57
  }
58
+ // Table (default) — grouped by site
39
59
  const sites = new Map();
40
60
  for (const cmd of commands) {
41
61
  const g = sites.get(cmd.site) ?? [];
@@ -48,48 +68,113 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
48
68
  for (const [site, cmds] of sites) {
49
69
  console.log(chalk.bold.cyan(` ${site}`));
50
70
  for (const cmd of cmds) {
51
- const tag = strategyLabel(cmd) === 'public' ? chalk.green('[public]') : chalk.yellow(`[${strategyLabel(cmd)}]`);
71
+ const tag = strategyLabel(cmd) === 'public'
72
+ ? chalk.green('[public]')
73
+ : chalk.yellow(`[${strategyLabel(cmd)}]`);
52
74
  console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
53
75
  }
54
76
  console.log();
55
77
  }
56
- console.log(chalk.dim(` ${commands.length} commands across ${sites.size} sites`));
78
+ const externalClis = loadExternalClis();
79
+ if (externalClis.length > 0) {
80
+ console.log(chalk.bold.cyan(' external CLIs'));
81
+ for (const ext of externalClis) {
82
+ const isInstalled = isBinaryInstalled(ext.binary);
83
+ const tag = isInstalled ? chalk.green('[installed]') : chalk.yellow('[auto-install]');
84
+ console.log(` ${ext.name} ${tag}${ext.description ? chalk.dim(` — ${ext.description}`) : ''}`);
85
+ }
86
+ console.log();
87
+ }
88
+ console.log(chalk.dim(` ${commands.length} built-in commands across ${sites.size} sites, ${externalClis.length} external CLIs`));
57
89
  console.log();
58
90
  });
59
- program.command('validate').description('Validate CLI definitions').argument('[target]', 'site or site/name')
91
+ // ── Built-in: validate / verify ───────────────────────────────────────────
92
+ program
93
+ .command('validate')
94
+ .description('Validate CLI definitions')
95
+ .argument('[target]', 'site or site/name')
60
96
  .action(async (target) => {
61
97
  const { validateClisWithTarget, renderValidationReport } = await import('./validate.js');
62
98
  console.log(renderValidationReport(validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target)));
63
99
  });
64
- program.command('verify').description('Validate + smoke test').argument('[target]').option('--smoke', 'Run smoke tests', false)
100
+ program
101
+ .command('verify')
102
+ .description('Validate + smoke test')
103
+ .argument('[target]')
104
+ .option('--smoke', 'Run smoke tests', false)
65
105
  .action(async (target, opts) => {
66
106
  const { verifyClis, renderVerifyReport } = await import('./verify.js');
67
107
  const r = await verifyClis({ builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, target, smoke: opts.smoke });
68
108
  console.log(renderVerifyReport(r));
69
109
  process.exitCode = r.ok ? 0 : 1;
70
110
  });
71
- 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,评论")')
72
- .action(async (url, opts) => { const { exploreUrl, renderExploreSummary } = await import('./explore.js'); const clickLabels = opts.click ? opts.click.split(',').map((s) => s.trim()) : undefined; const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; const workspace = `explore:${opts.site ?? (() => { try {
73
- return new URL(url).host;
74
- }
75
- catch {
76
- return 'default';
77
- } })()}`; console.log(renderExploreSummary(await exploreUrl(url, { BrowserFactory: BrowserFactory, site: opts.site, goal: opts.goal, waitSeconds: parseFloat(opts.wait), auto: opts.auto, clickLabels, workspace }))); });
78
- program.command('synthesize').description('Synthesize CLIs from explore').argument('<target>').option('--top <n>', '', '3')
79
- .action(async (target, opts) => { const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js'); console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) }))); });
80
- program.command('generate').description('One-shot: explore → synthesize → register').argument('<url>').option('--goal <text>').option('--site <name>')
81
- .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 {
82
- return new URL(url).host;
83
- }
84
- catch {
85
- return 'default';
86
- } })()}`; const r = await generateCliFromUrl({ url, BrowserFactory: BrowserFactory, builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, goal: opts.goal, site: opts.site, workspace }); console.log(renderGenerateSummary(r)); process.exitCode = r.ok ? 0 : 1; });
87
- program.command('cascade').description('Strategy cascade: find simplest working strategy').argument('<url>').option('--site <name>')
111
+ // ── Built-in: explore / synthesize / generate / cascade ───────────────────
112
+ program
113
+ .command('explore')
114
+ .alias('probe')
115
+ .description('Explore a website: discover APIs, stores, and recommend strategies')
116
+ .argument('<url>')
117
+ .option('--site <name>')
118
+ .option('--goal <text>')
119
+ .option('--wait <s>', '', '3')
120
+ .option('--auto', 'Enable interactive fuzzing')
121
+ .option('--click <labels>', 'Comma-separated labels to click before fuzzing')
122
+ .action(async (url, opts) => {
123
+ const { exploreUrl, renderExploreSummary } = await import('./explore.js');
124
+ const clickLabels = opts.click
125
+ ? opts.click.split(',').map((s) => s.trim())
126
+ : undefined;
127
+ const workspace = `explore:${inferHost(url, opts.site)}`;
128
+ const result = await exploreUrl(url, {
129
+ BrowserFactory: getBrowserFactory(),
130
+ site: opts.site,
131
+ goal: opts.goal,
132
+ waitSeconds: parseFloat(opts.wait),
133
+ auto: opts.auto,
134
+ clickLabels,
135
+ workspace,
136
+ });
137
+ console.log(renderExploreSummary(result));
138
+ });
139
+ program
140
+ .command('synthesize')
141
+ .description('Synthesize CLIs from explore')
142
+ .argument('<target>')
143
+ .option('--top <n>', '', '3')
144
+ .action(async (target, opts) => {
145
+ const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js');
146
+ console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) })));
147
+ });
148
+ program
149
+ .command('generate')
150
+ .description('One-shot: explore → synthesize → register')
151
+ .argument('<url>')
152
+ .option('--goal <text>')
153
+ .option('--site <name>')
154
+ .action(async (url, opts) => {
155
+ const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js');
156
+ const workspace = `generate:${inferHost(url, opts.site)}`;
157
+ const r = await generateCliFromUrl({
158
+ url,
159
+ BrowserFactory: getBrowserFactory(),
160
+ builtinClis: BUILTIN_CLIS,
161
+ userClis: USER_CLIS,
162
+ goal: opts.goal,
163
+ site: opts.site,
164
+ workspace,
165
+ });
166
+ console.log(renderGenerateSummary(r));
167
+ process.exitCode = r.ok ? 0 : 1;
168
+ });
169
+ program
170
+ .command('cascade')
171
+ .description('Strategy cascade: find simplest working strategy')
172
+ .argument('<url>')
173
+ .option('--site <name>')
88
174
  .action(async (url, opts) => {
89
175
  const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
90
- const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
91
- const result = await browserSession(BrowserFactory, async (page) => {
92
- // Navigate to the site first for cookie context
176
+ const workspace = `cascade:${inferHost(url, opts.site)}`;
177
+ const result = await browserSession(getBrowserFactory(), async (page) => {
93
178
  try {
94
179
  const siteUrl = new URL(url);
95
180
  await page.goto(`${siteUrl.protocol}//${siteUrl.host}`);
@@ -97,15 +182,12 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
97
182
  }
98
183
  catch { }
99
184
  return cascadeProbe(page, url);
100
- }, { workspace: `cascade:${opts.site ?? (() => { try {
101
- return new URL(url).host;
102
- }
103
- catch {
104
- return 'default';
105
- } })()}` });
185
+ }, { workspace });
106
186
  console.log(renderCascadeResult(result));
107
187
  });
108
- program.command('doctor')
188
+ // ── Built-in: doctor / setup / completion ─────────────────────────────────
189
+ program
190
+ .command('doctor')
109
191
  .description('Diagnose opencli browser bridge connectivity')
110
192
  .option('--live', 'Test browser connectivity (requires Chrome running)', false)
111
193
  .option('--sessions', 'Show active automation sessions', false)
@@ -114,124 +196,119 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
114
196
  const report = await runBrowserDoctor({ live: opts.live, sessions: opts.sessions, cliVersion: PKG_VERSION });
115
197
  console.log(renderBrowserDoctorReport(report));
116
198
  });
117
- program.command('setup')
199
+ program
200
+ .command('setup')
118
201
  .description('Interactive setup: verify browser bridge connectivity')
119
202
  .action(async () => {
120
203
  const { runSetup } = await import('./setup.js');
121
204
  await runSetup({ cliVersion: PKG_VERSION });
122
205
  });
123
- program.command('completion')
206
+ program
207
+ .command('completion')
124
208
  .description('Output shell completion script')
125
209
  .argument('<shell>', 'Shell type: bash, zsh, or fish')
126
210
  .action((shell) => {
127
211
  printCompletionScript(shell);
128
212
  });
129
- // ── Antigravity serve (built-in, long-running) ──────────────────────────────
213
+ // ── External CLIs ─────────────────────────────────────────────────────────
214
+ const externalClis = loadExternalClis();
215
+ program
216
+ .command('install')
217
+ .description('Install an external CLI')
218
+ .argument('<name>', 'Name of the external CLI')
219
+ .action((name) => {
220
+ const ext = externalClis.find(e => e.name === name);
221
+ if (!ext) {
222
+ console.error(chalk.red(`External CLI '${name}' not found in registry.`));
223
+ process.exitCode = 1;
224
+ return;
225
+ }
226
+ installExternalCli(ext);
227
+ });
228
+ program
229
+ .command('register')
230
+ .description('Register an external CLI')
231
+ .argument('<name>', 'Name of the CLI')
232
+ .option('--binary <bin>', 'Binary name if different from name')
233
+ .option('--install <cmd>', 'Auto-install command')
234
+ .option('--desc <text>', 'Description')
235
+ .action((name, opts) => {
236
+ registerExternalCli(name, { binary: opts.binary, install: opts.install, description: opts.desc });
237
+ });
238
+ function passthroughExternal(name, parsedArgs) {
239
+ const args = parsedArgs ?? (() => {
240
+ const idx = process.argv.indexOf(name);
241
+ return process.argv.slice(idx + 1);
242
+ })();
243
+ try {
244
+ executeExternalCli(name, args, externalClis);
245
+ }
246
+ catch (err) {
247
+ console.error(chalk.red(`Error: ${err.message}`));
248
+ process.exitCode = 1;
249
+ }
250
+ }
251
+ for (const ext of externalClis) {
252
+ if (program.commands.some(c => c.name() === ext.name))
253
+ continue;
254
+ program
255
+ .command(ext.name)
256
+ .description(`(External) ${ext.description || ext.name}`)
257
+ .argument('[args...]')
258
+ .allowUnknownOption()
259
+ .passThroughOptions()
260
+ .helpOption(false)
261
+ .action((args) => passthroughExternal(ext.name, args));
262
+ }
263
+ // ── Antigravity serve (long-running, special case) ────────────────────────
130
264
  const antigravityCmd = program.command('antigravity').description('antigravity commands');
131
- antigravityCmd.command('serve')
265
+ antigravityCmd
266
+ .command('serve')
132
267
  .description('Start Anthropic-compatible API proxy for Antigravity')
133
268
  .option('--port <port>', 'Server port (default: 8082)', '8082')
134
269
  .action(async (opts) => {
135
270
  const { startServe } = await import('./clis/antigravity/serve.js');
136
271
  await startServe({ port: parseInt(opts.port) });
137
272
  });
138
- // ── Dynamic site commands ──────────────────────────────────────────────────
139
- const registry = getRegistry();
273
+ // ── Dynamic adapter commands ──────────────────────────────────────────────
140
274
  const siteGroups = new Map();
141
- // Pre-seed with the antigravity command registered above to avoid duplicates
142
275
  siteGroups.set('antigravity', antigravityCmd);
143
- for (const [, cmd] of registry) {
144
- let siteCmd = siteGroups.get(cmd.site);
145
- if (!siteCmd) {
146
- siteCmd = program.command(cmd.site).description(`${cmd.site} commands`);
147
- siteGroups.set(cmd.site, siteCmd);
276
+ registerAllCommands(program, siteGroups);
277
+ // ── Unknown command fallback ──────────────────────────────────────────────
278
+ const DENY_LIST = new Set([
279
+ 'rm', 'sudo', 'dd', 'mkfs', 'fdisk', 'shutdown', 'reboot',
280
+ 'kill', 'killall', 'chmod', 'chown', 'passwd', 'su', 'mount',
281
+ 'umount', 'format', 'diskutil',
282
+ ]);
283
+ program.on('command:*', (operands) => {
284
+ const binary = operands[0];
285
+ if (DENY_LIST.has(binary)) {
286
+ console.error(chalk.red(`Refusing to register system command '${binary}'.`));
287
+ process.exitCode = 1;
288
+ return;
148
289
  }
149
- // Skip if this subcommand was already hardcoded (e.g. antigravity serve)
150
- if (siteCmd.commands.some((c) => c.name() === cmd.name))
151
- continue;
152
- const subCmd = siteCmd.command(cmd.name).description(cmd.description);
153
- // Register positional args first, then named options
154
- const positionalArgs = [];
155
- for (const arg of cmd.args) {
156
- if (arg.positional) {
157
- const bracket = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
158
- subCmd.argument(bracket, arg.help ?? '');
159
- positionalArgs.push(arg);
160
- }
161
- else {
162
- const flag = arg.required ? `--${arg.name} <value>` : `--${arg.name} [value]`;
163
- if (arg.required)
164
- subCmd.requiredOption(flag, arg.help ?? '');
165
- else if (arg.default != null)
166
- subCmd.option(flag, arg.help ?? '', String(arg.default));
167
- else
168
- subCmd.option(flag, arg.help ?? '');
169
- }
290
+ if (isBinaryInstalled(binary)) {
291
+ console.log(chalk.cyan(`🔹 Auto-discovered local CLI '${binary}'. Registering...`));
292
+ registerExternalCli(binary);
293
+ passthroughExternal(binary);
170
294
  }
171
- subCmd.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('-v, --verbose', 'Debug output', false);
172
- subCmd.action(async (...actionArgs) => {
173
- // Commander passes positional args first, then options object, then the Command
174
- const actionOpts = actionArgs[positionalArgs.length] ?? {};
175
- const startTime = Date.now();
176
- const kwargs = {};
177
- // Collect positional args
178
- for (let i = 0; i < positionalArgs.length; i++) {
179
- const arg = positionalArgs[i];
180
- const v = actionArgs[i];
181
- if (v !== undefined)
182
- kwargs[arg.name] = v;
183
- }
184
- // Collect named options
185
- for (const arg of cmd.args) {
186
- if (arg.positional)
187
- continue;
188
- const camelName = arg.name.replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
189
- const v = actionOpts[arg.name] ?? actionOpts[camelName];
190
- if (v !== undefined)
191
- kwargs[arg.name] = v;
192
- }
193
- try {
194
- if (actionOpts.verbose)
195
- process.env.OPENCLI_VERBOSE = '1';
196
- let result;
197
- if (shouldUseBrowserSession(cmd)) {
198
- const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
199
- result = await browserSession(BrowserFactory, async (page) => {
200
- // Cookie/header strategies require same-origin context for credentialed fetch.
201
- if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
202
- try {
203
- await page.goto(`https://${cmd.domain}`);
204
- await page.wait(2);
205
- }
206
- catch { }
207
- }
208
- return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
209
- }, { workspace: `site:${cmd.site}` });
210
- }
211
- else {
212
- result = await executeCommand(cmd, null, kwargs, actionOpts.verbose);
213
- }
214
- if (actionOpts.verbose && (!result || (Array.isArray(result) && result.length === 0))) {
215
- 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.`));
216
- }
217
- const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
218
- 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) });
219
- }
220
- catch (err) {
221
- if (err instanceof CliError) {
222
- console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
223
- if (err.hint)
224
- console.error(chalk.yellow(`Hint: ${err.hint}`));
225
- }
226
- else if (actionOpts.verbose && err.stack) {
227
- console.error(chalk.red(err.stack));
228
- }
229
- else {
230
- console.error(chalk.red(`Error: ${err.message ?? err}`));
231
- }
232
- process.exitCode = 1;
233
- }
234
- });
235
- }
295
+ else {
296
+ console.error(chalk.red(`error: unknown command '${binary}'`));
297
+ program.outputHelp();
298
+ process.exitCode = 1;
299
+ }
300
+ });
236
301
  program.parse();
237
302
  }
303
+ // ── Helpers ─────────────────────────────────────────────────────────────────
304
+ /** Infer a workspace-friendly hostname from a URL, with site override. */
305
+ function inferHost(url, site) {
306
+ if (site)
307
+ return site;
308
+ try {
309
+ return new URL(url).host;
310
+ }
311
+ catch {
312
+ return 'default';
313
+ }
314
+ }