@jackwener/opencli 1.1.0 → 1.2.0

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 (769) 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/CONTRIBUTING.md +39 -1
  8. package/README.md +33 -19
  9. package/README.zh-CN.md +64 -27
  10. package/SKILL.md +102 -33
  11. package/dist/browser/cdp.d.ts +4 -4
  12. package/dist/browser/cdp.js +45 -17
  13. package/dist/browser/daemon-client.d.ts +2 -1
  14. package/dist/browser/dom-helpers.js +38 -7
  15. package/dist/browser/dom-snapshot.d.ts +86 -0
  16. package/dist/browser/dom-snapshot.js +729 -0
  17. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  18. package/dist/browser/dom-snapshot.test.js +212 -0
  19. package/dist/browser/index.d.ts +2 -0
  20. package/dist/browser/index.js +1 -0
  21. package/dist/browser/page.d.ts +18 -25
  22. package/dist/browser/page.js +44 -5
  23. package/dist/build-manifest.d.ts +11 -4
  24. package/dist/build-manifest.js +79 -34
  25. package/dist/build-manifest.test.js +58 -2
  26. package/dist/cli-manifest.json +4273 -1771
  27. package/dist/cli.d.ts +6 -0
  28. package/dist/cli.js +255 -162
  29. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  30. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  31. package/dist/clis/apple-podcasts/search.js +2 -2
  32. package/dist/clis/apple-podcasts/top.js +9 -2
  33. package/dist/clis/arxiv/search.js +1 -1
  34. package/dist/clis/barchart/greeks.js +1 -1
  35. package/dist/clis/barchart/options.js +1 -1
  36. package/dist/clis/barchart/quote.js +1 -1
  37. package/dist/clis/bilibili/download.js +1 -1
  38. package/dist/clis/bilibili/dynamic.js +1 -1
  39. package/dist/clis/bilibili/favorite.js +1 -1
  40. package/dist/clis/bilibili/feed.js +1 -1
  41. package/dist/clis/bilibili/following.js +2 -2
  42. package/dist/clis/bilibili/history.js +1 -1
  43. package/dist/clis/bilibili/me.js +1 -1
  44. package/dist/clis/bilibili/ranking.js +1 -1
  45. package/dist/clis/bilibili/search.js +3 -3
  46. package/dist/clis/bilibili/subtitle.js +2 -2
  47. package/dist/clis/bilibili/user-videos.js +2 -2
  48. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  49. package/dist/clis/bloomberg/businessweek.js +17 -0
  50. package/dist/clis/bloomberg/economics.js +17 -0
  51. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  52. package/dist/clis/bloomberg/feeds.js +15 -0
  53. package/dist/clis/bloomberg/industries.d.ts +1 -0
  54. package/dist/clis/bloomberg/industries.js +17 -0
  55. package/dist/clis/bloomberg/main.d.ts +1 -0
  56. package/dist/clis/bloomberg/main.js +17 -0
  57. package/dist/clis/bloomberg/markets.d.ts +1 -0
  58. package/dist/clis/bloomberg/markets.js +17 -0
  59. package/dist/clis/bloomberg/news.d.ts +1 -0
  60. package/dist/clis/bloomberg/news.js +105 -0
  61. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  62. package/dist/clis/bloomberg/opinions.js +17 -0
  63. package/dist/clis/bloomberg/politics.d.ts +1 -0
  64. package/dist/clis/bloomberg/politics.js +17 -0
  65. package/dist/clis/bloomberg/tech.d.ts +1 -0
  66. package/dist/clis/bloomberg/tech.js +17 -0
  67. package/dist/clis/bloomberg/utils.d.ts +34 -0
  68. package/dist/clis/bloomberg/utils.js +364 -0
  69. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  70. package/dist/clis/bloomberg/utils.test.js +129 -0
  71. package/dist/clis/boss/batchgreet.js +12 -99
  72. package/dist/clis/boss/chatlist.js +9 -26
  73. package/dist/clis/boss/chatmsg.js +11 -42
  74. package/dist/clis/boss/common.d.ts +92 -0
  75. package/dist/clis/boss/common.js +223 -0
  76. package/dist/clis/boss/detail.js +8 -50
  77. package/dist/clis/boss/exchange.js +13 -79
  78. package/dist/clis/boss/greet.js +20 -147
  79. package/dist/clis/boss/invite.js +26 -121
  80. package/dist/clis/boss/joblist.js +6 -31
  81. package/dist/clis/boss/mark.js +12 -85
  82. package/dist/clis/boss/recommend.js +10 -49
  83. package/dist/clis/boss/resume.js +18 -118
  84. package/dist/clis/boss/search.js +13 -61
  85. package/dist/clis/boss/send.js +18 -152
  86. package/dist/clis/boss/stats.js +20 -71
  87. package/dist/clis/chaoxing/assignments.js +1 -1
  88. package/dist/clis/chaoxing/exams.js +1 -1
  89. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  90. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  91. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  92. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  93. package/dist/clis/chatgpt/read.js +1 -1
  94. package/dist/clis/chatwise/export.js +1 -1
  95. package/dist/clis/chatwise/model.js +2 -2
  96. package/dist/clis/chatwise/screenshot.js +1 -1
  97. package/dist/clis/codex/export.js +1 -1
  98. package/dist/clis/codex/model.js +2 -2
  99. package/dist/clis/codex/screenshot.js +1 -1
  100. package/dist/clis/coupang/add-to-cart.js +3 -4
  101. package/dist/clis/coupang/search.js +2 -4
  102. package/dist/clis/coupang/utils.test.d.ts +1 -0
  103. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  104. package/dist/clis/ctrip/search.js +1 -1
  105. package/dist/clis/cursor/export.js +1 -1
  106. package/dist/clis/cursor/model.js +2 -2
  107. package/dist/clis/cursor/screenshot.js +1 -1
  108. package/dist/clis/devto/tag.yaml +34 -0
  109. package/dist/clis/devto/top.yaml +29 -0
  110. package/dist/clis/devto/user.yaml +33 -0
  111. package/dist/clis/douban/book-hot.d.ts +1 -0
  112. package/dist/clis/douban/book-hot.js +14 -0
  113. package/dist/clis/douban/marks.d.ts +1 -0
  114. package/dist/clis/douban/marks.js +115 -0
  115. package/dist/clis/douban/movie-hot.d.ts +1 -0
  116. package/dist/clis/douban/movie-hot.js +14 -0
  117. package/dist/clis/douban/reviews.d.ts +1 -0
  118. package/dist/clis/douban/reviews.js +106 -0
  119. package/dist/clis/douban/search.d.ts +1 -0
  120. package/dist/clis/douban/search.js +16 -0
  121. package/dist/clis/douban/shared.d.ts +4 -0
  122. package/dist/clis/douban/shared.js +155 -0
  123. package/dist/clis/douban/subject.yaml +76 -0
  124. package/dist/clis/douban/top250.yaml +70 -0
  125. package/dist/clis/douban/utils.d.ts +35 -0
  126. package/dist/clis/douban/utils.js +48 -0
  127. package/dist/clis/facebook/add-friend.yaml +43 -0
  128. package/dist/clis/facebook/events.yaml +44 -0
  129. package/dist/clis/facebook/feed.yaml +63 -0
  130. package/dist/clis/facebook/friends.yaml +42 -0
  131. package/dist/clis/facebook/groups.yaml +50 -0
  132. package/dist/clis/facebook/join-group.yaml +44 -0
  133. package/dist/clis/facebook/memories.yaml +39 -0
  134. package/dist/clis/facebook/notifications.yaml +40 -0
  135. package/dist/clis/facebook/profile.yaml +37 -0
  136. package/dist/clis/facebook/search.yaml +46 -0
  137. package/dist/clis/google/news.d.ts +5 -0
  138. package/dist/clis/google/news.js +58 -0
  139. package/dist/clis/google/search.d.ts +10 -0
  140. package/dist/clis/google/search.js +127 -0
  141. package/dist/clis/google/suggest.d.ts +5 -0
  142. package/dist/clis/google/suggest.js +34 -0
  143. package/dist/clis/google/trends.d.ts +5 -0
  144. package/dist/clis/google/trends.js +38 -0
  145. package/dist/clis/google/utils.d.ts +9 -0
  146. package/dist/clis/google/utils.js +23 -0
  147. package/dist/clis/google/utils.test.d.ts +1 -0
  148. package/dist/clis/google/utils.test.js +75 -0
  149. package/dist/clis/grok/ask.d.ts +14 -0
  150. package/dist/clis/grok/ask.js +257 -65
  151. package/dist/clis/grok/ask.test.d.ts +1 -0
  152. package/dist/clis/grok/ask.test.js +36 -0
  153. package/dist/clis/instagram/comment.yaml +52 -0
  154. package/dist/clis/instagram/explore.yaml +43 -0
  155. package/dist/clis/instagram/follow.yaml +41 -0
  156. package/dist/clis/instagram/followers.yaml +51 -0
  157. package/dist/clis/instagram/following.yaml +51 -0
  158. package/dist/clis/instagram/like.yaml +46 -0
  159. package/dist/clis/instagram/profile.yaml +42 -0
  160. package/dist/clis/instagram/save.yaml +46 -0
  161. package/dist/clis/instagram/saved.yaml +40 -0
  162. package/dist/clis/instagram/search.yaml +43 -0
  163. package/dist/clis/instagram/unfollow.yaml +38 -0
  164. package/dist/clis/instagram/unlike.yaml +46 -0
  165. package/dist/clis/instagram/unsave.yaml +46 -0
  166. package/dist/clis/instagram/user.yaml +54 -0
  167. package/dist/clis/jike/comment.js +2 -3
  168. package/dist/clis/jike/create.js +1 -2
  169. package/dist/clis/jike/feed.js +0 -1
  170. package/dist/clis/jike/like.js +1 -2
  171. package/dist/clis/jike/notifications.js +0 -1
  172. package/dist/clis/jike/post.yaml +1 -0
  173. package/dist/clis/jike/repost.js +2 -3
  174. package/dist/clis/jike/search.js +2 -3
  175. package/dist/clis/jike/topic.yaml +1 -0
  176. package/dist/clis/jike/user.yaml +1 -0
  177. package/dist/clis/jimeng/generate.yaml +1 -0
  178. package/dist/clis/jimeng/history.yaml +0 -1
  179. package/dist/clis/linkedin/search.js +7 -7
  180. package/dist/clis/linux-do/category.yaml +2 -0
  181. package/dist/clis/linux-do/search.yaml +4 -3
  182. package/dist/clis/linux-do/topic.yaml +1 -0
  183. package/dist/clis/lobsters/active.yaml +29 -0
  184. package/dist/clis/lobsters/hot.yaml +29 -0
  185. package/dist/clis/lobsters/newest.yaml +29 -0
  186. package/dist/clis/lobsters/tag.yaml +34 -0
  187. package/dist/clis/medium/feed.d.ts +1 -0
  188. package/dist/clis/medium/feed.js +15 -0
  189. package/dist/clis/medium/search.d.ts +1 -0
  190. package/dist/clis/medium/search.js +15 -0
  191. package/dist/clis/medium/shared.d.ts +5 -0
  192. package/dist/clis/medium/shared.js +78 -0
  193. package/dist/clis/medium/user.d.ts +1 -0
  194. package/dist/clis/medium/user.js +15 -0
  195. package/dist/clis/notion/export.js +1 -1
  196. package/dist/clis/reddit/comment.js +3 -4
  197. package/dist/clis/reddit/read.js +4 -5
  198. package/dist/clis/reddit/save.js +2 -3
  199. package/dist/clis/reddit/saved.js +0 -1
  200. package/dist/clis/reddit/search.yaml +1 -0
  201. package/dist/clis/reddit/subreddit.yaml +1 -0
  202. package/dist/clis/reddit/subscribe.js +1 -2
  203. package/dist/clis/reddit/upvote.js +2 -3
  204. package/dist/clis/reddit/upvoted.js +0 -1
  205. package/dist/clis/reddit/user-comments.yaml +1 -0
  206. package/dist/clis/reddit/user-posts.yaml +1 -0
  207. package/dist/clis/reddit/user.yaml +1 -0
  208. package/dist/clis/reuters/search.js +1 -1
  209. package/dist/clis/sinablog/article.d.ts +1 -0
  210. package/dist/clis/sinablog/article.js +14 -0
  211. package/dist/clis/sinablog/hot.d.ts +1 -0
  212. package/dist/clis/sinablog/hot.js +14 -0
  213. package/dist/clis/sinablog/search.d.ts +1 -0
  214. package/dist/clis/sinablog/search.js +51 -0
  215. package/dist/clis/sinablog/shared.d.ts +7 -0
  216. package/dist/clis/sinablog/shared.js +187 -0
  217. package/dist/clis/sinablog/user.d.ts +1 -0
  218. package/dist/clis/sinablog/user.js +15 -0
  219. package/dist/clis/smzdm/search.js +2 -3
  220. package/dist/clis/stackoverflow/search.yaml +1 -0
  221. package/dist/clis/steam/top-sellers.yaml +29 -0
  222. package/dist/clis/substack/feed.d.ts +1 -0
  223. package/dist/clis/substack/feed.js +15 -0
  224. package/dist/clis/substack/publication.d.ts +1 -0
  225. package/dist/clis/substack/publication.js +15 -0
  226. package/dist/clis/substack/search.d.ts +1 -0
  227. package/dist/clis/substack/search.js +77 -0
  228. package/dist/clis/substack/shared.d.ts +4 -0
  229. package/dist/clis/substack/shared.js +129 -0
  230. package/dist/clis/tiktok/comment.yaml +66 -0
  231. package/dist/clis/tiktok/explore.yaml +39 -0
  232. package/dist/clis/tiktok/follow.yaml +39 -0
  233. package/dist/clis/tiktok/following.yaml +46 -0
  234. package/dist/clis/tiktok/friends.yaml +47 -0
  235. package/dist/clis/tiktok/like.yaml +38 -0
  236. package/dist/clis/tiktok/live.yaml +51 -0
  237. package/dist/clis/tiktok/notifications.yaml +52 -0
  238. package/dist/clis/tiktok/profile.yaml +45 -0
  239. package/dist/clis/tiktok/save.yaml +34 -0
  240. package/dist/clis/tiktok/search.yaml +46 -0
  241. package/dist/clis/tiktok/unfollow.yaml +44 -0
  242. package/dist/clis/tiktok/unlike.yaml +38 -0
  243. package/dist/clis/tiktok/unsave.yaml +36 -0
  244. package/dist/clis/tiktok/user.yaml +44 -0
  245. package/dist/clis/twitter/accept.js +2 -2
  246. package/dist/clis/twitter/article.js +2 -2
  247. package/dist/clis/twitter/block.d.ts +1 -0
  248. package/dist/clis/twitter/block.js +88 -0
  249. package/dist/clis/twitter/delete.js +1 -1
  250. package/dist/clis/twitter/download.d.ts +1 -1
  251. package/dist/clis/twitter/download.js +3 -3
  252. package/dist/clis/twitter/followers.js +1 -1
  253. package/dist/clis/twitter/following.js +1 -1
  254. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  255. package/dist/clis/twitter/hide-reply.js +66 -0
  256. package/dist/clis/twitter/like.js +1 -1
  257. package/dist/clis/twitter/post.js +1 -1
  258. package/dist/clis/twitter/reply-dm.js +1 -1
  259. package/dist/clis/twitter/reply.js +2 -2
  260. package/dist/clis/twitter/search.js +1 -1
  261. package/dist/clis/twitter/thread.js +2 -2
  262. package/dist/clis/twitter/timeline.d.ts +23 -0
  263. package/dist/clis/twitter/timeline.js +42 -14
  264. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  265. package/dist/clis/twitter/timeline.test.js +102 -0
  266. package/dist/clis/twitter/trending.d.ts +1 -0
  267. package/dist/clis/twitter/trending.js +91 -0
  268. package/dist/clis/twitter/unblock.d.ts +1 -0
  269. package/dist/clis/twitter/unblock.js +71 -0
  270. package/dist/clis/v2ex/topic.yaml +1 -0
  271. package/dist/clis/weibo/hot.js +0 -1
  272. package/dist/clis/weread/book.js +1 -1
  273. package/dist/clis/weread/highlights.js +1 -1
  274. package/dist/clis/weread/notes.js +1 -1
  275. package/dist/clis/weread/search.js +1 -1
  276. package/dist/clis/wikipedia/random.d.ts +1 -0
  277. package/dist/clis/wikipedia/random.js +19 -0
  278. package/dist/clis/wikipedia/search.js +4 -4
  279. package/dist/clis/wikipedia/summary.js +4 -9
  280. package/dist/clis/wikipedia/trending.d.ts +1 -0
  281. package/dist/clis/wikipedia/trending.js +35 -0
  282. package/dist/clis/wikipedia/utils.d.ts +28 -0
  283. package/dist/clis/wikipedia/utils.js +13 -0
  284. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +15 -0
  285. package/dist/clis/xiaohongshu/creator-note-detail.js +69 -5
  286. package/dist/clis/xiaohongshu/creator-note-detail.test.js +82 -33
  287. package/dist/clis/xiaohongshu/creator-notes.js +35 -5
  288. package/dist/clis/xiaohongshu/creator-notes.test.js +37 -6
  289. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  290. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  291. package/dist/clis/xiaohongshu/download.js +2 -3
  292. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  293. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  294. package/dist/clis/xiaohongshu/search.js +2 -2
  295. package/dist/clis/xiaohongshu/user.js +1 -2
  296. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  297. package/dist/clis/xueqiu/search.yaml +2 -1
  298. package/dist/clis/xueqiu/stock.yaml +2 -0
  299. package/dist/clis/yahoo-finance/quote.js +1 -2
  300. package/dist/clis/youtube/search.js +1 -1
  301. package/dist/clis/youtube/transcript.js +1 -1
  302. package/dist/clis/youtube/video.js +1 -1
  303. package/dist/clis/zhihu/download.js +1 -2
  304. package/dist/clis/zhihu/question.js +1 -1
  305. package/dist/clis/zhihu/search.yaml +4 -3
  306. package/dist/commanderAdapter.d.ts +21 -0
  307. package/dist/commanderAdapter.js +117 -0
  308. package/dist/{engine.d.ts → discovery.d.ts} +6 -4
  309. package/dist/{engine.js → discovery.js} +93 -104
  310. package/dist/doctor.js +3 -1
  311. package/dist/doctor.test.js +46 -2
  312. package/dist/download/index.d.ts +2 -6
  313. package/dist/download/index.js +19 -46
  314. package/dist/engine.test.d.ts +0 -3
  315. package/dist/engine.test.js +80 -11
  316. package/dist/execution.d.ts +24 -0
  317. package/dist/execution.js +153 -0
  318. package/dist/explore.d.ts +76 -3
  319. package/dist/explore.js +132 -111
  320. package/dist/external-clis.yaml +48 -0
  321. package/dist/external.d.ts +7 -2
  322. package/dist/external.js +11 -14
  323. package/dist/generate.d.ts +41 -2
  324. package/dist/generate.js +5 -4
  325. package/dist/main.js +2 -1
  326. package/dist/pipeline/executor.d.ts +2 -2
  327. package/dist/pipeline/executor.js +2 -2
  328. package/dist/pipeline/executor.test.js +33 -6
  329. package/dist/pipeline/registry.d.ts +1 -1
  330. package/dist/pipeline/steps/browser.d.ts +7 -7
  331. package/dist/pipeline/steps/browser.js +21 -7
  332. package/dist/pipeline/steps/fetch.d.ts +1 -1
  333. package/dist/pipeline/steps/fetch.js +11 -7
  334. package/dist/pipeline/steps/transform.d.ts +6 -5
  335. package/dist/pipeline/steps/transform.js +30 -9
  336. package/dist/pipeline/template.d.ts +6 -6
  337. package/dist/pipeline/template.js +43 -5
  338. package/dist/pipeline/template.test.js +18 -0
  339. package/dist/pipeline/transform.test.js +11 -0
  340. package/dist/plugin.d.ts +31 -0
  341. package/dist/plugin.js +216 -0
  342. package/dist/plugin.test.d.ts +4 -0
  343. package/dist/plugin.test.js +76 -0
  344. package/dist/registry-api.d.ts +11 -0
  345. package/dist/registry-api.js +9 -0
  346. package/dist/registry.d.ts +13 -0
  347. package/dist/registry.js +8 -1
  348. package/dist/runtime.d.ts +5 -0
  349. package/dist/runtime.js +8 -0
  350. package/dist/serialization.d.ts +34 -0
  351. package/dist/serialization.js +63 -0
  352. package/dist/synthesize.d.ts +94 -4
  353. package/dist/synthesize.js +5 -4
  354. package/dist/types.d.ts +43 -27
  355. package/dist/validate.js +8 -2
  356. package/docs/.vitepress/config.mts +20 -7
  357. package/docs/adapters/browser/arxiv.md +27 -0
  358. package/docs/adapters/browser/barchart.md +33 -0
  359. package/docs/adapters/browser/bilibili.md +9 -0
  360. package/docs/adapters/browser/bloomberg.md +70 -0
  361. package/docs/adapters/browser/chaoxing.md +39 -0
  362. package/docs/adapters/browser/devto.md +35 -0
  363. package/docs/adapters/browser/douban.md +38 -0
  364. package/docs/adapters/browser/facebook.md +36 -0
  365. package/docs/adapters/browser/google.md +62 -0
  366. package/docs/adapters/browser/grok.md +53 -0
  367. package/docs/adapters/browser/hf.md +42 -0
  368. package/docs/adapters/browser/instagram.md +46 -0
  369. package/docs/adapters/browser/jike.md +45 -0
  370. package/docs/adapters/browser/jimeng.md +39 -0
  371. package/docs/adapters/browser/linux-do.md +45 -0
  372. package/docs/adapters/browser/lobsters.md +32 -0
  373. package/docs/adapters/browser/medium.md +32 -0
  374. package/docs/adapters/browser/reddit.md +9 -0
  375. package/docs/adapters/browser/sinablog.md +36 -0
  376. package/docs/adapters/browser/sinafinance.md +35 -0
  377. package/docs/adapters/browser/stackoverflow.md +35 -0
  378. package/docs/adapters/browser/steam.md +26 -0
  379. package/docs/adapters/browser/substack.md +38 -0
  380. package/docs/adapters/browser/tiktok.md +68 -0
  381. package/docs/adapters/browser/twitter.md +3 -0
  382. package/docs/adapters/browser/weread.md +48 -0
  383. package/docs/adapters/browser/wikipedia.md +39 -0
  384. package/docs/adapters/browser/xiaohongshu.md +5 -1
  385. package/docs/adapters/browser/xueqiu.md +10 -0
  386. package/docs/adapters/browser/yahoo-finance.md +6 -5
  387. package/docs/adapters/desktop/antigravity.md +6 -0
  388. package/docs/adapters/desktop/chatgpt.md +5 -4
  389. package/docs/adapters/desktop/codex.md +5 -1
  390. package/docs/adapters/desktop/cursor.md +4 -0
  391. package/docs/adapters/desktop/discord.md +7 -7
  392. package/docs/adapters/index.md +14 -4
  393. package/docs/advanced/download.md +4 -4
  394. package/docs/developer/architecture.md +17 -4
  395. package/docs/guide/getting-started.md +1 -0
  396. package/docs/guide/plugins.md +153 -0
  397. package/docs/zh/guide/plugins.md +107 -0
  398. package/extension/src/background.ts +18 -11
  399. package/package.json +10 -5
  400. package/scripts/check-doc-coverage.sh +69 -0
  401. package/scripts/clean-dist.cjs +13 -0
  402. package/scripts/copy-yaml.cjs +7 -0
  403. package/src/browser/cdp.ts +77 -32
  404. package/src/browser/daemon-client.ts +2 -1
  405. package/src/browser/dom-helpers.ts +38 -7
  406. package/src/browser/dom-snapshot.test.ts +249 -0
  407. package/src/browser/dom-snapshot.ts +770 -0
  408. package/src/browser/index.ts +2 -0
  409. package/src/browser/page.ts +57 -20
  410. package/src/build-manifest.test.ts +70 -2
  411. package/src/build-manifest.ts +114 -40
  412. package/src/cli.ts +287 -139
  413. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  414. package/src/clis/apple-podcasts/search.ts +2 -2
  415. package/src/clis/apple-podcasts/top.ts +12 -2
  416. package/src/clis/arxiv/search.ts +1 -1
  417. package/src/clis/barchart/greeks.ts +1 -1
  418. package/src/clis/barchart/options.ts +1 -1
  419. package/src/clis/barchart/quote.ts +1 -1
  420. package/src/clis/bilibili/download.ts +1 -1
  421. package/src/clis/bilibili/dynamic.ts +1 -1
  422. package/src/clis/bilibili/favorite.ts +1 -1
  423. package/src/clis/bilibili/feed.ts +1 -1
  424. package/src/clis/bilibili/following.ts +2 -2
  425. package/src/clis/bilibili/history.ts +1 -1
  426. package/src/clis/bilibili/me.ts +1 -1
  427. package/src/clis/bilibili/ranking.ts +1 -1
  428. package/src/clis/bilibili/search.ts +3 -3
  429. package/src/clis/bilibili/subtitle.ts +2 -2
  430. package/src/clis/bilibili/user-videos.ts +2 -2
  431. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  432. package/src/clis/bloomberg/businessweek.ts +18 -0
  433. package/src/clis/bloomberg/economics.ts +18 -0
  434. package/src/clis/bloomberg/feeds.ts +16 -0
  435. package/src/clis/bloomberg/industries.ts +18 -0
  436. package/src/clis/bloomberg/main.ts +18 -0
  437. package/src/clis/bloomberg/markets.ts +18 -0
  438. package/src/clis/bloomberg/news.ts +136 -0
  439. package/src/clis/bloomberg/opinions.ts +18 -0
  440. package/src/clis/bloomberg/politics.ts +18 -0
  441. package/src/clis/bloomberg/tech.ts +18 -0
  442. package/src/clis/bloomberg/utils.test.ts +135 -0
  443. package/src/clis/bloomberg/utils.ts +429 -0
  444. package/src/clis/boss/batchgreet.ts +16 -108
  445. package/src/clis/boss/chatlist.ts +13 -27
  446. package/src/clis/boss/chatmsg.ts +16 -40
  447. package/src/clis/boss/common.ts +287 -0
  448. package/src/clis/boss/detail.ts +9 -55
  449. package/src/clis/boss/exchange.ts +15 -89
  450. package/src/clis/boss/greet.ts +25 -162
  451. package/src/clis/boss/invite.ts +36 -133
  452. package/src/clis/boss/joblist.ts +7 -36
  453. package/src/clis/boss/mark.ts +13 -94
  454. package/src/clis/boss/recommend.ts +12 -57
  455. package/src/clis/boss/resume.ts +19 -124
  456. package/src/clis/boss/search.ts +14 -67
  457. package/src/clis/boss/send.ts +22 -162
  458. package/src/clis/boss/stats.ts +21 -76
  459. package/src/clis/chaoxing/assignments.ts +1 -1
  460. package/src/clis/chaoxing/exams.ts +1 -1
  461. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  462. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  463. package/src/clis/chatgpt/read.ts +1 -1
  464. package/src/clis/chatwise/export.ts +1 -1
  465. package/src/clis/chatwise/model.ts +2 -2
  466. package/src/clis/chatwise/screenshot.ts +1 -1
  467. package/src/clis/codex/export.ts +1 -1
  468. package/src/clis/codex/model.ts +2 -2
  469. package/src/clis/codex/screenshot.ts +1 -1
  470. package/src/clis/coupang/add-to-cart.ts +3 -4
  471. package/src/clis/coupang/search.ts +2 -4
  472. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  473. package/src/clis/ctrip/search.ts +1 -1
  474. package/src/clis/cursor/export.ts +1 -1
  475. package/src/clis/cursor/model.ts +2 -2
  476. package/src/clis/cursor/screenshot.ts +1 -1
  477. package/src/clis/devto/tag.yaml +34 -0
  478. package/src/clis/devto/top.yaml +29 -0
  479. package/src/clis/devto/user.yaml +33 -0
  480. package/src/clis/douban/book-hot.ts +15 -0
  481. package/src/clis/douban/marks.ts +135 -0
  482. package/src/clis/douban/movie-hot.ts +15 -0
  483. package/src/clis/douban/reviews.ts +127 -0
  484. package/src/clis/douban/search.ts +17 -0
  485. package/src/clis/douban/shared.ts +165 -0
  486. package/src/clis/douban/subject.yaml +76 -0
  487. package/src/clis/douban/top250.yaml +70 -0
  488. package/src/clis/douban/utils.ts +81 -0
  489. package/src/clis/facebook/add-friend.yaml +43 -0
  490. package/src/clis/facebook/events.yaml +44 -0
  491. package/src/clis/facebook/feed.yaml +63 -0
  492. package/src/clis/facebook/friends.yaml +42 -0
  493. package/src/clis/facebook/groups.yaml +50 -0
  494. package/src/clis/facebook/join-group.yaml +44 -0
  495. package/src/clis/facebook/memories.yaml +39 -0
  496. package/src/clis/facebook/notifications.yaml +40 -0
  497. package/src/clis/facebook/profile.yaml +37 -0
  498. package/src/clis/facebook/search.yaml +46 -0
  499. package/src/clis/google/news.ts +66 -0
  500. package/src/clis/google/search.ts +133 -0
  501. package/src/clis/google/suggest.ts +40 -0
  502. package/src/clis/google/trends.ts +44 -0
  503. package/src/clis/google/utils.test.ts +82 -0
  504. package/src/clis/google/utils.ts +24 -0
  505. package/src/clis/grok/ask.test.ts +53 -0
  506. package/src/clis/grok/ask.ts +300 -69
  507. package/src/clis/instagram/comment.yaml +52 -0
  508. package/src/clis/instagram/explore.yaml +43 -0
  509. package/src/clis/instagram/follow.yaml +41 -0
  510. package/src/clis/instagram/followers.yaml +51 -0
  511. package/src/clis/instagram/following.yaml +51 -0
  512. package/src/clis/instagram/like.yaml +46 -0
  513. package/src/clis/instagram/profile.yaml +42 -0
  514. package/src/clis/instagram/save.yaml +46 -0
  515. package/src/clis/instagram/saved.yaml +40 -0
  516. package/src/clis/instagram/search.yaml +43 -0
  517. package/src/clis/instagram/unfollow.yaml +38 -0
  518. package/src/clis/instagram/unlike.yaml +46 -0
  519. package/src/clis/instagram/unsave.yaml +46 -0
  520. package/src/clis/instagram/user.yaml +54 -0
  521. package/src/clis/jike/comment.ts +2 -3
  522. package/src/clis/jike/create.ts +1 -2
  523. package/src/clis/jike/feed.ts +0 -1
  524. package/src/clis/jike/like.ts +1 -2
  525. package/src/clis/jike/notifications.ts +0 -1
  526. package/src/clis/jike/post.yaml +1 -0
  527. package/src/clis/jike/repost.ts +2 -3
  528. package/src/clis/jike/search.ts +2 -3
  529. package/src/clis/jike/topic.yaml +1 -0
  530. package/src/clis/jike/user.yaml +1 -0
  531. package/src/clis/jimeng/generate.yaml +1 -0
  532. package/src/clis/jimeng/history.yaml +0 -1
  533. package/src/clis/linkedin/search.ts +7 -7
  534. package/src/clis/linux-do/category.yaml +2 -0
  535. package/src/clis/linux-do/search.yaml +4 -3
  536. package/src/clis/linux-do/topic.yaml +1 -0
  537. package/src/clis/lobsters/active.yaml +29 -0
  538. package/src/clis/lobsters/hot.yaml +29 -0
  539. package/src/clis/lobsters/newest.yaml +29 -0
  540. package/src/clis/lobsters/tag.yaml +34 -0
  541. package/src/clis/medium/feed.ts +16 -0
  542. package/src/clis/medium/search.ts +16 -0
  543. package/src/clis/medium/shared.ts +83 -0
  544. package/src/clis/medium/user.ts +16 -0
  545. package/src/clis/notion/export.ts +1 -1
  546. package/src/clis/reddit/comment.ts +3 -4
  547. package/src/clis/reddit/read.ts +4 -5
  548. package/src/clis/reddit/save.ts +2 -3
  549. package/src/clis/reddit/saved.ts +0 -1
  550. package/src/clis/reddit/search.yaml +1 -0
  551. package/src/clis/reddit/subreddit.yaml +1 -0
  552. package/src/clis/reddit/subscribe.ts +1 -2
  553. package/src/clis/reddit/upvote.ts +2 -3
  554. package/src/clis/reddit/upvoted.ts +0 -1
  555. package/src/clis/reddit/user-comments.yaml +1 -0
  556. package/src/clis/reddit/user-posts.yaml +1 -0
  557. package/src/clis/reddit/user.yaml +1 -0
  558. package/src/clis/reuters/search.ts +1 -1
  559. package/src/clis/sinablog/article.ts +15 -0
  560. package/src/clis/sinablog/hot.ts +15 -0
  561. package/src/clis/sinablog/search.ts +56 -0
  562. package/src/clis/sinablog/shared.ts +198 -0
  563. package/src/clis/sinablog/user.ts +16 -0
  564. package/src/clis/smzdm/search.ts +2 -3
  565. package/src/clis/stackoverflow/search.yaml +1 -0
  566. package/src/clis/steam/top-sellers.yaml +29 -0
  567. package/src/clis/substack/feed.ts +16 -0
  568. package/src/clis/substack/publication.ts +16 -0
  569. package/src/clis/substack/search.ts +91 -0
  570. package/src/clis/substack/shared.ts +132 -0
  571. package/src/clis/tiktok/comment.yaml +66 -0
  572. package/src/clis/tiktok/explore.yaml +39 -0
  573. package/src/clis/tiktok/follow.yaml +39 -0
  574. package/src/clis/tiktok/following.yaml +46 -0
  575. package/src/clis/tiktok/friends.yaml +47 -0
  576. package/src/clis/tiktok/like.yaml +38 -0
  577. package/src/clis/tiktok/live.yaml +51 -0
  578. package/src/clis/tiktok/notifications.yaml +52 -0
  579. package/src/clis/tiktok/profile.yaml +45 -0
  580. package/src/clis/tiktok/save.yaml +34 -0
  581. package/src/clis/tiktok/search.yaml +46 -0
  582. package/src/clis/tiktok/unfollow.yaml +44 -0
  583. package/src/clis/tiktok/unlike.yaml +38 -0
  584. package/src/clis/tiktok/unsave.yaml +36 -0
  585. package/src/clis/tiktok/user.yaml +44 -0
  586. package/src/clis/twitter/accept.ts +2 -2
  587. package/src/clis/twitter/article.ts +2 -2
  588. package/src/clis/twitter/block.ts +92 -0
  589. package/src/clis/twitter/delete.ts +1 -1
  590. package/src/clis/twitter/download.ts +3 -3
  591. package/src/clis/twitter/followers.ts +1 -1
  592. package/src/clis/twitter/following.ts +1 -1
  593. package/src/clis/twitter/hide-reply.ts +70 -0
  594. package/src/clis/twitter/like.ts +1 -1
  595. package/src/clis/twitter/post.ts +1 -1
  596. package/src/clis/twitter/reply-dm.ts +1 -1
  597. package/src/clis/twitter/reply.ts +2 -2
  598. package/src/clis/twitter/search.ts +1 -1
  599. package/src/clis/twitter/thread.ts +2 -2
  600. package/src/clis/twitter/timeline.test.ts +109 -0
  601. package/src/clis/twitter/timeline.ts +59 -19
  602. package/src/clis/twitter/trending.ts +113 -0
  603. package/src/clis/twitter/unblock.ts +75 -0
  604. package/src/clis/v2ex/topic.yaml +1 -0
  605. package/src/clis/weibo/hot.ts +0 -1
  606. package/src/clis/weread/book.ts +1 -1
  607. package/src/clis/weread/highlights.ts +1 -1
  608. package/src/clis/weread/notes.ts +1 -1
  609. package/src/clis/weread/search.ts +1 -1
  610. package/src/clis/wikipedia/random.ts +19 -0
  611. package/src/clis/wikipedia/search.ts +11 -5
  612. package/src/clis/wikipedia/summary.ts +4 -9
  613. package/src/clis/wikipedia/trending.ts +41 -0
  614. package/src/clis/wikipedia/utils.ts +31 -0
  615. package/src/clis/xiaohongshu/creator-note-detail.test.ts +84 -33
  616. package/src/clis/xiaohongshu/creator-note-detail.ts +89 -5
  617. package/src/clis/xiaohongshu/creator-notes.test.ts +41 -6
  618. package/src/clis/xiaohongshu/creator-notes.ts +44 -5
  619. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  620. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  621. package/src/clis/xiaohongshu/download.ts +2 -3
  622. package/src/clis/xiaohongshu/feed.yaml +0 -1
  623. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  624. package/src/clis/xiaohongshu/search.ts +2 -2
  625. package/src/clis/xiaohongshu/user.ts +1 -2
  626. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  627. package/src/clis/xueqiu/search.yaml +2 -1
  628. package/src/clis/xueqiu/stock.yaml +2 -0
  629. package/src/clis/yahoo-finance/quote.ts +1 -2
  630. package/src/clis/youtube/search.ts +1 -1
  631. package/src/clis/youtube/transcript.ts +1 -1
  632. package/src/clis/youtube/video.ts +1 -1
  633. package/src/clis/zhihu/download.ts +1 -2
  634. package/src/clis/zhihu/question.ts +1 -1
  635. package/src/clis/zhihu/search.yaml +4 -3
  636. package/src/commanderAdapter.ts +120 -0
  637. package/src/discovery.ts +277 -0
  638. package/src/doctor.test.ts +59 -2
  639. package/src/doctor.ts +4 -2
  640. package/src/download/index.ts +21 -54
  641. package/src/engine.test.ts +85 -11
  642. package/src/execution.ts +164 -0
  643. package/src/explore.ts +211 -117
  644. package/src/external-clis.yaml +9 -0
  645. package/src/external.ts +15 -12
  646. package/src/generate.ts +58 -9
  647. package/src/main.ts +2 -1
  648. package/src/pipeline/executor.test.ts +35 -6
  649. package/src/pipeline/executor.ts +11 -7
  650. package/src/pipeline/registry.ts +3 -3
  651. package/src/pipeline/steps/browser.ts +29 -15
  652. package/src/pipeline/steps/fetch.ts +18 -13
  653. package/src/pipeline/steps/transform.ts +40 -15
  654. package/src/pipeline/template.test.ts +18 -0
  655. package/src/pipeline/template.ts +86 -13
  656. package/src/pipeline/transform.test.ts +15 -2
  657. package/src/plugin.test.ts +86 -0
  658. package/src/plugin.ts +254 -0
  659. package/src/registry-api.ts +12 -0
  660. package/src/registry.ts +24 -1
  661. package/src/runtime.ts +9 -0
  662. package/src/serialization.ts +79 -0
  663. package/src/synthesize.ts +102 -21
  664. package/src/types.ts +45 -13
  665. package/src/validate.ts +19 -4
  666. package/tests/e2e/browser-public.test.ts +36 -0
  667. package/tests/e2e/public-commands.test.ts +119 -1
  668. package/dist/clis/feishu/new.d.ts +0 -1
  669. package/dist/clis/feishu/new.js +0 -27
  670. package/dist/clis/feishu/read.d.ts +0 -1
  671. package/dist/clis/feishu/read.js +0 -40
  672. package/dist/clis/feishu/search.d.ts +0 -1
  673. package/dist/clis/feishu/search.js +0 -30
  674. package/dist/clis/feishu/send.d.ts +0 -1
  675. package/dist/clis/feishu/send.js +0 -39
  676. package/dist/clis/feishu/status.d.ts +0 -1
  677. package/dist/clis/feishu/status.js +0 -28
  678. package/dist/clis/neteasemusic/like.d.ts +0 -1
  679. package/dist/clis/neteasemusic/like.js +0 -25
  680. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  681. package/dist/clis/neteasemusic/lyrics.js +0 -47
  682. package/dist/clis/neteasemusic/next.d.ts +0 -1
  683. package/dist/clis/neteasemusic/next.js +0 -26
  684. package/dist/clis/neteasemusic/play.d.ts +0 -1
  685. package/dist/clis/neteasemusic/play.js +0 -26
  686. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  687. package/dist/clis/neteasemusic/playing.js +0 -59
  688. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  689. package/dist/clis/neteasemusic/playlist.js +0 -46
  690. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  691. package/dist/clis/neteasemusic/prev.js +0 -25
  692. package/dist/clis/neteasemusic/search.d.ts +0 -1
  693. package/dist/clis/neteasemusic/search.js +0 -52
  694. package/dist/clis/neteasemusic/status.d.ts +0 -1
  695. package/dist/clis/neteasemusic/status.js +0 -16
  696. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  697. package/dist/clis/neteasemusic/volume.js +0 -54
  698. package/dist/clis/twitter/trending.yaml +0 -46
  699. package/dist/clis/wechat/chats.d.ts +0 -1
  700. package/dist/clis/wechat/chats.js +0 -28
  701. package/dist/clis/wechat/contacts.d.ts +0 -1
  702. package/dist/clis/wechat/contacts.js +0 -28
  703. package/dist/clis/wechat/read.d.ts +0 -1
  704. package/dist/clis/wechat/read.js +0 -58
  705. package/dist/clis/wechat/search.d.ts +0 -1
  706. package/dist/clis/wechat/search.js +0 -31
  707. package/dist/clis/wechat/send.d.ts +0 -1
  708. package/dist/clis/wechat/send.js +0 -42
  709. package/dist/clis/wechat/status.d.ts +0 -1
  710. package/dist/clis/wechat/status.js +0 -29
  711. package/dist/pipeline.d.ts +0 -7
  712. package/dist/pipeline.js +0 -7
  713. package/docs/adapters/browser/github.md +0 -26
  714. package/docs/adapters/desktop/feishu.md +0 -20
  715. package/docs/adapters/desktop/neteasemusic.md +0 -31
  716. package/docs/adapters/desktop/wechat.md +0 -28
  717. package/docs/public/CNAME +0 -1
  718. package/src/clis/antigravity/README.md +0 -5
  719. package/src/clis/antigravity/README.zh-CN.md +0 -51
  720. package/src/clis/chaoxing/README.md +0 -14
  721. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  722. package/src/clis/chatgpt/README.md +0 -5
  723. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  724. package/src/clis/chatwise/README.md +0 -5
  725. package/src/clis/chatwise/README.zh-CN.md +0 -38
  726. package/src/clis/codex/README.md +0 -5
  727. package/src/clis/codex/README.zh-CN.md +0 -33
  728. package/src/clis/cursor/README.md +0 -5
  729. package/src/clis/cursor/README.zh-CN.md +0 -33
  730. package/src/clis/discord-app/README.md +0 -5
  731. package/src/clis/discord-app/README.zh-CN.md +0 -28
  732. package/src/clis/feishu/README.md +0 -5
  733. package/src/clis/feishu/README.zh-CN.md +0 -20
  734. package/src/clis/feishu/new.ts +0 -32
  735. package/src/clis/feishu/read.ts +0 -48
  736. package/src/clis/feishu/search.ts +0 -35
  737. package/src/clis/feishu/send.ts +0 -46
  738. package/src/clis/feishu/status.ts +0 -34
  739. package/src/clis/neteasemusic/README.md +0 -5
  740. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  741. package/src/clis/neteasemusic/like.ts +0 -28
  742. package/src/clis/neteasemusic/lyrics.ts +0 -53
  743. package/src/clis/neteasemusic/next.ts +0 -30
  744. package/src/clis/neteasemusic/play.ts +0 -30
  745. package/src/clis/neteasemusic/playing.ts +0 -62
  746. package/src/clis/neteasemusic/playlist.ts +0 -51
  747. package/src/clis/neteasemusic/prev.ts +0 -29
  748. package/src/clis/neteasemusic/search.ts +0 -58
  749. package/src/clis/neteasemusic/status.ts +0 -18
  750. package/src/clis/neteasemusic/volume.ts +0 -61
  751. package/src/clis/notion/README.md +0 -5
  752. package/src/clis/notion/README.zh-CN.md +0 -29
  753. package/src/clis/twitter/trending.yaml +0 -46
  754. package/src/clis/wechat/README.md +0 -5
  755. package/src/clis/wechat/README.zh-CN.md +0 -28
  756. package/src/clis/wechat/chats.ts +0 -33
  757. package/src/clis/wechat/contacts.ts +0 -33
  758. package/src/clis/wechat/read.ts +0 -72
  759. package/src/clis/wechat/search.ts +0 -36
  760. package/src/clis/wechat/send.ts +0 -49
  761. package/src/clis/wechat/status.ts +0 -35
  762. package/src/engine.ts +0 -274
  763. package/src/pipeline.ts +0 -8
  764. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  765. /package/dist/{chaoxing.test.d.ts → clis/bloomberg/businessweek.d.ts} +0 -0
  766. /package/dist/{coupang.test.d.ts → clis/bloomberg/economics.d.ts} +0 -0
  767. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  768. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  769. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
