@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
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Command execution: validates args, manages browser sessions, runs commands.
3
+ *
4
+ * This is the single entry point for executing any CLI command. It handles:
5
+ * 1. Argument validation and coercion
6
+ * 2. Browser session lifecycle (if needed)
7
+ * 3. Domain pre-navigation for cookie/header strategies
8
+ * 4. Timeout enforcement
9
+ * 5. Lazy-loading of TS modules from manifest
10
+ */
11
+ import { Strategy, getRegistry, fullName } from './registry.js';
12
+ import { executePipeline } from './pipeline.js';
13
+ import { AdapterLoadError } from './errors.js';
14
+ import { shouldUseBrowserSession } from './capabilityRouting.js';
15
+ import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
16
+ /** Set of TS module paths that have been loaded */
17
+ const _loadedModules = new Set();
18
+ /**
19
+ * Validates and coerces arguments based on the command's Arg definitions.
20
+ */
21
+ export function coerceAndValidateArgs(cmdArgs, kwargs) {
22
+ const result = { ...kwargs };
23
+ for (const argDef of cmdArgs) {
24
+ const val = result[argDef.name];
25
+ // 1. Check required
26
+ if (argDef.required && (val === undefined || val === null || val === '')) {
27
+ throw new Error(`Argument "${argDef.name}" is required.\n${argDef.help ? `Hint: ${argDef.help}` : ''}`);
28
+ }
29
+ if (val !== undefined && val !== null) {
30
+ // 2. Type coercion
31
+ if (argDef.type === 'int' || argDef.type === 'number') {
32
+ const num = Number(val);
33
+ if (Number.isNaN(num)) {
34
+ throw new Error(`Argument "${argDef.name}" must be a valid number. Received: "${val}"`);
35
+ }
36
+ result[argDef.name] = num;
37
+ }
38
+ else if (argDef.type === 'boolean' || argDef.type === 'bool') {
39
+ if (typeof val === 'string') {
40
+ const lower = val.toLowerCase();
41
+ if (lower === 'true' || lower === '1')
42
+ result[argDef.name] = true;
43
+ else if (lower === 'false' || lower === '0')
44
+ result[argDef.name] = false;
45
+ else
46
+ throw new Error(`Argument "${argDef.name}" must be a boolean (true/false). Received: "${val}"`);
47
+ }
48
+ else {
49
+ result[argDef.name] = Boolean(val);
50
+ }
51
+ }
52
+ // 3. Choices validation
53
+ const coercedVal = result[argDef.name];
54
+ if (argDef.choices && argDef.choices.length > 0) {
55
+ if (!argDef.choices.map(String).includes(String(coercedVal))) {
56
+ throw new Error(`Argument "${argDef.name}" must be one of: ${argDef.choices.join(', ')}. Received: "${coercedVal}"`);
57
+ }
58
+ }
59
+ }
60
+ else if (argDef.default !== undefined) {
61
+ result[argDef.name] = argDef.default;
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+ /**
67
+ * Run a command's func or pipeline against a page.
68
+ */
69
+ async function runCommand(cmd, page, kwargs, debug) {
70
+ // Lazy-load TS module on first execution (manifest fast-path)
71
+ const internal = cmd;
72
+ if (internal._lazy && internal._modulePath) {
73
+ const modulePath = internal._modulePath;
74
+ if (!_loadedModules.has(modulePath)) {
75
+ try {
76
+ await import(`file://${modulePath}`);
77
+ _loadedModules.add(modulePath);
78
+ }
79
+ catch (err) {
80
+ throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${err.message}`, 'Check that the adapter file exists and has no syntax errors.');
81
+ }
82
+ }
83
+ // After loading, the module's cli() call will have updated the registry.
84
+ const updated = getRegistry().get(fullName(cmd));
85
+ if (updated?.func)
86
+ return updated.func(page, kwargs, debug);
87
+ if (updated?.pipeline)
88
+ return executePipeline(page, updated.pipeline, { args: kwargs, debug });
89
+ }
90
+ if (cmd.func)
91
+ return cmd.func(page, kwargs, debug);
92
+ if (cmd.pipeline)
93
+ return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
94
+ throw new Error(`Command ${fullName(cmd)} has no func or pipeline`);
95
+ }
96
+ /**
97
+ * Execute a CLI command. Automatically manages browser sessions when needed.
98
+ *
99
+ * This is the unified entry point — callers don't need to care about
100
+ * whether the command requires a browser or not.
101
+ */
102
+ export async function executeCommand(cmd, rawKwargs, debug = false) {
103
+ let kwargs;
104
+ try {
105
+ kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
106
+ }
107
+ catch (err) {
108
+ throw new Error(`[Argument Validation Error]\n${err.message}`);
109
+ }
110
+ if (shouldUseBrowserSession(cmd)) {
111
+ const BrowserFactory = getBrowserFactory();
112
+ return browserSession(BrowserFactory, async (page) => {
113
+ // Cookie/header strategies require same-origin context for credentialed fetch.
114
+ if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
115
+ try {
116
+ await page.goto(`https://${cmd.domain}`);
117
+ await page.wait(2);
118
+ }
119
+ catch { }
120
+ }
121
+ return runWithTimeout(runCommand(cmd, page, kwargs, debug), {
122
+ timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
123
+ label: fullName(cmd),
124
+ });
125
+ }, { workspace: `site:${cmd.site}` });
126
+ }
127
+ // Non-browser commands run directly
128
+ return runCommand(cmd, null, kwargs, debug);
129
+ }
package/dist/explore.js CHANGED
@@ -210,6 +210,120 @@ const FRAMEWORK_DETECT_JS = detectFramework.toString();
210
210
  const STORE_DISCOVER_JS = discoverStores.toString();
211
211
  // ── Auto-Interaction (Fuzzing) ─────────────────────────────────────────────
212
212
  const INTERACT_FUZZ_JS = interactFuzz.toString();
213
+ // ── Analysis helpers (extracted from exploreUrl) ───────────────────────────
214
+ /** Filter, deduplicate, and score network endpoints. */
215
+ function analyzeEndpoints(networkEntries) {
216
+ const seen = new Map();
217
+ for (const entry of networkEntries) {
218
+ if (!entry.url)
219
+ continue;
220
+ const ct = entry.contentType.toLowerCase();
221
+ if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm'))
222
+ continue;
223
+ if (entry.status && entry.status >= 400)
224
+ continue;
225
+ const pattern = urlToPattern(entry.url);
226
+ const key = `${entry.method}:${pattern}`;
227
+ if (seen.has(key))
228
+ continue;
229
+ const qp = [];
230
+ try {
231
+ new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
232
+ qp.push(k); });
233
+ }
234
+ catch { }
235
+ const ep = {
236
+ pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
237
+ queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
238
+ hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
239
+ hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
240
+ authIndicators: detectAuthIndicators(entry.requestHeaders),
241
+ responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
242
+ score: 0,
243
+ };
244
+ ep.score = scoreEndpoint(ep);
245
+ seen.set(key, ep);
246
+ }
247
+ const analyzed = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
248
+ return { analyzed, totalCount: seen.size };
249
+ }
250
+ /** Infer CLI capabilities from analyzed endpoints. */
251
+ function inferCapabilitiesFromEndpoints(endpoints, stores, opts) {
252
+ const capabilities = [];
253
+ const usedNames = new Set();
254
+ for (const ep of endpoints.slice(0, 8)) {
255
+ let capName = inferCapabilityName(ep.url, opts.goal);
256
+ if (usedNames.has(capName)) {
257
+ const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
258
+ capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
259
+ }
260
+ usedNames.add(capName);
261
+ const cols = [];
262
+ if (ep.responseAnalysis) {
263
+ for (const role of ['title', 'url', 'author', 'score', 'time']) {
264
+ if (ep.responseAnalysis.detectedFields[role])
265
+ cols.push(role);
266
+ }
267
+ }
268
+ const args = [];
269
+ if (ep.hasSearchParam)
270
+ args.push({ name: 'keyword', type: 'str', required: true });
271
+ args.push({ name: 'limit', type: 'int', required: false, default: 20 });
272
+ if (ep.hasPaginationParam)
273
+ args.push({ name: 'page', type: 'int', required: false, default: 1 });
274
+ const epStrategy = inferStrategy(ep.authIndicators);
275
+ let storeHint;
276
+ if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
277
+ for (const s of stores) {
278
+ const matchingAction = s.actions.find(a => capName.split('_').some(part => a.toLowerCase().includes(part)) ||
279
+ a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get'));
280
+ if (matchingAction) {
281
+ storeHint = { store: s.id, action: matchingAction };
282
+ break;
283
+ }
284
+ }
285
+ }
286
+ capabilities.push({
287
+ name: capName, description: `${opts.site ?? detectSiteName(opts.url)} ${capName}`,
288
+ strategy: storeHint ? 'store-action' : epStrategy,
289
+ confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
290
+ itemPath: ep.responseAnalysis?.itemPath ?? null,
291
+ recommendedColumns: cols.length ? cols : ['title', 'url'],
292
+ recommendedArgs: args,
293
+ ...(storeHint ? { storeHint } : {}),
294
+ });
295
+ }
296
+ const allAuth = new Set(endpoints.flatMap(ep => ep.authIndicators));
297
+ const topStrategy = allAuth.has('signature') ? 'intercept'
298
+ : allAuth.has('bearer') || allAuth.has('csrf') ? 'header'
299
+ : allAuth.size === 0 ? 'public' : 'cookie';
300
+ return { capabilities, topStrategy, authIndicators: [...allAuth] };
301
+ }
302
+ /** Write explore artifacts (manifest, endpoints, capabilities, auth, stores) to disk. */
303
+ async function writeExploreArtifacts(targetDir, result, analyzedEndpoints, stores) {
304
+ await fs.promises.mkdir(targetDir, { recursive: true });
305
+ const tasks = [
306
+ fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
307
+ site: result.site, target_url: result.target_url, final_url: result.final_url, title: result.title,
308
+ framework: result.framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
309
+ top_strategy: result.top_strategy, explored_at: new Date().toISOString(),
310
+ }, null, 2)),
311
+ fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
312
+ pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
313
+ contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
314
+ itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
315
+ detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
316
+ })), null, 2)),
317
+ fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(result.capabilities, null, 2)),
318
+ fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
319
+ top_strategy: result.top_strategy, indicators: result.auth_indicators, framework: result.framework,
320
+ }, null, 2)),
321
+ ];
322
+ if (stores.length > 0) {
323
+ tasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
324
+ }
325
+ await Promise.all(tasks);
326
+ }
213
327
  // ── Main explore function ──────────────────────────────────────────────────
