@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/explore.ts CHANGED
@@ -223,6 +223,132 @@ export interface DiscoveredStore {
223
223
 
224
224
  const INTERACT_FUZZ_JS = interactFuzz.toString();
225
225
 
226
+ // ── Analysis helpers (extracted from exploreUrl) ───────────────────────────
227
+
228
+ /** Filter, deduplicate, and score network endpoints. */
229
+ function analyzeEndpoints(networkEntries: NetworkEntry[]): { analyzed: AnalyzedEndpoint[]; totalCount: number } {
230
+ const seen = new Map<string, AnalyzedEndpoint>();
231
+ for (const entry of networkEntries) {
232
+ if (!entry.url) continue;
233
+ const ct = entry.contentType.toLowerCase();
234
+ if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm')) continue;
235
+ if (entry.status && entry.status >= 400) continue;
236
+
237
+ const pattern = urlToPattern(entry.url);
238
+ const key = `${entry.method}:${pattern}`;
239
+ if (seen.has(key)) continue;
240
+
241
+ const qp: string[] = [];
242
+ try { new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) qp.push(k); }); } catch {}
243
+
244
+ const ep: AnalyzedEndpoint = {
245
+ pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
246
+ queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
247
+ hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
248
+ hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
249
+ authIndicators: detectAuthIndicators(entry.requestHeaders),
250
+ responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
251
+ score: 0,
252
+ };
253
+ ep.score = scoreEndpoint(ep);
254
+ seen.set(key, ep);
255
+ }
256
+
257
+ const analyzed = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
258
+ return { analyzed, totalCount: seen.size };
259
+ }
260
+
261
+ /** Infer CLI capabilities from analyzed endpoints. */
262
+ function inferCapabilitiesFromEndpoints(
263
+ endpoints: AnalyzedEndpoint[],
264
+ stores: DiscoveredStore[],
265
+ opts: { site?: string; goal?: string; url: string },
266
+ ): { capabilities: InferredCapability[]; topStrategy: string; authIndicators: string[] } {
267
+ const capabilities: InferredCapability[] = [];
268
+ const usedNames = new Set<string>();
269
+
270
+ for (const ep of endpoints.slice(0, 8)) {
271
+ let capName = inferCapabilityName(ep.url, opts.goal);
272
+ if (usedNames.has(capName)) {
273
+ const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
274
+ capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
275
+ }
276
+ usedNames.add(capName);
277
+
278
+ const cols: string[] = [];
279
+ if (ep.responseAnalysis) {
280
+ for (const role of ['title', 'url', 'author', 'score', 'time']) {
281
+ if (ep.responseAnalysis.detectedFields[role]) cols.push(role);
282
+ }
283
+ }
284
+
285
+ const args: InferredCapability['recommendedArgs'] = [];
286
+ if (ep.hasSearchParam) args.push({ name: 'keyword', type: 'str', required: true });
287
+ args.push({ name: 'limit', type: 'int', required: false, default: 20 });
288
+ if (ep.hasPaginationParam) args.push({ name: 'page', type: 'int', required: false, default: 1 });
289
+
290
+ const epStrategy = inferStrategy(ep.authIndicators);
291
+ let storeHint: { store: string; action: string } | undefined;
292
+ if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
293
+ for (const s of stores) {
294
+ const matchingAction = s.actions.find(a =>
295
+ capName.split('_').some(part => a.toLowerCase().includes(part)) ||
296
+ a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get')
297
+ );
298
+ if (matchingAction) { storeHint = { store: s.id, action: matchingAction }; break; }
299
+ }
300
+ }
301
+
302
+ capabilities.push({
303
+ name: capName, description: `${opts.site ?? detectSiteName(opts.url)} ${capName}`,
304
+ strategy: storeHint ? 'store-action' : epStrategy,
305
+ confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
306
+ itemPath: ep.responseAnalysis?.itemPath ?? null,
307
+ recommendedColumns: cols.length ? cols : ['title', 'url'],
308
+ recommendedArgs: args,
309
+ ...(storeHint ? { storeHint } : {}),
310
+ });
311
+ }
312
+
313
+ const allAuth = new Set(endpoints.flatMap(ep => ep.authIndicators));
314
+ const topStrategy = allAuth.has('signature') ? 'intercept'
315
+ : allAuth.has('bearer') || allAuth.has('csrf') ? 'header'
316
+ : allAuth.size === 0 ? 'public' : 'cookie';
317
+
318
+ return { capabilities, topStrategy, authIndicators: [...allAuth] };
319
+ }
320
+
321
+ /** Write explore artifacts (manifest, endpoints, capabilities, auth, stores) to disk. */
322
+ async function writeExploreArtifacts(
323
+ targetDir: string,
324
+ result: Record<string, any>,
325
+ analyzedEndpoints: AnalyzedEndpoint[],
326
+ stores: DiscoveredStore[],
327
+ ): Promise<void> {
328
+ await fs.promises.mkdir(targetDir, { recursive: true });
329
+ const tasks = [
330
+ fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
331
+ site: result.site, target_url: result.target_url, final_url: result.final_url, title: result.title,
332
+ framework: result.framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
333
+ top_strategy: result.top_strategy, explored_at: new Date().toISOString(),
334
+ }, null, 2)),
335
+ fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
336
+ pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
337
+ contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
338
+ itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
339
+ detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
340
+ })), null, 2)),
341
+ fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(result.capabilities, null, 2)),
342
+ fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
343
+ top_strategy: result.top_strategy, indicators: result.auth_indicators, framework: result.framework,
344
+ }, null, 2)),
345
+ ];
346
+ if (stores.length > 0) {
347
+ tasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
348
+ }
349
+ await Promise.all(tasks);
350
+ }
351
+
226
352
  // ── Main explore function ──────────────────────────────────────────────────