@@ -8,14 +8,27 @@
8
8
  * 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
9
9
  */
10
10
  import * as fs from 'node:fs';
11
+ import * as os from 'node:os';
11
12
  import * as path from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
12
14
  import yaml from 'js-yaml';
13
15
  import { Strategy, registerCommand } from './registry.js';
14
- import { executePipeline } from './pipeline.js';
15
16
  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();
17
+ /** Plugins directory: ~/.opencli/plugins/ */
18
+ export const PLUGINS_DIR = path.join(os.homedir(), '.opencli', 'plugins');
19
+ const CLI_MODULE_PATTERN = /\bcli\s*\(/;
20
+ function getErrorMessage(error) {
21
+ return error instanceof Error ? error.message : String(error);
22
+ }
23
+ function parseStrategy(rawStrategy, fallback = Strategy.COOKIE) {
24
+ if (!rawStrategy)
25
+ return fallback;
26
+ const key = rawStrategy.toUpperCase();
27
+ return Strategy[key] ?? fallback;
28
+ }
29
+ function isRecord(value) {
30
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
31
+ }
19
32
  /**
20
33
  * Discover and register CLI commands.
21
34
  * Uses pre-compiled manifest when available for instant startup.
@@ -47,7 +60,7 @@ async function loadFromManifest(manifestPath, clisDir) {
47
60
  for (const entry of manifest) {
48
61
  if (entry.type === 'yaml') {
49
62
  // YAML pipelines fully inlined in manifest — register directly
50
- const strategy = Strategy[entry.strategy.toUpperCase()] ?? Strategy.COOKIE;
63
+ const strategy = parseStrategy(entry.strategy);
51
64
  const cmd = {
52
65
  site: entry.site,
53
66
  name: entry.name,
@@ -60,13 +73,14 @@ async function loadFromManifest(manifestPath, clisDir) {
60
73
  pipeline: entry.pipeline,
61
74
  timeoutSeconds: entry.timeout,
62
75
  source: `manifest:${entry.site}/${entry.name}`,
76
+ navigateBefore: entry.navigateBefore,
63
77
  };
64
78
  registerCommand(cmd);
65
79
  }
66
80
  else if (entry.type === 'ts' && entry.modulePath) {
67
81
  // TS adapters: register a lightweight stub.
68
82
  // The actual module is loaded lazily on first executeCommand().
69
- const strategy = Strategy[(entry.strategy ?? 'cookie').toUpperCase()] ?? Strategy.COOKIE;
83
+ const strategy = parseStrategy(entry.strategy ?? 'cookie');
70
84
  const modulePath = path.resolve(clisDir, entry.modulePath);
71
85
  const cmd = {
72
86
  site: entry.site,
@@ -79,6 +93,7 @@ async function loadFromManifest(manifestPath, clisDir) {
79
93
  columns: entry.columns,
80
94
  timeoutSeconds: entry.timeout,
81
95
  source: modulePath,
96
+ navigateBefore: entry.navigateBefore,
82
97
  _lazy: true,
83
98
  _modulePath: modulePath,
84
99
  };
@@ -87,7 +102,7 @@ async function loadFromManifest(manifestPath, clisDir) {
87
102
  }
88
103
  }
89
104
  catch (err) {
90
- log.warn(`Failed to load manifest ${manifestPath}: ${err.message}`);
105
+ log.warn(`Failed to load manifest ${manifestPath}: ${getErrorMessage(err)}`);
91
106
  }
92
107
  }
93
108
  /**
@@ -115,8 +130,10 @@ async function discoverClisFromFs(dir) {
115
130
  }
116
131
  else if ((file.endsWith('.js') && !file.endsWith('.d.js')) ||
117
132
  (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts'))) {
118
- promises.push(import(`file://${filePath}`).catch((err) => {
119
- log.warn(`Failed to load module ${filePath}: ${err.message}`);
133
+ if (!(await isCliModule(filePath)))
134
+ continue;
135
+ promises.push(import(pathToFileURL(filePath).href).catch((err) => {
136
+ log.warn(`Failed to load module ${filePath}: ${getErrorMessage(err)}`);
120
137
  }));
121
138
  }
122
139
  }
@@ -127,21 +144,23 @@ async function registerYamlCli(filePath, defaultSite) {
127
144
  try {
128
145
  const raw = await fs.promises.readFile(filePath, 'utf-8');
129
146
  const def = yaml.load(raw);
130
- if (!def || typeof def !== 'object')
147
+ if (!isRecord(def))
131
148
  return;
132
- const site = def.site ?? defaultSite;
133
- const name = def.name ?? path.basename(filePath, path.extname(filePath));
134
- const strategyStr = def.strategy ?? (def.browser === false ? 'public' : 'cookie');
135
- const strategy = Strategy[strategyStr.toUpperCase()] ?? Strategy.COOKIE;
136
- const browser = def.browser ?? (strategy !== Strategy.PUBLIC);
149
+ const cliDef = def;
150
+ const site = cliDef.site ?? defaultSite;
151
+ const name = cliDef.name ?? path.basename(filePath, path.extname(filePath));
152
+ const strategyStr = cliDef.strategy ?? (cliDef.browser === false ? 'public' : 'cookie');
153
+ const strategy = parseStrategy(strategyStr);
154
+ const browser = cliDef.browser ?? (strategy !== Strategy.PUBLIC);
137
155
  const args = [];
138
- if (def.args && typeof def.args === 'object') {
139
- for (const [argName, argDef] of Object.entries(def.args)) {
156
+ if (cliDef.args && typeof cliDef.args === 'object') {
157
+ for (const [argName, argDef] of Object.entries(cliDef.args)) {
140
158
  args.push({
141
159
  name: argName,
142
160
  type: argDef?.type ?? 'str',
143
161
  default: argDef?.default,
144
162
  required: argDef?.required ?? false,
163
+ positional: argDef?.positional ?? false,
145
164
  help: argDef?.description ?? argDef?.help ?? '',
146
165
  choices: argDef?.choices,
147
166
  });
@@ -150,113 +169,83 @@ async function registerYamlCli(filePath, defaultSite) {
150
169
  const cmd = {
151
170
  site,
152
171
  name,
153
- description: def.description ?? '',
154
- domain: def.domain,
172
+ description: cliDef.description ?? '',
173
+ domain: cliDef.domain,
155
174
  strategy,
156
175
  browser,
157
176
  args,
158
- columns: def.columns,
159
- pipeline: def.pipeline,
160
- timeoutSeconds: def.timeout,
177
+ columns: cliDef.columns,
178
+ pipeline: cliDef.pipeline,
179
+ timeoutSeconds: cliDef.timeout,
161
180
  source: filePath,
181
+ navigateBefore: cliDef.navigateBefore,
162
182
  };
163
183
  registerCommand(cmd);
164
184
  }
165
185
  catch (err) {
166
- log.warn(`Failed to load ${filePath}: ${err.message}`);
186
+ log.warn(`Failed to load ${filePath}: ${getErrorMessage(err)}`);
167
187
  }
168
188
  }
169
189
  /**
170
- * Validates and coerces arguments based on the command's Arg definitions.
190
+ * Discover and register plugins from ~/.opencli/plugins/.
191
+ * Each subdirectory is treated as a plugin (site = directory name).
192
+ * Files inside are scanned flat (no nested site subdirs).
171
193
  */
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
- }
194
+ export async function discoverPlugins() {
195
+ try {
196
+ await fs.promises.access(PLUGINS_DIR);
197
+ }
198
+ catch {
199
+ return;
200
+ }
201
+ const entries = await fs.promises.readdir(PLUGINS_DIR, { withFileTypes: true });
202
+ for (const entry of entries) {
203
+ if (!entry.isDirectory())
204
+ continue;
205
+ await discoverPluginDir(path.join(PLUGINS_DIR, entry.name), entry.name);
216
206
  }
217
- return result;
218
207
  }