214
328
  export async function exploreUrl(url, opts) {
215
329
  const waitSeconds = opts.waitSeconds ?? 3.0;
@@ -301,120 +415,20 @@ export async function exploreUrl(url, opts) {
301
415
  }
302
416
  catch { }
303
417
  }
304
- // Step 7: Analyze endpoints
305
- const seen = new Map();
306
- for (const entry of networkEntries) {
307
- if (!entry.url)
308
- continue;
309
- const ct = entry.contentType.toLowerCase();
310
- if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm'))
311
- continue;
312
- if (entry.status && entry.status >= 400)
313
- continue;
314
- const pattern = urlToPattern(entry.url);
315
- const key = `${entry.method}:${pattern}`;
316
- if (seen.has(key))
317
- continue;
318
- const qp = [];
319
- try {
320
- new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
321
- qp.push(k); });
322
- }
323
- catch { }
324
- const ep = {
325
- pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
326
- queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
327
- hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
328
- hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
329
- authIndicators: detectAuthIndicators(entry.requestHeaders),
330
- responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
331
- score: 0,
332
- };
333
- ep.score = scoreEndpoint(ep);
334
- seen.set(key, ep);
335
- }
336
- const analyzedEndpoints = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
337
- // Step 8: Infer capabilities
338
- const capabilities = [];
339
- const usedNames = new Set();
340
- for (const ep of analyzedEndpoints.slice(0, 8)) {
341
- let capName = inferCapabilityName(ep.url, opts.goal);
342
- if (usedNames.has(capName)) {
343
- const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
344
- capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
345
- }
346
- usedNames.add(capName);
347
- const cols = [];
348
- if (ep.responseAnalysis) {
349
- for (const role of ['title', 'url', 'author', 'score', 'time']) {
350
- if (ep.responseAnalysis.detectedFields[role])
351
- cols.push(role);
352
- }
353
- }
354
- const args = [];
355
- if (ep.hasSearchParam)
356
- args.push({ name: 'keyword', type: 'str', required: true });
357
- args.push({ name: 'limit', type: 'int', required: false, default: 20 });
358
- if (ep.hasPaginationParam)
359
- args.push({ name: 'page', type: 'int', required: false, default: 1 });
360
- // Link store actions to capabilities when store-action strategy is recommended
361
- const epStrategy = inferStrategy(ep.authIndicators);
362
- let storeHint;
363
- if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
364
- // Try to find a store/action that matches this endpoint's purpose
365
- for (const s of stores) {
366
- const matchingAction = s.actions.find(a => capName.split('_').some(part => a.toLowerCase().includes(part)) ||
367
- a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get'));
368
- if (matchingAction) {
369
- storeHint = { store: s.id, action: matchingAction };
370
- break;
371
- }
372
- }
373
- }
374
- capabilities.push({
375
- name: capName, description: `${opts.site ?? detectSiteName(url)} ${capName}`,
376
- strategy: storeHint ? 'store-action' : epStrategy,
377
- confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
378
- itemPath: ep.responseAnalysis?.itemPath ?? null,
379
- recommendedColumns: cols.length ? cols : ['title', 'url'],
380
- recommendedArgs: args,
381
- ...(storeHint ? { storeHint } : {}),
382
- });
383
- }
384
- // Step 9: Determine overall auth strategy
385
- const allAuth = new Set(analyzedEndpoints.flatMap(ep => ep.authIndicators));
386
- const topStrategy = allAuth.has('signature') ? 'intercept' : allAuth.has('bearer') || allAuth.has('csrf') ? 'header' : allAuth.size === 0 ? 'public' : 'cookie';
418
+ // Step 7+8: Analyze endpoints and infer capabilities
419
+ const { analyzed: analyzedEndpoints, totalCount } = analyzeEndpoints(networkEntries);
420
+ const { capabilities, topStrategy, authIndicators } = inferCapabilitiesFromEndpoints(analyzedEndpoints, stores, { site: opts.site, goal: opts.goal, url });
421
+ // Step 9: Assemble result and write artifacts
387
422
  const siteName = opts.site ?? detectSiteName(metadata.url || url);