227
353
 
228
354
  export async function exploreUrl(
@@ -317,125 +443,25 @@ export async function exploreUrl(
317
443
  } catch {}
318
444
  }
319
445
 
320
- // Step 7: Analyze endpoints
321
- const seen = new Map<string, AnalyzedEndpoint>();
322
- for (const entry of networkEntries) {
323
- if (!entry.url) continue;
324
- const ct = entry.contentType.toLowerCase();
325
- if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm')) continue;
326
- if (entry.status && entry.status >= 400) continue;
327
-
328
- const pattern = urlToPattern(entry.url);
329
- const key = `${entry.method}:${pattern}`;
330
- if (seen.has(key)) continue;
331
-
332
- const qp: string[] = [];
333
- try { new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) qp.push(k); }); } catch {}
334
-
335
- const ep: AnalyzedEndpoint = {
336
- pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
337
- queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
338
- hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
339
- hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
340
- authIndicators: detectAuthIndicators(entry.requestHeaders),
341
- responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
342
- score: 0,
343
- };
344
- ep.score = scoreEndpoint(ep);
345
- seen.set(key, ep);
346
- }
347
-
348
- const analyzedEndpoints = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
349
-
350
- // Step 8: Infer capabilities
351
- const capabilities: InferredCapability[] = [];
352
- const usedNames = new Set<string>();
353
- for (const ep of analyzedEndpoints.slice(0, 8)) {
354
- let capName = inferCapabilityName(ep.url, opts.goal);
355
- if (usedNames.has(capName)) {
356
- const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
357
- capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
358
- }
359
- usedNames.add(capName);
360
-
361
- const cols: string[] = [];
362
- if (ep.responseAnalysis) {
363
- for (const role of ['title', 'url', 'author', 'score', 'time']) {
364
- if (ep.responseAnalysis.detectedFields[role]) cols.push(role);
365
- }
366
- }
367
-
368
- const args: InferredCapability['recommendedArgs'] = [];
369
- if (ep.hasSearchParam) args.push({ name: 'keyword', type: 'str', required: true });
370
- args.push({ name: 'limit', type: 'int', required: false, default: 20 });
371
- if (ep.hasPaginationParam) args.push({ name: 'page', type: 'int', required: false, default: 1 });
372
-
373
- // Link store actions to capabilities when store-action strategy is recommended
374
- const epStrategy = inferStrategy(ep.authIndicators);
375
- let storeHint: { store: string; action: string } | undefined;
376
- if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
377
- // Try to find a store/action that matches this endpoint's purpose
378
- for (const s of stores) {
379
- const matchingAction = s.actions.find(a =>
380
- capName.split('_').some(part => a.toLowerCase().includes(part)) ||
381
- a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get')
382
- );
383
- if (matchingAction) {
384
- storeHint = { store: s.id, action: matchingAction };
385
- break;
386
- }
387
- }
388
- }
389
-
390
- capabilities.push({
391
- name: capName, description: `${opts.site ?? detectSiteName(url)} ${capName}`,
392
- strategy: storeHint ? 'store-action' : epStrategy,
393
- confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
394
- itemPath: ep.responseAnalysis?.itemPath ?? null,
395
- recommendedColumns: cols.length ? cols : ['title', 'url'],
396
- recommendedArgs: args,
397
- ...(storeHint ? { storeHint } : {}),
398
- });
399
- }
400
-
401
- // Step 9: Determine overall auth strategy
402
- const allAuth = new Set(analyzedEndpoints.flatMap(ep => ep.authIndicators));
403
- const topStrategy = allAuth.has('signature') ? 'intercept' : allAuth.has('bearer') || allAuth.has('csrf') ? 'header' : allAuth.size === 0 ? 'public' : 'cookie';
446
+ // Step 7+8: Analyze endpoints and infer capabilities
447
+ const { analyzed: analyzedEndpoints, totalCount } = analyzeEndpoints(networkEntries);
448
+ const { capabilities, topStrategy, authIndicators } = inferCapabilitiesFromEndpoints(
449
+ analyzedEndpoints, stores, { site: opts.site, goal: opts.goal, url },
450
+ );
404
451
 