219
208
  /**
220
- * Execute a CLI command. Handles lazy-loading of TS modules.
209
+ * Flat scan: read yaml/ts files directly in a plugin directory.
210
+ * Unlike discoverClisFromFs, this does NOT expect nested site subdirectories.
221
211
  */
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
- }
212
+ async function discoverPluginDir(dir, site) {
213
+ const files = await fs.promises.readdir(dir);
214
+ const fileSet = new Set(files);
215
+ const promises = [];
216
+ for (const file of files) {
217
+ const filePath = path.join(dir, file);
218
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
219
+ promises.push(registerYamlCli(filePath, site));
243
220
  }
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);
221
+ else if (file.endsWith('.js') && !file.endsWith('.d.js')) {
222
+ if (!(await isCliModule(filePath)))
223
+ continue;
224
+ promises.push(import(pathToFileURL(filePath).href).catch((err) => {
225
+ log.warn(`Plugin ${site}/${file}: ${getErrorMessage(err)}`);
226
+ }));
250
227
  }
251
- if (updated && updated.pipeline) {
252
- return executePipeline(page, updated.pipeline, { args: kwargs, debug });
228
+ else if (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts')) {
229
+ // Skip .ts if a compiled .js sibling exists (production mode can't load .ts)
230
+ const jsFile = file.replace(/\.ts$/, '.js');
231
+ if (fileSet.has(jsFile))
232
+ continue;
233
+ if (!(await isCliModule(filePath)))
234
+ continue;
235
+ promises.push(import(pathToFileURL(filePath).href).catch((err) => {
236
+ log.warn(`Plugin ${site}/${file}: ${getErrorMessage(err)}`);
237
+ }));
253
238
  }
254
239
  }
