@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
@@ -19,7 +19,6 @@ cli({
19
19
  columns: ['field', 'value'],
20
20
  func: async (page, _kwargs) => {
21
21
  await page.goto('https://creator.xiaohongshu.com/new/home');
22
- await page.wait(3);
23
22
  const data = await page.evaluate(`
24
23
  async () => {
25
24
  try {
@@ -29,7 +29,6 @@ cli({
29
29
  const period = kwargs.period || 'seven';
30
30
  // Navigate to creator center for cookie context
31
31
  await page.goto('https://creator.xiaohongshu.com/new/home');
32
- await page.wait(3);
33
32
  const data = await page.evaluate(`
34
33
  async () => {
35
34
  try {
@@ -16,16 +16,15 @@ cli({
16
16
  domain: 'www.xiaohongshu.com',
17
17
  strategy: Strategy.COOKIE,
18
18
  args: [
19
- { name: 'note_id', required: true, help: 'Note ID (from URL)' },
19
+ { name: 'note-id', required: true, help: 'Note ID (from URL)' },
20
20
  { name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
21
21
  ],
22
22
  columns: ['index', 'type', 'status', 'size'],
23
23
  func: async (page, kwargs) => {
24
- const noteId = kwargs.note_id;
24
+ const noteId = kwargs['note-id'];
25
25
  const output = kwargs.output;
26
26
  // Navigate to note page
27
27
  await page.goto(`https://www.xiaohongshu.com/explore/${noteId}`);
28
- await page.wait(3);
29
28
  // Extract note info and media URLs
30
29
  const data = await page.evaluate(`
31
30
  (() => {
@@ -15,7 +15,6 @@ columns: [title, author, likes, type, url]
15
15
 
16
16
  pipeline:
17
17
  - navigate: https://www.xiaohongshu.com/explore
18
- - wait: 3
19
18
  - tap:
20
19
  store: feed
21
20
  action: fetchFeeds
@@ -19,7 +19,6 @@ columns: [rank, user, action, content, note, time]
19
19
 
20
20
  pipeline:
21
21
  - navigate: https://www.xiaohongshu.com/notification
22
- - wait: 3
23
22
  - tap:
24
23
  store: notification
25
24
  action: getNotification
@@ -13,12 +13,12 @@ cli({
13
13
  domain: 'www.xiaohongshu.com',
14
14
  strategy: Strategy.COOKIE,
15
15
  args: [
16
- { name: 'keyword', required: true, help: 'Search keyword' },
16
+ { name: 'query', required: true, positional: true, help: 'Search keyword' },
17
17
  { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
18
18
  ],
19
19
  columns: ['rank', 'title', 'author', 'likes'],
20
20
  func: async (page, kwargs) => {
21
- const keyword = encodeURIComponent(kwargs.keyword);
21
+ const keyword = encodeURIComponent(kwargs.query);
22
22
  await page.goto(`https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`);
23
23
  await page.wait(3);
24
24
  // Scroll a couple of times to load more results
@@ -27,7 +27,7 @@ cli({
27
27
  strategy: Strategy.COOKIE,
28
28
  browser: true,
29
29
  args: [
30
- { name: 'id', type: 'string', required: true, help: 'User id or profile URL' },
30
+ { name: 'id', type: 'string', required: true, positional: true, help: 'User id or profile URL' },
31
31
  { name: 'limit', type: 'int', default: 15, help: 'Number of notes to return' },
32
32
  ],
33
33
  columns: ['id', 'title', 'type', 'likes', 'url'],
@@ -35,7 +35,6 @@ cli({
35
35
  const userId = normalizeXhsUserId(String(kwargs.id));
36
36
  const limit = Math.max(1, Number(kwargs.limit ?? 15));
37
37
  await page.goto(`https://www.xiaohongshu.com/user/profile/${userId}`);
38
- await page.wait(3);
39
38
  let snapshot = await readUserSnapshot(page);
40
39
  let results = extractXhsUserNotes(snapshot ?? {}, userId);
41
40
  let previousCount = results.length;
@@ -16,7 +16,6 @@ cli({
16
16
  func: async (page, kwargs) => {
17
17
  const symbol = kwargs.symbol.toUpperCase().trim();
18
18
  await page.goto(`https://finance.yahoo.com/quote/${encodeURIComponent(symbol)}/`);
19
- await page.wait(3);
20
19
  const data = await page.evaluate(`
21
20
  (async () => {
22
21
  const sym = '${symbol}';
@@ -10,7 +10,7 @@ cli({
10
10
  domain: 'www.youtube.com',
11
11
  strategy: Strategy.COOKIE,
12
12
  args: [
13
- { name: 'query', required: true, help: 'Search query' },
13
+ { name: 'query', required: true, positional: true, help: 'Search query' },
14
14
  { name: 'limit', type: 'int', default: 20, help: 'Max results (max 50)' },
15
15
  ],
16
16
  columns: ['rank', 'title', 'channel', 'views', 'duration', 'url'],
@@ -19,7 +19,7 @@ cli({
19
19
  domain: 'www.youtube.com',
20
20
  strategy: Strategy.COOKIE,
21
21
  args: [
22
- { name: 'url', required: true, help: 'YouTube video URL or video ID' },
22
+ { name: 'url', required: true, positional: true, help: 'YouTube video URL or video ID' },
23
23
  { name: 'lang', required: false, help: 'Language code (e.g. en, zh-Hans). Omit to auto-select' },
24
24
  { name: 'mode', required: false, default: 'grouped', help: 'Output mode: grouped (readable paragraphs) or raw (every segment)' },
25
25
  ],
@@ -10,7 +10,7 @@ cli({
10
10
  domain: 'www.youtube.com',
11
11
  strategy: Strategy.COOKIE,
12
12
  args: [
13
- { name: 'url', required: true, help: 'YouTube video URL or video ID' },
13
+ { name: 'url', required: true, positional: true, help: 'YouTube video URL or video ID' },
14
14
  ],
15
15
  columns: ['field', 'value'],
16
16
  func: async (page, kwargs) => {
@@ -72,7 +72,7 @@ cli({
72
72
  domain: 'zhuanlan.zhihu.com',
73
73
  strategy: Strategy.COOKIE,
74
74
  args: [
75
- { name: 'url', required: true, help: 'Article URL (zhuanlan.zhihu.com/p/xxx)' },
75
+ { name: 'url', required: true, positional: true, help: 'Article URL (zhuanlan.zhihu.com/p/xxx)' },
76
76
  { name: 'output', default: './zhihu-articles', help: 'Output directory' },
77
77
  { name: 'download-images', type: 'boolean', default: false, help: 'Download images locally' },
78
78
  ],
@@ -83,7 +83,6 @@ cli({
83
83
  const downloadImages = kwargs['download-images'];
84
84
  // Navigate to article page
85
85
  await page.goto(url);
86
- await page.wait(3);
87
86
  // Extract article content
88
87
  const data = await page.evaluate(`
89
88
  (() => {
@@ -6,7 +6,7 @@ cli({
6
6
  domain: 'www.zhihu.com',
7
7
  strategy: Strategy.COOKIE,
8
8
  args: [
9
- { name: 'id', required: true, help: 'Question ID (numeric)' },
9
+ { name: 'id', required: true, positional: true, help: 'Question ID (numeric)' },
10
10
  { name: 'limit', type: 'int', default: 5, help: 'Number of answers' },
11
11
  ],
12
12
  columns: ['rank', 'author', 'votes', 'content'],
@@ -4,10 +4,11 @@ description: 知乎搜索
4
4
  domain: www.zhihu.com
5
5
 
6
6
  args:
7
- keyword:
7
+ query:
8
+ positional: true
8
9
  type: str
9
10
  required: true
10
- description: Search keyword
11
+ description: Search query
11
12
  limit:
12
13
  type: int
13
14
  default: 10
@@ -19,7 +20,7 @@ pipeline:
19
20
  - evaluate: |
20
21
  (async () => {
21
22
  const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/<em>/g, '').replace(/<\/em>/g, '').trim();
22
- const keyword = ${{ args.keyword | json }};
23
+ const keyword = ${{ args.query | json }};
23
24
  const limit = ${{ args.limit }};
24
25
  const res = await fetch('https://www.zhihu.com/api/v4/search_v3?q=' + encodeURIComponent(keyword) + '&t=general&offset=0&limit=' + limit, {
25
26
  credentials: 'include'
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Commander adapter: bridges Registry commands to Commander subcommands.
3
+ *
4
+ * This is a THIN adapter — it only handles:
5
+ * 1. Commander arg/option registration
6
+ * 2. Collecting kwargs from Commander's action args
7
+ * 3. Calling executeCommand (which handles browser sessions, validation, etc.)
8
+ * 4. Rendering output and errors
9
+ *
10
+ * All execution logic lives in execution.ts.
11
+ */
12
+ import { Command } from 'commander';
13
+ import { type CliCommand } from './registry.js';
14
+ /**
15
+ * Register a single CliCommand as a Commander subcommand.
16
+ */
17
+ export declare function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): void;
18
+ /**
19
+ * Register all commands from the registry onto a Commander program.
20
+ */
21
+ export declare function registerAllCommands(program: Command, siteGroups: Map<string, Command>): void;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Commander adapter: bridges Registry commands to Commander subcommands.
3
+ *
4
+ * This is a THIN adapter — it only handles:
5
+ * 1. Commander arg/option registration
6
+ * 2. Collecting kwargs from Commander's action args
7
+ * 3. Calling executeCommand (which handles browser sessions, validation, etc.)
8
+ * 4. Rendering output and errors
9
+ *
10
+ * All execution logic lives in execution.ts.
11
+ */
12
+ import chalk from 'chalk';
13
+ import { fullName, getRegistry } from './registry.js';
14
+ import { formatRegistryHelpText } from './serialization.js';
15
+ import { render as renderOutput } from './output.js';
16
+ import { executeCommand } from './execution.js';
17
+ import { CliError } from './errors.js';
18
+ /**
19
+ * Register a single CliCommand as a Commander subcommand.
20
+ */
21
+ export function registerCommandToProgram(siteCmd, cmd) {
22
+ if (siteCmd.commands.some((c) => c.name() === cmd.name))
23
+ return;
24
+ const subCmd = siteCmd.command(cmd.name).description(cmd.description);
25
+ // Register positional args first, then named options
26
+ const positionalArgs = [];
27
+ for (const arg of cmd.args) {
28
+ if (arg.positional) {
29
+ const bracket = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
30
+ subCmd.argument(bracket, arg.help ?? '');
31
+ positionalArgs.push(arg);
32
+ }
33
+ else {
34
+ const flag = arg.required ? `--${arg.name} <value>` : `--${arg.name} [value]`;
35
+ if (arg.required)
36
+ subCmd.requiredOption(flag, arg.help ?? '');
37
+ else if (arg.default != null)
38
+ subCmd.option(flag, arg.help ?? '', String(arg.default));
39
+ else
40
+ subCmd.option(flag, arg.help ?? '');
41
+ }
42
+ }
43
+ subCmd
44
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
45
+ .option('-v, --verbose', 'Debug output', false);
46
+ subCmd.addHelpText('after', formatRegistryHelpText(cmd));
47
+ subCmd.action(async (...actionArgs) => {
48
+ const actionOpts = actionArgs[positionalArgs.length] ?? {};
49
+ const startTime = Date.now();
50
+ // ── Collect kwargs ──────────────────────────────────────────────────
51
+ const kwargs = {};
52
+ for (let i = 0; i < positionalArgs.length; i++) {
53
+ const v = actionArgs[i];
54
+ if (v !== undefined)
55
+ kwargs[positionalArgs[i].name] = v;
56
+ }
57
+ for (const arg of cmd.args) {
58
+ if (arg.positional)
59
+ continue;
60
+ const camelName = arg.name.replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
61
+ const v = actionOpts[arg.name] ?? actionOpts[camelName];
62
+ if (v !== undefined)
63
+ kwargs[arg.name] = v;
64
+ }
65
+ // ── Execute + render ────────────────────────────────────────────────
66
+ try {
67
+ if (actionOpts.verbose)
68
+ process.env.OPENCLI_VERBOSE = '1';
69
+ const result = await executeCommand(cmd, kwargs, actionOpts.verbose);
70
+ if (actionOpts.verbose && (!result || (Array.isArray(result) && result.length === 0))) {
71
+ console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
72
+ }
73
+ const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
74
+ renderOutput(result, {
75
+ fmt: actionOpts.format,
76
+ columns: resolved.columns,
77
+ title: `${resolved.site}/${resolved.name}`,
78
+ elapsed: (Date.now() - startTime) / 1000,
79
+ source: fullName(resolved),
80
+ footerExtra: resolved.footerExtra?.(kwargs),
81
+ });
82
+ }
83
+ catch (err) {
84
+ if (err instanceof CliError) {
85
+ console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
86
+ if (err.hint)
87
+ console.error(chalk.yellow(`Hint: ${err.hint}`));
88
+ }
89
+ else if (actionOpts.verbose && err.stack) {
90
+ console.error(chalk.red(err.stack));
91
+ }
92
+ else {
93
+ console.error(chalk.red(`Error: ${err.message ?? err}`));
94
+ }
95
+ process.exitCode = 1;
96
+ }
97
+ });
98
+ }
99
+ /**
100
+ * Register all commands from the registry onto a Commander program.
101
+ */
102
+ export function registerAllCommands(program, siteGroups) {
103
+ for (const [, cmd] of getRegistry()) {
104
+ let siteCmd = siteGroups.get(cmd.site);
105
+ if (!siteCmd) {
106
+ siteCmd = program.command(cmd.site).description(`${cmd.site} commands`);
107
+ siteGroups.set(cmd.site, siteCmd);
108
+ }
109
+ registerCommandToProgram(siteCmd, cmd);
110
+ }
111
+ }
@@ -7,14 +7,8 @@
7
7
  * TS modules are loaded lazily only when their command is executed.
8
8
  * 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
9
9
  */
10
- import { type CliCommand } from './registry.js';
11
- import type { IPage } from './types.js';
12
10
  /**
13
11
  * Discover and register CLI commands.
14
12
  * Uses pre-compiled manifest when available for instant startup.
15
13
  */
16
14
  export declare function discoverClis(...dirs: string[]): Promise<void>;
17
- /**
18
- * Execute a CLI command. Handles lazy-loading of TS modules.
19
- */
20
- export declare function executeCommand(cmd: CliCommand, page: IPage | null, rawKwargs: Record<string, any>, debug?: boolean): Promise<any>;
@@ -11,11 +11,7 @@ import * as fs from 'node:fs';
11
11
  import * as path from 'node:path';
12
12
  import yaml from 'js-yaml';
13
13
  import { Strategy, registerCommand } from './registry.js';
14
- import { executePipeline } from './pipeline.js';
15
14
  import { log } from './logger.js';
16
- import { AdapterLoadError } from './errors.js';
17
- /** Set of TS module paths that have been loaded */
18
- const _loadedModules = new Set();
19
15
  /**
20
16
  * Discover and register CLI commands.
21
17
  * Uses pre-compiled manifest when available for instant startup.
@@ -142,6 +138,7 @@ async function registerYamlCli(filePath, defaultSite) {
142
138
  type: argDef?.type ?? 'str',
143
139
  default: argDef?.default,
144
140
  required: argDef?.required ?? false,
141
+ positional: argDef?.positional ?? false,
145
142
  help: argDef?.description ?? argDef?.help ?? '',
146
143
  choices: argDef?.choices,
147
144
  });
@@ -166,97 +163,3 @@ async function registerYamlCli(filePath, defaultSite) {
166
163
  log.warn(`Failed to load ${filePath}: ${err.message}`);
167
164
  }
168
165
  }
169
- /**
170
- * Validates and coerces arguments based on the command's Arg definitions.
171
- */
172
- function coerceAndValidateArgs(cmdArgs, kwargs) {
173
- const result = { ...kwargs };
174
- for (const argDef of cmdArgs) {
175
- const val = result[argDef.name];
176
- // 1. Check required
177
- if (argDef.required && (val === undefined || val === null || val === '')) {
178
- throw new Error(`Argument "${argDef.name}" is required.\n${argDef.help ? `Hint: ${argDef.help}` : ''}`);
179
- }
180
- if (val !== undefined && val !== null) {
181
- // 2. Type coercion
182
- if (argDef.type === 'int' || argDef.type === 'number') {
183
- const num = Number(val);
184
- if (Number.isNaN(num)) {
185
- throw new Error(`Argument "${argDef.name}" must be a valid number. Received: "${val}"`);
186
- }
187
- result[argDef.name] = num;
188
- }
189
- else if (argDef.type === 'boolean' || argDef.type === 'bool') {
190
- if (typeof val === 'string') {
191
- const lower = val.toLowerCase();
192
- if (lower === 'true' || lower === '1')
193
- result[argDef.name] = true;
194
- else if (lower === 'false' || lower === '0')
195
- result[argDef.name] = false;
196
- else
197
- throw new Error(`Argument "${argDef.name}" must be a boolean (true/false). Received: "${val}"`);
198
- }
199
- else {
200
- result[argDef.name] = Boolean(val);
201
- }
202
- }
203
- // 3. Choices validation
204
- const coercedVal = result[argDef.name];
205
- if (argDef.choices && argDef.choices.length > 0) {
206
- // Only stringent check for string/number types against choices array
207
- if (!argDef.choices.map(String).includes(String(coercedVal))) {
208
- throw new Error(`Argument "${argDef.name}" must be one of: ${argDef.choices.join(', ')}. Received: "${coercedVal}"`);
209
- }
210
- }
211
- }
212
- else if (argDef.default !== undefined) {
213
- // Set default if value is missing
214
- result[argDef.name] = argDef.default;
215
- }
216
- }
217
- return result;
218
- }
219
- /**
220
- * Execute a CLI command. Handles lazy-loading of TS modules.
221
- */
222
- export async function executeCommand(cmd, page, rawKwargs, debug = false) {
223
- let kwargs;
224
- try {
225
- kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
226
- }
227
- catch (err) {
228
- // Re-throw validation errors clearly
229
- throw new Error(`[Argument Validation Error]\n${err.message}`);
230
- }
231
- // Lazy-load TS module on first execution
232
- const internal = cmd;
233
- if (internal._lazy && internal._modulePath) {
234
- const modulePath = internal._modulePath;
235
- if (!_loadedModules.has(modulePath)) {
236
- try {
237
- await import(`file://${modulePath}`);
238
- _loadedModules.add(modulePath);
239
- }
240
- catch (err) {
241
- throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${err.message}`, 'Check that the adapter file exists and has no syntax errors.');
242
- }
243
- }
244
- // After loading, the module's cli() call will have updated the registry
245
- // with the real func/pipeline. Re-fetch the command.
246
- const { getRegistry, fullName } = await import('./registry.js');
247
- const updated = getRegistry().get(fullName(cmd));
248
- if (updated && updated.func) {
249
- return updated.func(page, kwargs, debug);
250
- }
251
- if (updated && updated.pipeline) {
252
- return executePipeline(page, updated.pipeline, { args: kwargs, debug });
253
- }
254
- }
255
- if (cmd.func) {
256
- return cmd.func(page, kwargs, debug);
257
- }
258
- if (cmd.pipeline) {
259
- return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
260
- }
261
- throw new Error(`Command ${cmd.site}/${cmd.name} has no func or pipeline`);
262
- }
@@ -23,13 +23,9 @@ export interface BrowserCookie {
23
23
  httpOnly?: boolean;
24
24
  expirationDate?: number;
25
25
  }
26
- /**
27
- * Check if yt-dlp is available in PATH.
28
- */
26
+ /** Check if yt-dlp is available in PATH. */
29
27
  export declare function checkYtdlp(): boolean;
30
- /**
31
- * Check if ffmpeg is available in PATH.
32
- */
28
+ /** Check if ffmpeg is available in PATH. */
33
29
  export declare function checkFfmpeg(): boolean;
34
30
  /**
35
31
  * Detect content type from URL and optional headers.
@@ -1,42 +1,34 @@
1
1
  /**
2
2
  * Download utilities: HTTP downloads, yt-dlp wrapper, format conversion.
3
3
  */
4
- import { spawn, execSync } from 'node:child_process';
4
+ import { spawn } from 'node:child_process';
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import * as https from 'node:https';
8
8
  import * as http from 'node:http';
9
9
  import * as os from 'node:os';
10
10
  import { URL } from 'node:url';
11
- /**
12
- * Check if yt-dlp is available in PATH.
13
- */
11
+ import { isBinaryInstalled } from '../external.js';
12
+ /** Check if yt-dlp is available in PATH. */
14
13
  export function checkYtdlp() {
15
- try {
16
- execSync('yt-dlp --version', { encoding: 'utf-8', stdio: 'pipe' });
17
- return true;
18
- }
19
- catch {
20
- return false;
21
- }
14
+ return isBinaryInstalled('yt-dlp');
22
15
  }
23
- /**
24
- * Check if ffmpeg is available in PATH.
25
- */
16
+ /** Check if ffmpeg is available in PATH. */
26
17
  export function checkFfmpeg() {
27
- try {
28
- execSync('ffmpeg -version', { encoding: 'utf-8', stdio: 'pipe' });
29
- return true;
30
- }
31
- catch {
32
- return false;
33
- }
18
+ return isBinaryInstalled('ffmpeg');
34
19
  }
20
+ /** Domains that host video content and can be downloaded via yt-dlp. */
21
+ const VIDEO_PLATFORM_DOMAINS = [
22
+ 'youtube.com', 'youtu.be', 'bilibili.com', 'twitter.com',
23
+ 'x.com', 'tiktok.com', 'vimeo.com', 'twitch.tv',
24
+ ];
25
+ const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif']);
26
+ const VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.avi', '.mov', '.mkv', '.flv', '.m3u8', '.ts']);
27
+ const DOC_EXTENSIONS = new Set(['.html', '.htm', '.json', '.xml', '.txt', '.md', '.markdown']);
35
28
  /**
36
29
  * Detect content type from URL and optional headers.
37
30
  */
38
31
  export function detectContentType(url, contentType) {
39
- // Check content-type header first
40
32
  if (contentType) {
41
33
  if (contentType.startsWith('image/'))
42
34
  return 'image';
@@ -45,28 +37,16 @@ export function detectContentType(url, contentType) {
45
37
  if (contentType.startsWith('text/') || contentType.includes('json') || contentType.includes('xml'))
46
38
  return 'document';
47
39
  }
48
- // Detect from URL
49
40
  const urlLower = url.toLowerCase();
50
41
  const ext = path.extname(new URL(url).pathname).toLowerCase();
51
- // Image extensions
52
- if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif'].includes(ext)) {
42
+ if (IMAGE_EXTENSIONS.has(ext))
53
43
  return 'image';
54
- }
55
- // Video extensions
56
- if (['.mp4', '.webm', '.avi', '.mov', '.mkv', '.flv', '.m3u8', '.ts'].includes(ext)) {
44
+ if (VIDEO_EXTENSIONS.has(ext))
57
45
  return 'video';
58
- }
59
- // Video platforms (need yt-dlp)
60
- if (urlLower.includes('youtube.com') || urlLower.includes('youtu.be') ||
61
- urlLower.includes('bilibili.com') || urlLower.includes('twitter.com') ||
62
- urlLower.includes('x.com') || urlLower.includes('tiktok.com') ||
63
- urlLower.includes('vimeo.com') || urlLower.includes('twitch.tv')) {
46
+ if (VIDEO_PLATFORM_DOMAINS.some(d => urlLower.includes(d)))
64
47
  return 'video';
65
- }
66
- // Document extensions
67
- if (['.html', '.htm', '.json', '.xml', '.txt', '.md', '.markdown'].includes(ext)) {
48
+ if (DOC_EXTENSIONS.has(ext))
68
49
  return 'document';
69
- }
70
50
  return 'binary';
71
51
  }
72
52
  /**
@@ -74,14 +54,7 @@ export function detectContentType(url, contentType) {
74
54
  */
75
55
  export function requiresYtdlp(url) {
76
56
  const urlLower = url.toLowerCase();
77
- return (urlLower.includes('youtube.com') ||
78
- urlLower.includes('youtu.be') ||
79
- urlLower.includes('bilibili.com/video') ||
80
- urlLower.includes('twitter.com') ||
81
- urlLower.includes('x.com') ||
82
- urlLower.includes('tiktok.com') ||
83
- urlLower.includes('vimeo.com') ||
84
- urlLower.includes('twitch.tv'));
57
+ return VIDEO_PLATFORM_DOMAINS.some(d => urlLower.includes(d));
85
58
  }
86
59
  /**
87
60
  * HTTP download with progress callback.
@@ -1,4 +1,4 @@
1
1
  /**
2
- * Tests for engine.ts: CLI discovery and command execution.
2
+ * Tests for discovery and execution modules.
3
3
  */
4
4
  export {};
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Tests for engine.ts: CLI discovery and command execution.
2
+ * Tests for discovery and execution modules.
3
3
  */
4
4
  import { describe, it, expect } from 'vitest';
5
- import { discoverClis, executeCommand } from './engine.js';
5
+ import { discoverClis } from './discovery.js';
6
+ import { executeCommand } from './execution.js';
6
7
  import { cli, Strategy } from './registry.js';
7
8
  describe('discoverClis', () => {
8
9
  it('handles non-existent directories gracefully', async () => {
@@ -23,7 +24,7 @@ describe('executeCommand', () => {
23
24
  ],
24
25
  func: async (_page, kwargs) => [{ noteId: kwargs['note-id'] }],
25
26
  });
26
- const result = await executeCommand(cmd, null, { 'note-id': 'abc123' });
27
+ const result = await executeCommand(cmd, { 'note-id': 'abc123' });
27
28
  expect(result).toEqual([{ noteId: 'abc123' }]);
28
29
  });
29
30
  it('executes a command with func', async () => {
@@ -37,7 +38,7 @@ describe('executeCommand', () => {
37
38
  return [{ title: kwargs.query ?? 'default' }];
38
39
  },
39
40
  });
40
- const result = await executeCommand(cmd, null, { query: 'hello' });
41
+ const result = await executeCommand(cmd, { query: 'hello' });
41
42
  expect(result).toEqual([{ title: 'hello' }]);
42
43
  });
43
44
  it('executes a command with pipeline', async () => {
@@ -53,7 +54,7 @@ describe('executeCommand', () => {
53
54
  ],
54
55
  });
55
56
  // Pipeline commands require page for evaluate step, so we'll test the error path
56
- await expect(executeCommand(cmd, null, {})).rejects.toThrow();
57
+ await expect(executeCommand(cmd, {})).rejects.toThrow();
57
58
  });
58
59
  it('throws for command with no func or pipeline', async () => {
59
60
  const cmd = cli({
@@ -62,7 +63,7 @@ describe('executeCommand', () => {
62
63
  description: 'empty command',
63
64
  browser: false,
64
65
  });
65
- await expect(executeCommand(cmd, null, {})).rejects.toThrow('has no func or pipeline');
66
+ await expect(executeCommand(cmd, {})).rejects.toThrow('has no func or pipeline');
66
67
  });
67
68
  it('passes debug flag to func', async () => {
68
69
  let receivedDebug = false;
@@ -76,7 +77,7 @@ describe('executeCommand', () => {
76
77
  return [];
77
78
  },
78
79
  });
79
- await executeCommand(cmd, null, {}, true);
80
+ await executeCommand(cmd, {}, true);
80
81
  expect(receivedDebug).toBe(true);
81
82
  });
82
83
  });
@@ -0,0 +1,22 @@
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 { type CliCommand, type Arg } from './registry.js';
12
+ /**
13
+ * Validates and coerces arguments based on the command's Arg definitions.
14
+ */
15
+ export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: Record<string, any>): Record<string, any>;
16
+ /**
17
+ * Execute a CLI command. Automatically manages browser sessions when needed.
18
+ *
19
+ * This is the unified entry point — callers don't need to care about
20
+ * whether the command requires a browser or not.
21
+ */
22
+ export declare function executeCommand(cmd: CliCommand, rawKwargs: Record<string, any>, debug?: boolean): Promise<any>;