452
+ // Step 9: Assemble result and write artifacts
405
453
  const siteName = opts.site ?? detectSiteName(metadata.url || url);
406
454
  const targetDir = opts.outDir ?? path.join('.opencli', 'explore', siteName);
407
- await fs.promises.mkdir(targetDir, { recursive: true });
408
455
 
409
456
  const result = {
410
457
  site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
411
458
  framework, stores, top_strategy: topStrategy,
412
- endpoint_count: analyzedEndpoints.length + [...seen.values()].filter(ep => ep.score < 5).length,
459
+ endpoint_count: totalCount,
413
460
  api_endpoint_count: analyzedEndpoints.length,
414
- capabilities, auth_indicators: [...allAuth],
461
+ capabilities, auth_indicators: authIndicators,
415
462
  };
416
463
 
417
- // Write artifacts
418
- const writeTasks = [];
419
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
420
- site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
421
- framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
422
- top_strategy: topStrategy, explored_at: new Date().toISOString(),
423
- }, null, 2)));
424
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
425
- pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
426
- contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
427
- itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
428
- detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
429
- })), null, 2)));
430
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(capabilities, null, 2)));
431
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
432
- top_strategy: topStrategy, indicators: [...allAuth], framework,
433
- }, null, 2)));
434
- if (stores.length > 0) {
435
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
436
- }
437
- await Promise.all(writeTasks);
438
-
464
+ await writeExploreArtifacts(targetDir, result, analyzedEndpoints, stores);
439
465
  return { ...result, out_dir: targetDir };
440
466
  })(), { timeout: exploreTimeout, label: `Explore ${url}` });
441
467
  }, { workspace: opts.workspace });