255
- if (cmd.func) {
256
- return cmd.func(page, kwargs, debug);
240
+ await Promise.all(promises);
241
+ }
242
+ async function isCliModule(filePath) {
243
+ try {
244
+ const source = await fs.promises.readFile(filePath, 'utf-8');
245
+ return CLI_MODULE_PATTERN.test(source);
257
246
  }
258
- if (cmd.pipeline) {
259
- return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
247
+ catch (err) {
248
+ log.warn(`Failed to inspect module ${filePath}: ${getErrorMessage(err)}`);
249
+ return false;
260
250
  }
261
- throw new Error(`Command ${cmd.site}/${cmd.name} has no func or pipeline`);
262
251
  }
package/dist/doctor.js CHANGED
@@ -26,11 +26,13 @@ export async function checkConnectivity(opts) {
26
26
  }
27
27
  }
28
28
  export async function runBrowserDoctor(opts = {}) {
29
- const status = await checkDaemonStatus();
29
+ // Run the live connectivity check first — it may auto-start the daemon as a
30
+ // side-effect, so we read daemon status only *after* all side-effects settle.
30
31
  let connectivity;
31
32
  if (opts.live) {
32
33
  connectivity = await checkConnectivity();
33
34
  }
35
+ const status = await checkDaemonStatus();
34
36
  const sessions = opts.sessions && status.running && status.extensionConnected
35
37
  ? await listSessions()
36
38
  : undefined;
@@ -1,7 +1,28 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { renderBrowserDoctorReport } from './doctor.js';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const { mockCheckDaemonStatus, mockListSessions, mockConnect, mockClose } = vi.hoisted(() => ({
3
+ mockCheckDaemonStatus: vi.fn(),
4
+ mockListSessions: vi.fn(),
5
+ mockConnect: vi.fn(),
6
+ mockClose: vi.fn(),
7
+ }));
8
+ vi.mock('./browser/discover.js', () => ({
9
+ checkDaemonStatus: mockCheckDaemonStatus,
10
+ }));
11
+ vi.mock('./browser/daemon-client.js', () => ({
12
+ listSessions: mockListSessions,
13
+ }));
14
+ vi.mock('./browser/index.js', () => ({
15
+ BrowserBridge: class {
16
+ connect = mockConnect;
17
+ close = mockClose;
18
+ },
19
+ }));
20
+ import { renderBrowserDoctorReport, runBrowserDoctor } from './doctor.js';
3
21
  describe('doctor report rendering', () => {
4
22
  const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ });
5
26
  it('renders OK-style report when daemon and extension connected', () => {
6
27
  const text = strip(renderBrowserDoctorReport({
7
28
  daemonRunning: true,
@@ -48,4 +69,27 @@ describe('doctor report rendering', () => {
48
69
  }));
49
70
  expect(text).toContain('[SKIP] Connectivity: not tested (use --live)');
50
71
  });
72
+ it('reports consistent status when live check auto-starts the daemon', async () => {
73
+ // With the reordered flow, checkDaemonStatus is called only ONCE — after
74
+ // the connectivity check that may auto-start the daemon.
75
+ mockCheckDaemonStatus.mockResolvedValueOnce({ running: true, extensionConnected: false });
76
+ mockConnect.mockRejectedValueOnce(new Error('Daemon is running but the Browser Extension is not connected.\n' +
77
+ 'Please install and enable the opencli Browser Bridge extension in Chrome.'));
78
+ const report = await runBrowserDoctor({ live: true });
79
+ // Status reflects the post-connectivity state (daemon running)
80
+ expect(report.daemonRunning).toBe(true);
81
+ expect(report.extensionConnected).toBe(false);
82
+ // checkDaemonStatus should only be called once
83
+ expect(mockCheckDaemonStatus).toHaveBeenCalledTimes(1);
84
+ // Should NOT report "daemon not running" since it IS running after live check
85
+ expect(report.issues).not.toContain(expect.stringContaining('Daemon is not running'));
86
+ // Should report extension not connected
87
+ expect(report.issues).toEqual(expect.arrayContaining([
88
+ expect.stringContaining('Chrome extension is not connected'),
89
+ ]));
90
+ // Should report connectivity failure
91
+ expect(report.issues).toEqual(expect.arrayContaining([
92
+ expect.stringContaining('Browser connectivity test failed'),
93
+ ]));
94
+ });
51
95
  });
@@ -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 @@
1
- /**
2
- * Tests for engine.ts: CLI discovery and command execution.
3
- */
4
1
  export {};
@@ -1,14 +1,83 @@
1
- /**
2
- * Tests for engine.ts: CLI discovery and command execution.
3
- */
4
- import { describe, it, expect } from 'vitest';
5
- import { discoverClis, executeCommand } from './engine.js';
6
- import { cli, Strategy } from './registry.js';
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
3
+ import { executeCommand } from './execution.js';
4
+ import { getRegistry, cli, Strategy } from './registry.js';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
7
  describe('discoverClis', () => {
8
8
  it('handles non-existent directories gracefully', async () => {
9
9
  // Should not throw for missing directories
10
10
  await expect(discoverClis('/tmp/nonexistent-opencli-test-dir')).resolves.not.toThrow();
11
11
  });
12
+ it('imports only CLI command modules during filesystem discovery', async () => {
13
+ const tempRoot = await fs.promises.mkdtemp(path.join('/tmp', 'opencli-discovery-'));
14
+ const siteDir = path.join(tempRoot, 'temp-site');
15
+ const helperPath = path.join(siteDir, 'helper.ts');
16
+ const commandPath = path.join(siteDir, 'hello.ts');
17
+ try {
18
+ await fs.promises.mkdir(siteDir, { recursive: true });
19
+ await fs.promises.writeFile(helperPath, `
20
+ globalThis.__opencli_helper_loaded__ = true;
21
+ export const helper = true;
22
+ `);
23
+ await fs.promises.writeFile(commandPath, `
24
+ import { cli, Strategy } from '${path.join(process.cwd(), 'src', 'registry.ts')}';
25
+ cli({
26
+ site: 'temp-site',
27
+ name: 'hello',
28
+ description: 'hello command',
29
+ strategy: Strategy.PUBLIC,
30
+ browser: false,
31
+ func: async () => [{ ok: true }],
32
+ });
33
+ `);
34
+ delete globalThis.__opencli_helper_loaded__;
35
+ await discoverClis(tempRoot);
36
+ expect(globalThis.__opencli_helper_loaded__).toBeUndefined();
37
+ expect(getRegistry().get('temp-site/hello')).toBeDefined();
38
+ }
39
+ finally {
40
+ delete globalThis.__opencli_helper_loaded__;
41
+ await fs.promises.rm(tempRoot, { recursive: true, force: true });
42
+ }
43
+ });
44
+ });
45
+ describe('discoverPlugins', () => {
46
+ const testPluginDir = path.join(PLUGINS_DIR, '__test-plugin__');
47
+ const yamlPath = path.join(testPluginDir, 'greeting.yaml');
48
+ afterEach(async () => {
49
+ try {
50
+ await fs.promises.rm(testPluginDir, { recursive: true });
51
+ }
52
+ catch { }
53
+ });
54
+ it('discovers YAML plugins from ~/.opencli/plugins/', async () => {
55
+ // Create a simple YAML adapter in the plugins directory
56
+ await fs.promises.mkdir(testPluginDir, { recursive: true });
57
+ await fs.promises.writeFile(yamlPath, `
58
+ site: __test-plugin__
59
+ name: greeting
60
+ description: Test plugin greeting
61
+ strategy: public
62
+ browser: false
63
+
64
+ pipeline:
65
+ - evaluate: "() => [{ message: 'hello from plugin' }]"
66
+
67
+ columns: [message]
68
+ `);
69
+ await discoverPlugins();
70
+ const registry = getRegistry();
71
+ const cmd = registry.get('__test-plugin__/greeting');
72
+ expect(cmd).toBeDefined();
73
+ expect(cmd.site).toBe('__test-plugin__');
74
+ expect(cmd.name).toBe('greeting');
75
+ expect(cmd.description).toBe('Test plugin greeting');
76
+ });
77
+ it('handles non-existent plugins directory gracefully', async () => {
78
+ // discoverPlugins should not throw if ~/.opencli/plugins/ does not exist
79
+ await expect(discoverPlugins()).resolves.not.toThrow();
80
+ });
12
81
  });
13
82
  describe('executeCommand', () => {
14
83
  it('accepts kebab-case option names after Commander camelCases them', async () => {
@@ -23,7 +92,7 @@ describe('executeCommand', () => {
23
92
  ],
24
93
  func: async (_page, kwargs) => [{ noteId: kwargs['note-id'] }],
25
94
  });
26
- const result = await executeCommand(cmd, null, { 'note-id': 'abc123' });
95
+ const result = await executeCommand(cmd, { 'note-id': 'abc123' });
27
96
  expect(result).toEqual([{ noteId: 'abc123' }]);
28
97
  });
29
98
  it('executes a command with func', async () => {
@@ -37,7 +106,7 @@ describe('executeCommand', () => {
37
106
  return [{ title: kwargs.query ?? 'default' }];
38
107
  },
39
108
  });
40
- const result = await executeCommand(cmd, null, { query: 'hello' });
109
+ const result = await executeCommand(cmd, { query: 'hello' });
41
110
  expect(result).toEqual([{ title: 'hello' }]);
42
111
  });
43
112
  it('executes a command with pipeline', async () => {
@@ -53,7 +122,7 @@ describe('executeCommand', () => {
53
122
  ],
54
123
  });
55
124
  // Pipeline commands require page for evaluate step, so we'll test the error path
56
- await expect(executeCommand(cmd, null, {})).rejects.toThrow();
125
+ await expect(executeCommand(cmd, {})).rejects.toThrow();
57
126
  });