388
423
  const targetDir = opts.outDir ?? path.join('.opencli', 'explore', siteName);
389
- await fs.promises.mkdir(targetDir, { recursive: true });
390
424
  const result = {
391
425
  site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
392
426
  framework, stores, top_strategy: topStrategy,
393
- endpoint_count: analyzedEndpoints.length + [...seen.values()].filter(ep => ep.score < 5).length,
427
+ endpoint_count: totalCount,
394
428
  api_endpoint_count: analyzedEndpoints.length,
395
- capabilities, auth_indicators: [...allAuth],
429
+ capabilities, auth_indicators: authIndicators,
396
430
  };
397
- // Write artifacts
398
- const writeTasks = [];
399
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
400
- site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
401
- framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
402
- top_strategy: topStrategy, explored_at: new Date().toISOString(),
403
- }, null, 2)));
404
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
405
- pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
406
- contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
407
- itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
408
- detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
409
- })), null, 2)));
410
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(capabilities, null, 2)));
411
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
412
- top_strategy: topStrategy, indicators: [...allAuth], framework,
413
- }, null, 2)));
414
- if (stores.length > 0) {
415
- writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
416
- }
417
- await Promise.all(writeTasks);
431
+ await writeExploreArtifacts(targetDir, result, analyzedEndpoints, stores);
418
432
  return { ...result, out_dir: targetDir };