@@ -0,0 +1,48 @@
1
+ - name: gh
2
+ binary: gh
3
+ description: "GitHub CLI — repos, PRs, issues, releases, gists"
4
+ homepage: "https://cli.github.com"
5
+ tags: [github, git, dev]
6
+ install:
7
+ mac: "brew install gh"
8
+
9
+ - name: obsidian
10
+ binary: obsidian
11
+ description: "Obsidian vault management — notes, search, tags, tasks, sync"
12
+ homepage: "https://obsidian.md/help/cli"
13
+ tags: [notes, knowledge, markdown]
14
+ install:
15
+ mac: "brew install --cask obsidian"
16
+
17
+ - name: readwise
18
+ binary: readwise
19
+ description: "Readwise & Reader CLI — highlights, annotations, reading list"
20
+ homepage: "https://github.com/readwiseio/readwise-cli"
21
+ tags: [reading, highlights]
22
+ install:
23
+ default: "npm install -g @readwiseio/readwise-cli"
24
+
25
+ - name: kubectl
26
+ binary: kubectl
27
+ description: "Kubernetes command-line tool"
28
+ homepage: "https://kubernetes.io/docs/reference/kubectl/"
29
+ tags: [kubernetes, k8s, devops]
30
+ install:
31
+ mac: "brew install kubectl"
32
+
33
+ - name: docker
34
+ binary: docker
35
+ description: "Docker command-line interface"
36
+ homepage: "https://docs.docker.com/engine/reference/commandline/cli/"
37
+ tags: [docker, containers, devops]
38
+ install:
39
+ mac: "brew install --cask docker"
40
+
41
+ - name: gws
42
+ binary: gws
43
+ description: "Google Workspace CLI — Docs, Sheets, Drive, Gmail, Calendar"
44
+ homepage: "https://github.com/nicholasgasior/gws"
45
+ tags: [google, docs, sheets, drive, workspace]
46
+ install:
47
+ mac: "brew install gws"
48
+ default: "npm install -g @nicholasgasior/gws"
@@ -0,0 +1,185 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync, execSync, execFileSync } from 'node:child_process';
6
+ import yaml from 'js-yaml';
7
+ import chalk from 'chalk';
8
+ import { log } from './logger.js';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ export interface ExternalCliInstall {
13
+ mac?: string;
14
+ linux?: string;
15
+ windows?: string;
16
+ default?: string;
17
+ }
18
+
19
+ export interface ExternalCliConfig {
20
+ name: string;
21
+ binary: string;
22
+ description?: string;
23
+ homepage?: string;
24
+ tags?: string[];
25
+ install?: ExternalCliInstall;
26
+ }
27
+
28
+ function getUserRegistryPath(): string {
29
+ const home = os.homedir();
30
+ return path.join(home, '.opencli', 'external-clis.yaml');
31
+ }
32
+
33
+ export function loadExternalClis(): ExternalCliConfig[] {
34
+ const configs = new Map<string, ExternalCliConfig>();
35
+
36
+ // 1. Load built-in
37
+ const builtinPath = path.resolve(__dirname, 'external-clis.yaml');
38
+ try {
39
+ if (fs.existsSync(builtinPath)) {
40
+ const raw = fs.readFileSync(builtinPath, 'utf8');
41
+ const parsed = (yaml.load(raw) || []) as ExternalCliConfig[];
42
+ for (const item of parsed) configs.set(item.name, item);
43
+ }
44
+ } catch (err: any) {
45
+ log.warn(`Failed to parse built-in external-clis.yaml: ${err.message}`);
46
+ }
47
+
48
+ // 2. Load user custom
49
+ const userPath = getUserRegistryPath();
50
+ try {
51
+ if (fs.existsSync(userPath)) {
52
+ const raw = fs.readFileSync(userPath, 'utf8');
53
+ const parsed = (yaml.load(raw) || []) as ExternalCliConfig[];
54
+ for (const item of parsed) {
55
+ configs.set(item.name, item); // Overwrite built-in if duplicated
56
+ }
57
+ }
58
+ } catch (err: any) {
59
+ log.warn(`Failed to parse user external-clis.yaml: ${err.message}`);
60
+ }
61
+
62
+ return Array.from(configs.values()).sort((a, b) => a.name.localeCompare(b.name));
63
+ }
64
+
65
+ export function isBinaryInstalled(binary: string): boolean {
66
+ try {
67
+ const isWindows = os.platform() === 'win32';
68
+ execFileSync(isWindows ? 'where' : 'which', [binary], { stdio: 'ignore' });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ export function getInstallCmd(installConfig?: ExternalCliInstall): string | null {
76
+ if (!installConfig) return null;
77
+ const platform = os.platform();
78
+ if (platform === 'darwin' && installConfig.mac) return installConfig.mac;
79
+ if (platform === 'linux' && installConfig.linux) return installConfig.linux;
80
+ if (platform === 'win32' && installConfig.windows) return installConfig.windows;
81
+ if (installConfig.default) return installConfig.default;
82
+ return null;
83
+ }
84
+
85
+ export function installExternalCli(cli: ExternalCliConfig): boolean {
86
+ if (!cli.install) {
87
+ console.error(chalk.red(`No auto-install command configured for '${cli.name}'.`));
88
+ console.error(`Please install '${cli.binary}' manually.`);
89
+ return false;
90
+ }
91
+
92
+ const cmd = getInstallCmd(cli.install);
93
+ if (!cmd) {
94
+ console.error(chalk.red(`No install command for your platform (${os.platform()}) for '${cli.name}'.`));
95
+ if (cli.homepage) console.error(`See: ${cli.homepage}`);
96
+ return false;
97
+ }
98
+
99
+ console.log(chalk.cyan(`🔹 '${cli.name}' is not installed. Auto-installing...`));
100
+ console.log(chalk.dim(`$ ${cmd}`));
101
+ try {
102
+ execSync(cmd, { stdio: 'inherit' });
103
+ console.log(chalk.green(`✅ Installed '${cli.name}' successfully.\n`));
104
+ return true;
105
+ } catch (err: any) {
106
+ console.error(chalk.red(`❌ Failed to install '${cli.name}': ${err.message}`));
107
+ return false;
108
+ }
109
+ }
110
+
111
+ export function executeExternalCli(name: string, args: string[], preloaded?: ExternalCliConfig[]): void {
112
+ const configs = preloaded ?? loadExternalClis();
113
+ const cli = configs.find((c) => c.name === name);
114
+ if (!cli) {
115
+ throw new Error(`External CLI '${name}' not found in registry.`);
116
+ }
117
+
118
+ // 1. Check if installed
119
+ if (!isBinaryInstalled(cli.binary)) {
120
+ // 2. Try to auto install
121
+ const success = installExternalCli(cli);
122
+ if (!success) {
123
+ process.exitCode = 1;
124
+ return;
125
+ }
126
+ }
127
+
128
+ // 3. Passthrough execution with stdio inherited
129
+ const result = spawnSync(cli.binary, args, { stdio: 'inherit' });
130
+ if (result.error) {
131
+ console.error(chalk.red(`Failed to execute '${cli.binary}': ${result.error.message}`));
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+
136
+ if (result.status !== null) {
137
+ process.exitCode = result.status;
138
+ }
139
+ }
140
+
141
+ export interface RegisterOptions {
142
+ binary?: string;
143
+ install?: string;
144
+ description?: string;
145
+ }
146
+
147
+ export function registerExternalCli(name: string, opts?: RegisterOptions): void {
148
+ const userPath = getUserRegistryPath();
149
+ const configDir = path.dirname(userPath);
150
+
151
+ if (!fs.existsSync(configDir)) {
152
+ fs.mkdirSync(configDir, { recursive: true });
153
+ }
154
+
155
+ let items: ExternalCliConfig[] = [];
156
+ if (fs.existsSync(userPath)) {
157
+ try {
158
+ const raw = fs.readFileSync(userPath, 'utf8');
159
+ items = (yaml.load(raw) || []) as ExternalCliConfig[];
160
+ } catch {
161
+ // Ignore
162
+ }
163
+ }
164
+
165
+ const existingIndex = items.findIndex((c) => c.name === name);
166
+
167
+ const newItem: ExternalCliConfig = {
168
+ name,
169
+ binary: opts?.binary || name,
170
+ };
171
+ if (opts?.description) newItem.description = opts.description;
172
+ if (opts?.install) newItem.install = { default: opts.install };
173
+
174
+ if (existingIndex >= 0) {
175
+ items[existingIndex] = { ...items[existingIndex], ...newItem };
176
+ console.log(chalk.green(`Updated '${name}' in user registry.`));
177
+ } else {
178
+ items.push(newItem);
179
+ console.log(chalk.green(`Registered '${name}' in user registry.`));
180
+ }
181
+
182
+ const dump = yaml.dump(items, { indent: 2, sortKeys: true });
183
+ fs.writeFileSync(userPath, dump, 'utf8');
184
+ console.log(chalk.dim(userPath));
185
+ }
package/src/main.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import * as os from 'node:os';
7
7
  import * as path from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
- import { discoverClis } from './engine.js';
9
+ import { discoverClis } from './discovery.js';
10
10
  import { getCompletions } from './completion.js';
11
11
  import { runCli } from './cli.js';
12
12
 
@@ -7,8 +7,13 @@ import type { IPage } from '../../types.js';
7
7
  import { render } from '../template.js';
8
8
 
9
9
  export async function stepNavigate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any> {
10
- const url = render(params, { args, data });
11
- await page!.goto(String(url));
10
+ if (typeof params === 'object' && params && 'url' in params) {
11
+ const url = String(render(params.url, { args, data }));
12
+ await page!.goto(url, { waitUntil: params.waitUntil, settleMs: params.settleMs });
13
+ } else {
14
+ const url = render(params, { args, data });
15
+ await page!.goto(String(url));
16
+ }
12
17
  return data;
13
18
  }
14
19
 
package/src/registry.ts CHANGED
@@ -89,3 +89,8 @@ export function strategyLabel(cmd: CliCommand): string {
89
89
  export function registerCommand(cmd: CliCommand): void {
90
90
  _registry.set(fullName(cmd), cmd);
91
91
  }
92
+
93
+ // Re-export serialization helpers from their dedicated module
94
+ export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
95
+ export type { SerializedArg } from './serialization.js';
96
+
package/src/runtime.ts CHANGED
@@ -1,5 +1,14 @@
1
+ import { BrowserBridge, CDPBridge } from './browser/index.js';
1
2
  import type { IPage } from './types.js';
2
3
 
4
+ /**
5
+ * Returns the appropriate browser factory based on environment config.
6
+ * Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
7
+ */
8
+ export function getBrowserFactory(): new () => IBrowserFactory {
9
+ return (process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge) as any;
10
+ }
11
+
3
12
  export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
4
13
  export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
5
14
  export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_EXPLORE_TIMEOUT ?? '120', 10);
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Serialization and formatting helpers for CLI commands and args.
3
+ *
4
+ * Used by the `list` command, Commander --help, and build-manifest.
5
+ * Separated from registry.ts to keep the registry focused on types + registration.
6
+ */
7
+
8
+ import type { Arg, CliCommand } from './registry.js';
9
+ import { fullName, strategyLabel } from './registry.js';
10
+
11
+ // ── Serialization ───────────────────────────────────────────────────────────
12
+
13
+ export type SerializedArg = {
14
+ name: string;
15
+ type: string;
16
+ required: boolean;
17
+ positional: boolean;
18
+ choices: string[];
19
+ default: unknown;
20
+ help: string;
21
+ };
22
+
23
+ /** Stable arg schema — every field is always present (no sparse objects). */
24
+ export function serializeArg(a: Arg): SerializedArg {
25
+ return {
26
+ name: a.name,
27
+ type: a.type ?? 'string',
28
+ required: !!a.required,
29
+ positional: !!a.positional,
30
+ choices: a.choices ?? [],
31
+ default: a.default ?? null,
32
+ help: a.help ?? '',
33
+ };
34
+ }
35
+
36
+ /** Full command metadata for structured output (json/yaml). */
37
+ export function serializeCommand(cmd: CliCommand) {
38
+ return {
39
+ command: fullName(cmd),
40
+ site: cmd.site,
41
+ name: cmd.name,
42
+ description: cmd.description,
43
+ strategy: strategyLabel(cmd),
44
+ browser: !!cmd.browser,
45
+ args: cmd.args.map(serializeArg),
46
+ columns: cmd.columns ?? [],
47
+ domain: cmd.domain ?? null,
48
+ };
49
+ }
50
+
51
+ // ── Formatting ──────────────────────────────────────────────────────────────
52
+
53
+ /** Human-readable arg summary: `<required> [optional]` style. */
54
+ export function formatArgSummary(args: Arg[]): string {
55
+ return args
56
+ .map(a => {
57
+ if (a.positional) return a.required ? `<${a.name}>` : `[${a.name}]`;
58
+ return a.required ? `--${a.name}` : `[--${a.name}]`;
59
+ })
60
+ .join(' ');
61
+ }
62
+
63
+ /** Generate the --help appendix showing registry metadata not exposed by Commander. */
64
+ export function formatRegistryHelpText(cmd: CliCommand): string {
65
+ const lines: string[] = [];
66
+ const choicesArgs = cmd.args.filter(a => a.choices?.length);
67
+ for (const a of choicesArgs) {
68
+ const prefix = a.positional ? `<${a.name}>` : `--${a.name}`;
69
+ const def = a.default != null ? ` (default: ${a.default})` : '';
70
+ lines.push(` ${prefix}: ${a.choices!.join(', ')}${def}`);
71
+ }
72
+ const meta: string[] = [];
73
+ meta.push(`Strategy: ${strategyLabel(cmd)}`);
74
+ meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
75
+ if (cmd.domain) meta.push(`Domain: ${cmd.domain}`);
76
+ lines.push(meta.join(' | '));
77
+ if (cmd.columns?.length) lines.push(`Output columns: ${cmd.columns.join(', ')}`);
78
+ return '\n' + lines.join('\n') + '\n';
79
+ }
package/src/types.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  export interface IPage {
9
- goto(url: string): Promise<void>;
9
+ goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void>;
10
10
  evaluate(js: string): Promise<any>;
11
11
  getCookies(opts?: { domain?: string; url?: string }): Promise<Array<{
12
12
  name: string;
@@ -46,6 +46,31 @@ describe('browser public-data commands E2E', () => {
46
46
  }
47
47
  }, 60_000);
48
48
 
49
+ it('bloomberg news returns article detail when the article page is accessible', async () => {
50
+ const feedResult = await runCli(['bloomberg', 'tech', '--limit', '1', '-f', 'json']);
51
+ if (feedResult.code !== 0) {
52
+ console.warn('bloomberg news: skipped — could not load Bloomberg tech feed');
53
+ return;
54
+ }
55
+
56
+ const feedItems = parseJsonOutput(feedResult.stdout);
57
+ const link = Array.isArray(feedItems) ? feedItems[0]?.link : null;
58
+ if (!link) {
59
+ console.warn('bloomberg news: skipped — tech feed returned no link');
60
+ return;
61
+ }
62
+
63
+ const data = await tryBrowserCommand(['bloomberg', 'news', link, '-f', 'json']);
64
+ expectDataOrSkip(data, 'bloomberg news');
65
+ if (data) {
66
+ expect(data[0]).toHaveProperty('title');
67
+ expect(data[0]).toHaveProperty('summary');
68
+ expect(data[0]).toHaveProperty('link');
69
+ expect(data[0]).toHaveProperty('mediaLinks');
70
+ expect(data[0]).toHaveProperty('content');
71
+ }
72
+ }, 60_000);
73
+
49
74
  // ── v2ex daily (browser: true) ──
50
75
  it('v2ex daily returns topics', async () => {
51
76
  const data = await tryBrowserCommand(['v2ex', 'daily', '--limit', '3', '-f', 'json']);