58
127
  it('throws for command with no func or pipeline', async () => {
59
128
  const cmd = cli({
@@ -62,7 +131,7 @@ describe('executeCommand', () => {
62
131
  description: 'empty command',
63
132
  browser: false,
64
133
  });
65
- await expect(executeCommand(cmd, null, {})).rejects.toThrow('has no func or pipeline');
134
+ await expect(executeCommand(cmd, {})).rejects.toThrow('has no func or pipeline');
66
135
  });
67
136
  it('passes debug flag to func', async () => {
68
137
  let receivedDebug = false;
@@ -76,7 +145,7 @@ describe('executeCommand', () => {
76
145
  return [];
77
146
  },
78
147
  });
79
- await executeCommand(cmd, null, {}, true);
148
+ await executeCommand(cmd, {}, true);
80
149
  expect(receivedDebug).toBe(true);
81
150
  });
82
151
  });
@@ -0,0 +1,24 @@
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
+ type CommandArgs = Record<string, unknown>;
13
+ /**
14
+ * Validates and coerces arguments based on the command's Arg definitions.
15
+ */
16
+ export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs;
17
+ /**
18
+ * Execute a CLI command. Automatically manages browser sessions when needed.
19
+ *
20
+ * This is the unified entry point — callers don't need to care about
21
+ * whether the command requires a browser or not.
22
+ */
23
+ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs, debug?: boolean): Promise<unknown>;
24
+ export {};