419
433
  })(), { timeout: exploreTimeout, label: `Explore ${url}` });
420
434
  }, { 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,25 @@
1
+ export interface ExternalCliInstall {
2
+ mac?: string;
3
+ linux?: string;
4
+ windows?: string;
5
+ default?: string;
6
+ }
7
+ export interface ExternalCliConfig {
8
+ name: string;
9
+ binary: string;
10
+ description?: string;
11
+ homepage?: string;
12
+ tags?: string[];
13
+ install?: ExternalCliInstall;
14
+ }
15
+ export declare function loadExternalClis(): ExternalCliConfig[];
16
+ export declare function isBinaryInstalled(binary: string): boolean;
17
+ export declare function getInstallCmd(installConfig?: ExternalCliInstall): string | null;
18
+ export declare function installExternalCli(cli: ExternalCliConfig): boolean;
19
+ export declare function executeExternalCli(name: string, args: string[], preloaded?: ExternalCliConfig[]): void;
20
+ export interface RegisterOptions {
21
+ binary?: string;
22
+ install?: string;
23
+ description?: string;
24
+ }
25
+ export declare function registerExternalCli(name: string, opts?: RegisterOptions): void;
@@ -0,0 +1,156 @@
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
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ function getUserRegistryPath() {
11
+ const home = os.homedir();
12
+ return path.join(home, '.opencli', 'external-clis.yaml');
13
+ }
14
+ export function loadExternalClis() {
15
+ const configs = new Map();
16
+ // 1. Load built-in
17
+ const builtinPath = path.resolve(__dirname, 'external-clis.yaml');
18
+ try {
19
+ if (fs.existsSync(builtinPath)) {
20
+ const raw = fs.readFileSync(builtinPath, 'utf8');
21
+ const parsed = (yaml.load(raw) || []);
22
+ for (const item of parsed)
23
+ configs.set(item.name, item);
24
+ }
25
+ }
26
+ catch (err) {
27
+ log.warn(`Failed to parse built-in external-clis.yaml: ${err.message}`);
28
+ }
29
+ // 2. Load user custom
30
+ const userPath = getUserRegistryPath();
31
+ try {
32
+ if (fs.existsSync(userPath)) {
33
+ const raw = fs.readFileSync(userPath, 'utf8');
34
+ const parsed = (yaml.load(raw) || []);
35
+ for (const item of parsed) {
36
+ configs.set(item.name, item); // Overwrite built-in if duplicated
37
+ }
38
+ }
39
+ }
40
+ catch (err) {
41
+ log.warn(`Failed to parse user external-clis.yaml: ${err.message}`);
42
+ }
43
+ return Array.from(configs.values()).sort((a, b) => a.name.localeCompare(b.name));
44
+ }
45
+ export function isBinaryInstalled(binary) {
46
+ try {
47
+ const isWindows = os.platform() === 'win32';
48
+ execFileSync(isWindows ? 'where' : 'which', [binary], { stdio: 'ignore' });
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ export function getInstallCmd(installConfig) {
56
+ if (!installConfig)
57
+ return null;
58
+ const platform = os.platform();
59
+ if (platform === 'darwin' && installConfig.mac)
60
+ return installConfig.mac;
61
+ if (platform === 'linux' && installConfig.linux)
62
+ return installConfig.linux;
63
+ if (platform === 'win32' && installConfig.windows)
64
+ return installConfig.windows;
65
+ if (installConfig.default)
66
+ return installConfig.default;
67
+ return null;
68
+ }
69
+ export function installExternalCli(cli) {
70
+ if (!cli.install) {
71
+ console.error(chalk.red(`No auto-install command configured for '${cli.name}'.`));
72
+ console.error(`Please install '${cli.binary}' manually.`);
73
+ return false;
74
+ }
75
+ const cmd = getInstallCmd(cli.install);
76
+ if (!cmd) {
77
+ console.error(chalk.red(`No install command for your platform (${os.platform()}) for '${cli.name}'.`));
78
+ if (cli.homepage)
79
+ console.error(`See: ${cli.homepage}`);
80
+ return false;
81
+ }
82
+ console.log(chalk.cyan(`🔹 '${cli.name}' is not installed. Auto-installing...`));
83
+ console.log(chalk.dim(`$ ${cmd}`));
84
+ try {
85
+ execSync(cmd, { stdio: 'inherit' });
86
+ console.log(chalk.green(`✅ Installed '${cli.name}' successfully.\n`));
87
+ return true;
88
+ }
89
+ catch (err) {
90
+ console.error(chalk.red(`❌ Failed to install '${cli.name}': ${err.message}`));
91
+ return false;
92
+ }
93
+ }
94
+ export function executeExternalCli(name, args, preloaded) {
95
+ const configs = preloaded ?? loadExternalClis();
96
+ const cli = configs.find((c) => c.name === name);
97
+ if (!cli) {
98
+ throw new Error(`External CLI '${name}' not found in registry.`);
99
+ }
100
+ // 1. Check if installed
101
+ if (!isBinaryInstalled(cli.binary)) {
102
+ // 2. Try to auto install
103
+ const success = installExternalCli(cli);
104
+ if (!success) {
105
+ process.exitCode = 1;
106
+ return;
107
+ }
108
+ }
109
+ // 3. Passthrough execution with stdio inherited
110
+ const result = spawnSync(cli.binary, args, { stdio: 'inherit' });
111
+ if (result.error) {
112
+ console.error(chalk.red(`Failed to execute '${cli.binary}': ${result.error.message}`));
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ if (result.status !== null) {
117
+ process.exitCode = result.status;
118
+ }
119
+ }
120
+ export function registerExternalCli(name, opts) {
121
+ const userPath = getUserRegistryPath();
122
+ const configDir = path.dirname(userPath);
123
+ if (!fs.existsSync(configDir)) {
124
+ fs.mkdirSync(configDir, { recursive: true });
125
+ }
126
+ let items = [];
127
+ if (fs.existsSync(userPath)) {
128
+ try {
129
+ const raw = fs.readFileSync(userPath, 'utf8');
130
+ items = (yaml.load(raw) || []);
131
+ }
132
+ catch {
133
+ // Ignore
134
+ }
135
+ }
136
+ const existingIndex = items.findIndex((c) => c.name === name);
137
+ const newItem = {
138
+ name,
139
+ binary: opts?.binary || name,
140
+ };
141
+ if (opts?.description)
142
+ newItem.description = opts.description;
143
+ if (opts?.install)
144
+ newItem.install = { default: opts.install };
145
+ if (existingIndex >= 0) {
146
+ items[existingIndex] = { ...items[existingIndex], ...newItem };
147
+ console.log(chalk.green(`Updated '${name}' in user registry.`));
148
+ }
149
+ else {
150
+ items.push(newItem);
151
+ console.log(chalk.green(`Registered '${name}' in user registry.`));
152
+ }
153
+ const dump = yaml.dump(items, { indent: 2, sortKeys: true });
154
+ fs.writeFileSync(userPath, dump, 'utf8');
155
+ console.log(chalk.dim(userPath));
156
+ }
package/dist/main.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as os from 'node:os';
6
6
  import * as path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
- import { discoverClis } from './engine.js';
8
+ import { discoverClis } from './discovery.js';
9
9
  import { getCompletions } from './completion.js';
10
10
  import { runCli } from './cli.js';
11
11
  const __filename = fileURLToPath(import.meta.url);
@@ -4,8 +4,14 @@
4
4
  */
5
5
  import { render } from '../template.js';
6
6
  export async function stepNavigate(page, params, data, args) {
7
- const url = render(params, { args, data });
8
- await page.goto(String(url));
7
+ if (typeof params === 'object' && params && 'url' in params) {
8
+ const url = String(render(params.url, { args, data }));
9
+ await page.goto(url, { waitUntil: params.waitUntil, settleMs: params.settleMs });
10
+ }
11
+ else {
12
+ const url = render(params, { args, data });
13
+ await page.goto(String(url));
14
+ }
9
15
  return data;
10
16
  }
11
17
  export async function stepClick(page, params, data, args) {
@@ -49,3 +49,5 @@ export declare function getRegistry(): Map<string, CliCommand>;
49
49
  export declare function fullName(cmd: CliCommand): string;
50
50
  export declare function strategyLabel(cmd: CliCommand): string;
51
51
  export declare function registerCommand(cmd: CliCommand): void;
52
+ export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
53
+ export type { SerializedArg } from './serialization.js';
package/dist/registry.js CHANGED
@@ -43,3 +43,5 @@ export function strategyLabel(cmd) {
43
43
  export function registerCommand(cmd) {
44
44
  _registry.set(fullName(cmd), cmd);
45
45
  }
46
+ // Re-export serialization helpers from their dedicated module
47
+ export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
package/dist/runtime.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import type { IPage } from './types.js';
2
+ /**
3
+ * Returns the appropriate browser factory based on environment config.
4
+ * Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
5
+ */
6
+ export declare function getBrowserFactory(): new () => IBrowserFactory;
2
7
  export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
3
8
  export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
4
9
  export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;