@jackwener/opencli 1.1.1 → 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 (509) hide show
  1. package/CONTRIBUTING.md +39 -1
  2. package/README.md +9 -10
  3. package/README.zh-CN.md +39 -17
  4. package/SKILL.md +10 -5
  5. package/dist/browser/cdp.d.ts +4 -4
  6. package/dist/browser/cdp.js +39 -16
  7. package/dist/browser/daemon-client.d.ts +2 -1
  8. package/dist/browser/dom-helpers.js +38 -7
  9. package/dist/browser/dom-snapshot.d.ts +86 -0
  10. package/dist/browser/dom-snapshot.js +729 -0
  11. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  12. package/dist/browser/dom-snapshot.test.js +212 -0
  13. package/dist/browser/index.d.ts +2 -0
  14. package/dist/browser/index.js +1 -0
  15. package/dist/browser/page.d.ts +14 -24
  16. package/dist/browser/page.js +37 -4
  17. package/dist/build-manifest.d.ts +11 -4
  18. package/dist/build-manifest.js +59 -21
  19. package/dist/build-manifest.test.js +58 -2
  20. package/dist/cli-manifest.json +3856 -1509
  21. package/dist/cli.js +66 -0
  22. package/dist/clis/barchart/greeks.js +1 -1
  23. package/dist/clis/barchart/options.js +1 -1
  24. package/dist/clis/barchart/quote.js +1 -1
  25. package/dist/clis/bilibili/download.js +1 -1
  26. package/dist/clis/bilibili/following.js +1 -1
  27. package/dist/clis/bilibili/subtitle.js +1 -1
  28. package/dist/clis/bilibili/user-videos.js +1 -1
  29. package/dist/clis/boss/batchgreet.js +10 -97
  30. package/dist/clis/boss/chatlist.js +8 -25
  31. package/dist/clis/boss/chatmsg.js +11 -42
  32. package/dist/clis/boss/common.d.ts +92 -0
  33. package/dist/clis/boss/common.js +223 -0
  34. package/dist/clis/boss/detail.js +7 -49
  35. package/dist/clis/boss/exchange.js +13 -79
  36. package/dist/clis/boss/greet.js +18 -145
  37. package/dist/clis/boss/invite.js +26 -121
  38. package/dist/clis/boss/joblist.js +6 -31
  39. package/dist/clis/boss/mark.js +12 -85
  40. package/dist/clis/boss/recommend.js +10 -49
  41. package/dist/clis/boss/resume.js +18 -118
  42. package/dist/clis/boss/search.js +12 -60
  43. package/dist/clis/boss/send.js +17 -151
  44. package/dist/clis/boss/stats.js +18 -69
  45. package/dist/clis/coupang/add-to-cart.js +1 -1
  46. package/dist/clis/devto/tag.yaml +34 -0
  47. package/dist/clis/devto/top.yaml +29 -0
  48. package/dist/clis/devto/user.yaml +33 -0
  49. package/dist/clis/douban/book-hot.d.ts +1 -0
  50. package/dist/clis/douban/book-hot.js +14 -0
  51. package/dist/clis/douban/marks.d.ts +1 -0
  52. package/dist/clis/douban/marks.js +115 -0
  53. package/dist/clis/douban/movie-hot.d.ts +1 -0
  54. package/dist/clis/douban/movie-hot.js +14 -0
  55. package/dist/clis/douban/reviews.d.ts +1 -0
  56. package/dist/clis/douban/reviews.js +106 -0
  57. package/dist/clis/douban/search.d.ts +1 -0
  58. package/dist/clis/douban/search.js +16 -0
  59. package/dist/clis/douban/shared.d.ts +4 -0
  60. package/dist/clis/douban/shared.js +155 -0
  61. package/dist/clis/douban/subject.yaml +76 -0
  62. package/dist/clis/douban/top250.yaml +70 -0
  63. package/dist/clis/douban/utils.d.ts +35 -0
  64. package/dist/clis/douban/utils.js +48 -0
  65. package/dist/clis/facebook/add-friend.yaml +43 -0
  66. package/dist/clis/facebook/events.yaml +44 -0
  67. package/dist/clis/facebook/feed.yaml +63 -0
  68. package/dist/clis/facebook/friends.yaml +42 -0
  69. package/dist/clis/facebook/groups.yaml +50 -0
  70. package/dist/clis/facebook/join-group.yaml +44 -0
  71. package/dist/clis/facebook/memories.yaml +39 -0
  72. package/dist/clis/facebook/notifications.yaml +40 -0
  73. package/dist/clis/facebook/profile.yaml +37 -0
  74. package/dist/clis/facebook/search.yaml +46 -0
  75. package/dist/clis/google/news.d.ts +5 -0
  76. package/dist/clis/google/news.js +58 -0
  77. package/dist/clis/google/search.d.ts +10 -0
  78. package/dist/clis/google/search.js +127 -0
  79. package/dist/clis/google/suggest.d.ts +5 -0
  80. package/dist/clis/google/suggest.js +34 -0
  81. package/dist/clis/google/trends.d.ts +5 -0
  82. package/dist/clis/google/trends.js +38 -0
  83. package/dist/clis/google/utils.d.ts +9 -0
  84. package/dist/clis/google/utils.js +23 -0
  85. package/dist/clis/google/utils.test.d.ts +1 -0
  86. package/dist/clis/google/utils.test.js +75 -0
  87. package/dist/clis/grok/ask.d.ts +14 -0
  88. package/dist/clis/grok/ask.js +257 -65
  89. package/dist/clis/grok/ask.test.d.ts +1 -0
  90. package/dist/clis/grok/ask.test.js +36 -0
  91. package/dist/clis/instagram/comment.yaml +52 -0
  92. package/dist/clis/instagram/explore.yaml +43 -0
  93. package/dist/clis/instagram/follow.yaml +41 -0
  94. package/dist/clis/instagram/followers.yaml +51 -0
  95. package/dist/clis/instagram/following.yaml +51 -0
  96. package/dist/clis/instagram/like.yaml +46 -0
  97. package/dist/clis/instagram/profile.yaml +42 -0
  98. package/dist/clis/instagram/save.yaml +46 -0
  99. package/dist/clis/instagram/saved.yaml +40 -0
  100. package/dist/clis/instagram/search.yaml +43 -0
  101. package/dist/clis/instagram/unfollow.yaml +38 -0
  102. package/dist/clis/instagram/unlike.yaml +46 -0
  103. package/dist/clis/instagram/unsave.yaml +46 -0
  104. package/dist/clis/instagram/user.yaml +54 -0
  105. package/dist/clis/jike/repost.js +1 -1
  106. package/dist/clis/jimeng/generate.yaml +1 -0
  107. package/dist/clis/linux-do/category.yaml +1 -0
  108. package/dist/clis/lobsters/active.yaml +29 -0
  109. package/dist/clis/lobsters/hot.yaml +29 -0
  110. package/dist/clis/lobsters/newest.yaml +29 -0
  111. package/dist/clis/lobsters/tag.yaml +34 -0
  112. package/dist/clis/medium/feed.d.ts +1 -0
  113. package/dist/clis/medium/feed.js +15 -0
  114. package/dist/clis/medium/search.d.ts +1 -0
  115. package/dist/clis/medium/search.js +15 -0
  116. package/dist/clis/medium/shared.d.ts +5 -0
  117. package/dist/clis/medium/shared.js +78 -0
  118. package/dist/clis/medium/user.d.ts +1 -0
  119. package/dist/clis/medium/user.js +15 -0
  120. package/dist/clis/reddit/comment.js +1 -1
  121. package/dist/clis/reddit/read.js +1 -1
  122. package/dist/clis/reddit/save.js +1 -1
  123. package/dist/clis/reddit/subreddit.yaml +1 -0
  124. package/dist/clis/reddit/subscribe.js +1 -1
  125. package/dist/clis/reddit/upvote.js +1 -1
  126. package/dist/clis/sinablog/article.d.ts +1 -0
  127. package/dist/clis/sinablog/article.js +14 -0
  128. package/dist/clis/sinablog/hot.d.ts +1 -0
  129. package/dist/clis/sinablog/hot.js +14 -0
  130. package/dist/clis/sinablog/search.d.ts +1 -0
  131. package/dist/clis/sinablog/search.js +51 -0
  132. package/dist/clis/sinablog/shared.d.ts +7 -0
  133. package/dist/clis/sinablog/shared.js +187 -0
  134. package/dist/clis/sinablog/user.d.ts +1 -0
  135. package/dist/clis/sinablog/user.js +15 -0
  136. package/dist/clis/substack/feed.d.ts +1 -0
  137. package/dist/clis/substack/feed.js +15 -0
  138. package/dist/clis/substack/publication.d.ts +1 -0
  139. package/dist/clis/substack/publication.js +15 -0
  140. package/dist/clis/substack/search.d.ts +1 -0
  141. package/dist/clis/substack/search.js +77 -0
  142. package/dist/clis/substack/shared.d.ts +4 -0
  143. package/dist/clis/substack/shared.js +129 -0
  144. package/dist/clis/tiktok/comment.yaml +66 -0
  145. package/dist/clis/tiktok/explore.yaml +39 -0
  146. package/dist/clis/tiktok/follow.yaml +39 -0
  147. package/dist/clis/tiktok/following.yaml +46 -0
  148. package/dist/clis/tiktok/friends.yaml +47 -0
  149. package/dist/clis/tiktok/like.yaml +38 -0
  150. package/dist/clis/tiktok/live.yaml +51 -0
  151. package/dist/clis/tiktok/notifications.yaml +52 -0
  152. package/dist/clis/tiktok/profile.yaml +45 -0
  153. package/dist/clis/tiktok/save.yaml +34 -0
  154. package/dist/clis/tiktok/search.yaml +46 -0
  155. package/dist/clis/tiktok/unfollow.yaml +44 -0
  156. package/dist/clis/tiktok/unlike.yaml +38 -0
  157. package/dist/clis/tiktok/unsave.yaml +36 -0
  158. package/dist/clis/tiktok/user.yaml +44 -0
  159. package/dist/clis/twitter/download.d.ts +1 -1
  160. package/dist/clis/twitter/download.js +3 -3
  161. package/dist/clis/twitter/followers.js +1 -1
  162. package/dist/clis/twitter/following.js +1 -1
  163. package/dist/clis/twitter/thread.js +1 -1
  164. package/dist/clis/twitter/timeline.d.ts +23 -0
  165. package/dist/clis/twitter/timeline.js +42 -14
  166. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  167. package/dist/clis/twitter/timeline.test.js +102 -0
  168. package/dist/clis/wikipedia/random.d.ts +1 -0
  169. package/dist/clis/wikipedia/random.js +19 -0
  170. package/dist/clis/wikipedia/search.js +3 -3
  171. package/dist/clis/wikipedia/summary.js +4 -9
  172. package/dist/clis/wikipedia/trending.d.ts +1 -0
  173. package/dist/clis/wikipedia/trending.js +35 -0
  174. package/dist/clis/wikipedia/utils.d.ts +28 -0
  175. package/dist/clis/wikipedia/utils.js +13 -0
  176. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  177. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  178. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  179. package/dist/clis/xiaohongshu/download.js +1 -1
  180. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  181. package/dist/clis/xueqiu/search.yaml +2 -1
  182. package/dist/clis/xueqiu/stock.yaml +2 -0
  183. package/dist/clis/yahoo-finance/quote.js +1 -1
  184. package/dist/commanderAdapter.js +13 -7
  185. package/dist/discovery.d.ts +8 -0
  186. package/dist/discovery.js +105 -19
  187. package/dist/doctor.js +3 -1
  188. package/dist/doctor.test.js +46 -2
  189. package/dist/engine.test.d.ts +0 -3
  190. package/dist/engine.test.js +74 -6
  191. package/dist/execution.d.ts +4 -2
  192. package/dist/execution.js +31 -7
  193. package/dist/explore.d.ts +76 -3
  194. package/dist/explore.js +11 -4
  195. package/dist/generate.d.ts +41 -2
  196. package/dist/generate.js +5 -4
  197. package/dist/main.js +2 -1
  198. package/dist/pipeline/executor.d.ts +2 -2
  199. package/dist/pipeline/executor.js +2 -2
  200. package/dist/pipeline/executor.test.js +33 -6
  201. package/dist/pipeline/registry.d.ts +1 -1
  202. package/dist/pipeline/steps/browser.d.ts +7 -7
  203. package/dist/pipeline/steps/browser.js +15 -7
  204. package/dist/pipeline/steps/fetch.d.ts +1 -1
  205. package/dist/pipeline/steps/fetch.js +11 -7
  206. package/dist/pipeline/steps/transform.d.ts +6 -5
  207. package/dist/pipeline/steps/transform.js +30 -9
  208. package/dist/pipeline/template.d.ts +6 -6
  209. package/dist/pipeline/template.js +43 -5
  210. package/dist/pipeline/template.test.js +18 -0
  211. package/dist/pipeline/transform.test.js +11 -0
  212. package/dist/plugin.d.ts +31 -0
  213. package/dist/plugin.js +216 -0
  214. package/dist/plugin.test.d.ts +4 -0
  215. package/dist/plugin.test.js +76 -0
  216. package/dist/registry-api.d.ts +11 -0
  217. package/dist/registry-api.js +9 -0
  218. package/dist/registry.d.ts +11 -0
  219. package/dist/registry.js +6 -1
  220. package/dist/synthesize.d.ts +94 -4
  221. package/dist/synthesize.js +5 -4
  222. package/dist/types.d.ts +39 -26
  223. package/dist/validate.js +8 -2
  224. package/docs/.vitepress/config.mts +6 -4
  225. package/docs/adapters/browser/barchart.md +6 -5
  226. package/docs/adapters/browser/bilibili.md +9 -0
  227. package/docs/adapters/browser/devto.md +35 -0
  228. package/docs/adapters/browser/douban.md +38 -0
  229. package/docs/adapters/browser/facebook.md +36 -0
  230. package/docs/adapters/browser/google.md +62 -0
  231. package/docs/adapters/browser/grok.md +26 -8
  232. package/docs/adapters/browser/instagram.md +46 -0
  233. package/docs/adapters/browser/lobsters.md +32 -0
  234. package/docs/adapters/browser/medium.md +32 -0
  235. package/docs/adapters/browser/reddit.md +9 -0
  236. package/docs/adapters/browser/sinablog.md +36 -0
  237. package/docs/adapters/browser/substack.md +38 -0
  238. package/docs/adapters/browser/tiktok.md +68 -0
  239. package/docs/adapters/browser/wikipedia.md +11 -2
  240. package/docs/adapters/browser/xueqiu.md +10 -0
  241. package/docs/adapters/browser/yahoo-finance.md +6 -5
  242. package/docs/adapters/desktop/antigravity.md +6 -0
  243. package/docs/adapters/desktop/chatgpt.md +2 -1
  244. package/docs/adapters/desktop/codex.md +5 -1
  245. package/docs/adapters/desktop/cursor.md +4 -0
  246. package/docs/adapters/desktop/discord.md +7 -7
  247. package/docs/adapters/index.md +1 -4
  248. package/docs/guide/getting-started.md +1 -0
  249. package/docs/guide/plugins.md +153 -0
  250. package/docs/zh/guide/plugins.md +107 -0
  251. package/extension/src/background.ts +18 -11
  252. package/package.json +10 -5
  253. package/scripts/clean-dist.cjs +13 -0
  254. package/src/browser/cdp.ts +71 -31
  255. package/src/browser/daemon-client.ts +2 -1
  256. package/src/browser/dom-helpers.ts +38 -7
  257. package/src/browser/dom-snapshot.test.ts +249 -0
  258. package/src/browser/dom-snapshot.ts +770 -0
  259. package/src/browser/index.ts +2 -0
  260. package/src/browser/page.ts +50 -19
  261. package/src/build-manifest.test.ts +70 -2
  262. package/src/build-manifest.ts +94 -26
  263. package/src/cli.ts +71 -2
  264. package/src/clis/barchart/greeks.ts +1 -1
  265. package/src/clis/barchart/options.ts +1 -1
  266. package/src/clis/barchart/quote.ts +1 -1
  267. package/src/clis/bilibili/download.ts +1 -1
  268. package/src/clis/bilibili/following.ts +1 -1
  269. package/src/clis/bilibili/subtitle.ts +1 -1
  270. package/src/clis/bilibili/user-videos.ts +1 -1
  271. package/src/clis/boss/batchgreet.ts +14 -106
  272. package/src/clis/boss/chatlist.ts +12 -26
  273. package/src/clis/boss/chatmsg.ts +16 -40
  274. package/src/clis/boss/common.ts +287 -0
  275. package/src/clis/boss/detail.ts +8 -54
  276. package/src/clis/boss/exchange.ts +15 -89
  277. package/src/clis/boss/greet.ts +23 -160
  278. package/src/clis/boss/invite.ts +36 -133
  279. package/src/clis/boss/joblist.ts +7 -36
  280. package/src/clis/boss/mark.ts +13 -94
  281. package/src/clis/boss/recommend.ts +12 -57
  282. package/src/clis/boss/resume.ts +19 -124
  283. package/src/clis/boss/search.ts +13 -66
  284. package/src/clis/boss/send.ts +21 -161
  285. package/src/clis/boss/stats.ts +19 -74
  286. package/src/clis/coupang/add-to-cart.ts +1 -1
  287. package/src/clis/devto/tag.yaml +34 -0
  288. package/src/clis/devto/top.yaml +29 -0
  289. package/src/clis/devto/user.yaml +33 -0
  290. package/src/clis/douban/book-hot.ts +15 -0
  291. package/src/clis/douban/marks.ts +135 -0
  292. package/src/clis/douban/movie-hot.ts +15 -0
  293. package/src/clis/douban/reviews.ts +127 -0
  294. package/src/clis/douban/search.ts +17 -0
  295. package/src/clis/douban/shared.ts +165 -0
  296. package/src/clis/douban/subject.yaml +76 -0
  297. package/src/clis/douban/top250.yaml +70 -0
  298. package/src/clis/douban/utils.ts +81 -0
  299. package/src/clis/facebook/add-friend.yaml +43 -0
  300. package/src/clis/facebook/events.yaml +44 -0
  301. package/src/clis/facebook/feed.yaml +63 -0
  302. package/src/clis/facebook/friends.yaml +42 -0
  303. package/src/clis/facebook/groups.yaml +50 -0
  304. package/src/clis/facebook/join-group.yaml +44 -0
  305. package/src/clis/facebook/memories.yaml +39 -0
  306. package/src/clis/facebook/notifications.yaml +40 -0
  307. package/src/clis/facebook/profile.yaml +37 -0
  308. package/src/clis/facebook/search.yaml +46 -0
  309. package/src/clis/google/news.ts +66 -0
  310. package/src/clis/google/search.ts +133 -0
  311. package/src/clis/google/suggest.ts +40 -0
  312. package/src/clis/google/trends.ts +44 -0
  313. package/src/clis/google/utils.test.ts +82 -0
  314. package/src/clis/google/utils.ts +24 -0
  315. package/src/clis/grok/ask.test.ts +53 -0
  316. package/src/clis/grok/ask.ts +300 -69
  317. package/src/clis/instagram/comment.yaml +52 -0
  318. package/src/clis/instagram/explore.yaml +43 -0
  319. package/src/clis/instagram/follow.yaml +41 -0
  320. package/src/clis/instagram/followers.yaml +51 -0
  321. package/src/clis/instagram/following.yaml +51 -0
  322. package/src/clis/instagram/like.yaml +46 -0
  323. package/src/clis/instagram/profile.yaml +42 -0
  324. package/src/clis/instagram/save.yaml +46 -0
  325. package/src/clis/instagram/saved.yaml +40 -0
  326. package/src/clis/instagram/search.yaml +43 -0
  327. package/src/clis/instagram/unfollow.yaml +38 -0
  328. package/src/clis/instagram/unlike.yaml +46 -0
  329. package/src/clis/instagram/unsave.yaml +46 -0
  330. package/src/clis/instagram/user.yaml +54 -0
  331. package/src/clis/jike/repost.ts +1 -1
  332. package/src/clis/jimeng/generate.yaml +1 -0
  333. package/src/clis/linux-do/category.yaml +1 -0
  334. package/src/clis/lobsters/active.yaml +29 -0
  335. package/src/clis/lobsters/hot.yaml +29 -0
  336. package/src/clis/lobsters/newest.yaml +29 -0
  337. package/src/clis/lobsters/tag.yaml +34 -0
  338. package/src/clis/medium/feed.ts +16 -0
  339. package/src/clis/medium/search.ts +16 -0
  340. package/src/clis/medium/shared.ts +83 -0
  341. package/src/clis/medium/user.ts +16 -0
  342. package/src/clis/reddit/comment.ts +1 -1
  343. package/src/clis/reddit/read.ts +1 -1
  344. package/src/clis/reddit/save.ts +1 -1
  345. package/src/clis/reddit/subreddit.yaml +1 -0
  346. package/src/clis/reddit/subscribe.ts +1 -1
  347. package/src/clis/reddit/upvote.ts +1 -1
  348. package/src/clis/sinablog/article.ts +15 -0
  349. package/src/clis/sinablog/hot.ts +15 -0
  350. package/src/clis/sinablog/search.ts +56 -0
  351. package/src/clis/sinablog/shared.ts +198 -0
  352. package/src/clis/sinablog/user.ts +16 -0
  353. package/src/clis/substack/feed.ts +16 -0
  354. package/src/clis/substack/publication.ts +16 -0
  355. package/src/clis/substack/search.ts +91 -0
  356. package/src/clis/substack/shared.ts +132 -0
  357. package/src/clis/tiktok/comment.yaml +66 -0
  358. package/src/clis/tiktok/explore.yaml +39 -0
  359. package/src/clis/tiktok/follow.yaml +39 -0
  360. package/src/clis/tiktok/following.yaml +46 -0
  361. package/src/clis/tiktok/friends.yaml +47 -0
  362. package/src/clis/tiktok/like.yaml +38 -0
  363. package/src/clis/tiktok/live.yaml +51 -0
  364. package/src/clis/tiktok/notifications.yaml +52 -0
  365. package/src/clis/tiktok/profile.yaml +45 -0
  366. package/src/clis/tiktok/save.yaml +34 -0
  367. package/src/clis/tiktok/search.yaml +46 -0
  368. package/src/clis/tiktok/unfollow.yaml +44 -0
  369. package/src/clis/tiktok/unlike.yaml +38 -0
  370. package/src/clis/tiktok/unsave.yaml +36 -0
  371. package/src/clis/tiktok/user.yaml +44 -0
  372. package/src/clis/twitter/download.ts +3 -3
  373. package/src/clis/twitter/followers.ts +1 -1
  374. package/src/clis/twitter/following.ts +1 -1
  375. package/src/clis/twitter/thread.ts +1 -1
  376. package/src/clis/twitter/timeline.test.ts +109 -0
  377. package/src/clis/twitter/timeline.ts +59 -19
  378. package/src/clis/wikipedia/random.ts +19 -0
  379. package/src/clis/wikipedia/search.ts +10 -4
  380. package/src/clis/wikipedia/summary.ts +4 -9
  381. package/src/clis/wikipedia/trending.ts +41 -0
  382. package/src/clis/wikipedia/utils.ts +31 -0
  383. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  384. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  385. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  386. package/src/clis/xiaohongshu/download.ts +1 -1
  387. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  388. package/src/clis/xueqiu/search.yaml +2 -1
  389. package/src/clis/xueqiu/stock.yaml +2 -0
  390. package/src/clis/yahoo-finance/quote.ts +1 -1
  391. package/src/commanderAdapter.ts +17 -10
  392. package/src/discovery.ts +134 -24
  393. package/src/doctor.test.ts +59 -2
  394. package/src/doctor.ts +4 -2
  395. package/src/engine.test.ts +79 -6
  396. package/src/execution.ts +42 -16
  397. package/src/explore.ts +77 -9
  398. package/src/generate.ts +58 -9
  399. package/src/main.ts +2 -1
  400. package/src/pipeline/executor.test.ts +35 -6
  401. package/src/pipeline/executor.ts +11 -7
  402. package/src/pipeline/registry.ts +3 -3
  403. package/src/pipeline/steps/browser.ts +24 -15
  404. package/src/pipeline/steps/fetch.ts +18 -13
  405. package/src/pipeline/steps/transform.ts +40 -15
  406. package/src/pipeline/template.test.ts +18 -0
  407. package/src/pipeline/template.ts +86 -13
  408. package/src/pipeline/transform.test.ts +15 -2
  409. package/src/plugin.test.ts +86 -0
  410. package/src/plugin.ts +254 -0
  411. package/src/registry-api.ts +12 -0
  412. package/src/registry.ts +19 -1
  413. package/src/synthesize.ts +102 -21
  414. package/src/types.ts +44 -12
  415. package/src/validate.ts +19 -4
  416. package/tests/e2e/browser-public.test.ts +11 -0
  417. package/tests/e2e/public-commands.test.ts +64 -0
  418. package/dist/clis/feishu/new.d.ts +0 -1
  419. package/dist/clis/feishu/new.js +0 -27
  420. package/dist/clis/feishu/read.d.ts +0 -1
  421. package/dist/clis/feishu/read.js +0 -40
  422. package/dist/clis/feishu/search.d.ts +0 -1
  423. package/dist/clis/feishu/search.js +0 -30
  424. package/dist/clis/feishu/send.d.ts +0 -1
  425. package/dist/clis/feishu/send.js +0 -39
  426. package/dist/clis/feishu/status.d.ts +0 -1
  427. package/dist/clis/feishu/status.js +0 -28
  428. package/dist/clis/neteasemusic/like.d.ts +0 -1
  429. package/dist/clis/neteasemusic/like.js +0 -25
  430. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  431. package/dist/clis/neteasemusic/lyrics.js +0 -47
  432. package/dist/clis/neteasemusic/next.d.ts +0 -1
  433. package/dist/clis/neteasemusic/next.js +0 -26
  434. package/dist/clis/neteasemusic/play.d.ts +0 -1
  435. package/dist/clis/neteasemusic/play.js +0 -26
  436. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  437. package/dist/clis/neteasemusic/playing.js +0 -59
  438. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  439. package/dist/clis/neteasemusic/playlist.js +0 -46
  440. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  441. package/dist/clis/neteasemusic/prev.js +0 -25
  442. package/dist/clis/neteasemusic/search.d.ts +0 -1
  443. package/dist/clis/neteasemusic/search.js +0 -52
  444. package/dist/clis/neteasemusic/status.d.ts +0 -1
  445. package/dist/clis/neteasemusic/status.js +0 -16
  446. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  447. package/dist/clis/neteasemusic/volume.js +0 -54
  448. package/dist/clis/wechat/chats.d.ts +0 -1
  449. package/dist/clis/wechat/chats.js +0 -28
  450. package/dist/clis/wechat/contacts.d.ts +0 -1
  451. package/dist/clis/wechat/contacts.js +0 -28
  452. package/dist/clis/wechat/read.d.ts +0 -1
  453. package/dist/clis/wechat/read.js +0 -58
  454. package/dist/clis/wechat/search.d.ts +0 -1
  455. package/dist/clis/wechat/search.js +0 -31
  456. package/dist/clis/wechat/send.d.ts +0 -1
  457. package/dist/clis/wechat/send.js +0 -42
  458. package/dist/clis/wechat/status.d.ts +0 -1
  459. package/dist/clis/wechat/status.js +0 -29
  460. package/dist/pipeline.d.ts +0 -7
  461. package/dist/pipeline.js +0 -7
  462. package/docs/adapters/browser/github.md +0 -26
  463. package/docs/adapters/desktop/feishu.md +0 -20
  464. package/docs/adapters/desktop/neteasemusic.md +0 -31
  465. package/docs/adapters/desktop/wechat.md +0 -28
  466. package/src/clis/antigravity/README.md +0 -5
  467. package/src/clis/antigravity/README.zh-CN.md +0 -51
  468. package/src/clis/chaoxing/README.md +0 -14
  469. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  470. package/src/clis/chatgpt/README.md +0 -5
  471. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  472. package/src/clis/chatwise/README.md +0 -5
  473. package/src/clis/chatwise/README.zh-CN.md +0 -38
  474. package/src/clis/codex/README.md +0 -5
  475. package/src/clis/codex/README.zh-CN.md +0 -33
  476. package/src/clis/cursor/README.md +0 -5
  477. package/src/clis/cursor/README.zh-CN.md +0 -33
  478. package/src/clis/discord-app/README.md +0 -5
  479. package/src/clis/discord-app/README.zh-CN.md +0 -28
  480. package/src/clis/feishu/README.md +0 -5
  481. package/src/clis/feishu/README.zh-CN.md +0 -20
  482. package/src/clis/feishu/new.ts +0 -32
  483. package/src/clis/feishu/read.ts +0 -48
  484. package/src/clis/feishu/search.ts +0 -35
  485. package/src/clis/feishu/send.ts +0 -46
  486. package/src/clis/feishu/status.ts +0 -34
  487. package/src/clis/neteasemusic/README.md +0 -5
  488. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  489. package/src/clis/neteasemusic/like.ts +0 -28
  490. package/src/clis/neteasemusic/lyrics.ts +0 -53
  491. package/src/clis/neteasemusic/next.ts +0 -30
  492. package/src/clis/neteasemusic/play.ts +0 -30
  493. package/src/clis/neteasemusic/playing.ts +0 -62
  494. package/src/clis/neteasemusic/playlist.ts +0 -51
  495. package/src/clis/neteasemusic/prev.ts +0 -29
  496. package/src/clis/neteasemusic/search.ts +0 -58
  497. package/src/clis/neteasemusic/status.ts +0 -18
  498. package/src/clis/neteasemusic/volume.ts +0 -61
  499. package/src/clis/notion/README.md +0 -5
  500. package/src/clis/notion/README.zh-CN.md +0 -29
  501. package/src/clis/wechat/README.md +0 -5
  502. package/src/clis/wechat/README.zh-CN.md +0 -28
  503. package/src/clis/wechat/chats.ts +0 -33
  504. package/src/clis/wechat/contacts.ts +0 -33
  505. package/src/clis/wechat/read.ts +0 -72
  506. package/src/clis/wechat/search.ts +0 -36
  507. package/src/clis/wechat/send.ts +0 -49
  508. package/src/clis/wechat/status.ts +0 -35
  509. package/src/pipeline.ts +0 -8
@@ -9,6 +9,8 @@ export { Page } from './page.js';
9
9
  export { BrowserBridge, BrowserBridge as PlaywrightMCP } from './mcp.js';
10
10
  export { CDPBridge } from './cdp.js';
11
11
  export { isDaemonRunning } from './daemon-client.js';
12
+ export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
13
+ export type { SnapshotOptions } from './dom-snapshot.js';
12
14
 
13
15
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
14
16
  import { __test__ as cdpTest } from './cdp.js';
@@ -11,9 +11,10 @@
11
11
  */
12
12
 
13
13
  import { formatSnapshot } from '../snapshotFormatter.js';
14
- import type { IPage } from '../types.js';
14
+ import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
15
15
  import { sendCommand } from './daemon-client.js';
16
16
  import { wrapForEval } from './utils.js';
17
+ import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
17
18
  import {
18
19
  clickJs,
19
20
  typeTextJs,
@@ -69,17 +70,40 @@ export class Page implements IPage {
69
70
  }
70
71
  }
71
72
 
72
- async evaluate(js: string): Promise<any> {
73
+ async evaluate(js: string): Promise<unknown> {
73
74
  const code = wrapForEval(js);
74
75
  return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
75
76
  }
76
77
 
77
- async getCookies(opts: { domain?: string; url?: string } = {}): Promise<any[]> {
78
+ async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
78
79
  const result = await sendCommand('cookies', { ...this._workspaceOpt(), ...opts });
79
80
  return Array.isArray(result) ? result : [];
80
81
  }
81
82
 
82
- async snapshot(opts: { interactive?: boolean; compact?: boolean; maxDepth?: number; raw?: boolean } = {}): Promise<any> {
83
+ async snapshot(opts: SnapshotOptions = {}): Promise<unknown> {
84
+ // Primary: use the advanced DOM snapshot engine with multi-layer pruning
85
+ const snapshotJs = generateSnapshotJs({
86
+ viewportExpand: opts.viewportExpand ?? 800,
87
+ maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
88
+ interactiveOnly: opts.interactive ?? false,
89
+ maxTextLength: opts.maxTextLength ?? 120,
90
+ includeScrollInfo: true,
91
+ bboxDedup: true,
92
+ });
93
+
94
+ try {
95
+ const result = await sendCommand('exec', { code: snapshotJs, ...this._workspaceOpt(), ...this._tabOpt() });
96
+ // The advanced engine already produces a clean, pruned, LLM-friendly output.
97
+ // Do NOT pass through formatSnapshot — its format is incompatible.
98
+ return result;
99
+ } catch {
100
+ // Fallback: basic DOM snapshot (original implementation)
101
+ return this._basicSnapshot(opts);
102
+ }
103
+ }
104
+
105
+ /** Fallback basic snapshot — original buildTree approach */
106
+ private async _basicSnapshot(opts: Pick<SnapshotOptions, 'interactive' | 'compact' | 'maxDepth' | 'raw'> = {}): Promise<unknown> {
83
107
  const maxDepth = Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200));
84
108
  const code = `
85
109
  (async () => {
@@ -93,7 +117,7 @@ export class Page implements IPage {
93
117
 
94
118
  let indent = ' '.repeat(depth);
95
119
  let line = indent + role;
96
- if (name) line += ' "' + name.replace(/"/g, '\\\\"') + '"';
120
+ if (name) line += ' "' + name.replace(/"/g, '\\\\\\"') + '"';
97
121
  if (node.tagName?.toLowerCase() === 'a' && node.href) line += ' [' + node.href + ']';
98
122
  if (node.tagName?.toLowerCase() === 'input') line += ' [' + (node.type || 'text') + ']';
99
123
 
@@ -129,7 +153,17 @@ export class Page implements IPage {
129
153
  await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
130
154
  }
131
155
 
132
- async wait(options: number | { text?: string; time?: number; timeout?: number }): Promise<void> {
156
+ async scrollTo(ref: string): Promise<unknown> {
157
+ const code = scrollToRefJs(ref);
158
+ return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
159
+ }
160
+
161
+ async getFormState(): Promise<Record<string, unknown>> {
162
+ const code = getFormStateJs();
163
+ return (await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() })) as Record<string, unknown>;
164
+ }
165
+
166
+ async wait(options: number | WaitOptions): Promise<void> {
133
167
  if (typeof options === 'number') {
134
168
  await new Promise(resolve => setTimeout(resolve, options * 1000));
135
169
  return;
@@ -145,8 +179,9 @@ export class Page implements IPage {
145
179
  }
146
180
  }
147
181
 
148
- async tabs(): Promise<any> {
149
- return sendCommand('tabs', { op: 'list', ...this._workspaceOpt() });
182
+ async tabs(): Promise<unknown[]> {
183
+ const result = await sendCommand('tabs', { op: 'list', ...this._workspaceOpt() });
184
+ return Array.isArray(result) ? result : [];
150
185
  }
151
186
 
152
187
  async closeTab(index?: number): Promise<void> {
@@ -161,9 +196,10 @@ export class Page implements IPage {
161
196
  await sendCommand('tabs', { op: 'select', index, ...this._workspaceOpt() });
162
197
  }
163
198
 
164
- async networkRequests(includeStatic: boolean = false): Promise<any> {
199
+ async networkRequests(includeStatic: boolean = false): Promise<unknown[]> {
165
200
  const code = networkRequestsJs(includeStatic);
166
- return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
201
+ const result = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
202
+ return Array.isArray(result) ? result : [];
167
203
  }
168
204
 
169
205
  /**
@@ -171,7 +207,7 @@ export class Page implements IPage {
171
207
  * Would require CDP Runtime.consoleAPICalled event listener.
172
208
  * @returns Always returns empty array.
173
209
  */
174
- async consoleMessages(_level: string = 'info'): Promise<any> {
210
+ async consoleMessages(_level: string = 'info'): Promise<unknown[]> {
175
211
  return [];
176
212
  }
177
213
 
@@ -182,12 +218,7 @@ export class Page implements IPage {
182
218
  * @param options.fullPage - capture full scrollable page
183
219
  * @param options.path - save to file path (returns base64 if omitted)
184
220
  */
185
- async screenshot(options: {
186
- format?: 'png' | 'jpeg';
187
- quality?: number;
188
- fullPage?: boolean;
189
- path?: string;
190
- } = {}): Promise<string> {
221
+ async screenshot(options: ScreenshotOptions = {}): Promise<string> {
191
222
  const base64 = await sendCommand('screenshot', {
192
223
  ...this._workspaceOpt(),
193
224
  format: options.format,
@@ -229,11 +260,11 @@ export class Page implements IPage {
229
260
  }));
230
261
  }
231
262
 
232
- async getInterceptedRequests(): Promise<any[]> {
263
+ async getInterceptedRequests(): Promise<unknown[]> {
233
264
  const { generateReadInterceptedJs } = await import('../interceptor.js');
234
265
  // Same as installInterceptor: must go through evaluate() for IIFE wrapping
235
266
  const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
236
- return (result as any[]) || [];
267
+ return Array.isArray(result) ? result : [];
237
268
  }
238
269
  }
239
270
 
@@ -1,5 +1,8 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { parseTsArgsBlock } from './build-manifest.js';
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { parseTsArgsBlock, scanTs, shouldReplaceManifestEntry } from './build-manifest.js';
3
6
 
4
7
  describe('parseTsArgsBlock', () => {
5
8
  it('keeps args with nested choices arrays', () => {
@@ -62,3 +65,68 @@ describe('parseTsArgsBlock', () => {
62
65
  ]);
63
66
  });
64
67
  });
68
+
69
+ describe('manifest helper rules', () => {
70
+ const tempDirs: string[] = [];
71
+
72
+ afterEach(() => {
73
+ for (const dir of tempDirs.splice(0)) {
74
+ fs.rmSync(dir, { recursive: true, force: true });
75
+ }
76
+ });
77
+
78
+ it('prefers TS adapters over duplicate YAML adapters', () => {
79
+ expect(shouldReplaceManifestEntry(
80
+ {
81
+ site: 'demo',
82
+ name: 'search',
83
+ description: 'yaml',
84
+ strategy: 'public',
85
+ browser: false,
86
+ args: [],
87
+ type: 'yaml',
88
+ },
89
+ {
90
+ site: 'demo',
91
+ name: 'search',
92
+ description: 'ts',
93
+ strategy: 'public',
94
+ browser: false,
95
+ args: [],
96
+ type: 'ts',
97
+ modulePath: 'demo/search.js',
98
+ },
99
+ )).toBe(true);
100
+
101
+ expect(shouldReplaceManifestEntry(
102
+ {
103
+ site: 'demo',
104
+ name: 'search',
105
+ description: 'ts',
106
+ strategy: 'public',
107
+ browser: false,
108
+ args: [],
109
+ type: 'ts',
110
+ modulePath: 'demo/search.js',
111
+ },
112
+ {
113
+ site: 'demo',
114
+ name: 'search',
115
+ description: 'yaml',
116
+ strategy: 'public',
117
+ browser: false,
118
+ args: [],
119
+ type: 'yaml',
120
+ },
121
+ )).toBe(false);
122
+ });
123
+
124
+ it('skips TS files that do not register a cli', () => {
125
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
126
+ tempDirs.push(dir);
127
+ const file = path.join(dir, 'utils.ts');
128
+ fs.writeFileSync(file, `export function helper() { return 'noop'; }`);
129
+
130
+ expect(scanTs(file, 'demo')).toBeNull();
131
+ });
132
+ });
@@ -18,7 +18,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
18
  const CLIS_DIR = path.resolve(__dirname, 'clis');
19
19
  const OUTPUT = path.resolve(__dirname, '..', 'dist', 'cli-manifest.json');
20
20
 
21
- interface ManifestEntry {
21
+ export interface ManifestEntry {
22
22
  site: string;
23
23
  name: string;
24
24
  description: string;
@@ -28,19 +28,53 @@ interface ManifestEntry {
28
28
  args: Array<{
29
29
  name: string;
30
30
  type?: string;
31
- default?: any;
31
+ default?: unknown;
32
32
  required?: boolean;
33
33
  positional?: boolean;
34
34
  help?: string;
35
35
  choices?: string[];
36
36
  }>;
37
37
  columns?: string[];
38
- pipeline?: any[];
38
+ pipeline?: Record<string, unknown>[];
39
39
  timeout?: number;
40
40
  /** 'yaml' or 'ts' — determines how executeCommand loads the handler */
41
41
  type: 'yaml' | 'ts';
42
42
  /** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
43
43
  modulePath?: string;
44
+ /** Pre-navigation control — see CliCommand.navigateBefore */
45
+ navigateBefore?: boolean | string;
46
+ }
47
+
48
+ interface YamlArgDefinition {
49
+ type?: string;
50
+ default?: unknown;
51
+ required?: boolean;
52
+ positional?: boolean;
53
+ description?: string;
54
+ help?: string;
55
+ choices?: string[];
56
+ }
57
+
58
+ interface YamlCliDefinition {
59
+ site?: string;
60
+ name?: string;
61
+ description?: string;
62
+ domain?: string;
63
+ strategy?: string;
64
+ browser?: boolean;
65
+ args?: Record<string, YamlArgDefinition>;
66
+ columns?: string[];
67
+ pipeline?: Record<string, unknown>[];
68
+ timeout?: number;
69
+ navigateBefore?: boolean | string;
70
+ }
71
+
72
+ function isRecord(value: unknown): value is Record<string, unknown> {
73
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
74
+ }
75
+
76
+ function getErrorMessage(error: unknown): string {
77
+ return error instanceof Error ? error.message : String(error);
44
78
  }
45
79
 
46
80
  function extractBalancedBlock(
@@ -127,7 +161,7 @@ export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
127
161
  const helpMatch = body.match(/help\s*:\s*['"`]([^'"`]*)['"`]/);
128
162
  const positionalMatch = body.match(/positional\s*:\s*(true|false)/);
129
163
 
130
- let defaultVal: any = undefined;
164
+ let defaultVal: unknown = undefined;
131
165
  if (defaultMatch) {
132
166
  const raw = defaultMatch[1].trim();
133
167
  if (raw === 'true') defaultVal = true;
@@ -156,21 +190,23 @@ export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
156
190
  function scanYaml(filePath: string, site: string): ManifestEntry | null {
157
191
  try {
158
192
  const raw = fs.readFileSync(filePath, 'utf-8');
159
- const def = yaml.load(raw) as any;
160
- if (!def || typeof def !== 'object') return null;
193
+ const def = yaml.load(raw) as YamlCliDefinition | null;
194
+ if (!isRecord(def)) return null;
195
+ const cliDef = def as YamlCliDefinition;
161
196
 
162
- const strategyStr = def.strategy ?? (def.browser === false ? 'public' : 'cookie');
197
+ const strategyStr = cliDef.strategy ?? (cliDef.browser === false ? 'public' : 'cookie');
163
198
  const strategy = strategyStr.toUpperCase();
164
- const browser = def.browser ?? (strategy !== 'PUBLIC');
199
+ const browser = cliDef.browser ?? (strategy !== 'PUBLIC');
165
200
 
166
201
  const args: ManifestEntry['args'] = [];
167
- if (def.args && typeof def.args === 'object') {
168
- for (const [argName, argDef] of Object.entries(def.args as Record<string, any>)) {
202
+ if (cliDef.args && typeof cliDef.args === 'object') {
203
+ for (const [argName, argDef] of Object.entries(cliDef.args)) {
169
204
  args.push({
170
205
  name: argName,
171
206
  type: argDef?.type ?? 'str',
172
207
  default: argDef?.default,
173
208
  required: argDef?.required ?? false,
209
+ positional: argDef?.positional === true || undefined,
174
210
  help: argDef?.description ?? argDef?.help ?? '',
175
211
  choices: argDef?.choices,
176
212
  });
@@ -178,25 +214,26 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
178
214
  }
179
215
 
180
216
  return {
181
- site: def.site ?? site,
182
- name: def.name ?? path.basename(filePath, path.extname(filePath)),
183
- description: def.description ?? '',
184
- domain: def.domain,
217
+ site: cliDef.site ?? site,
218
+ name: cliDef.name ?? path.basename(filePath, path.extname(filePath)),
219
+ description: cliDef.description ?? '',
220
+ domain: cliDef.domain,
185
221
  strategy: strategy.toLowerCase(),
186
222
  browser,
187
223
  args,
188
- columns: def.columns,
189
- pipeline: def.pipeline,
190
- timeout: def.timeout,
224
+ columns: cliDef.columns,
225
+ pipeline: cliDef.pipeline,
226
+ timeout: cliDef.timeout,
191
227
  type: 'yaml',
228
+ navigateBefore: cliDef.navigateBefore,
192
229
  };
193
- } catch (err: any) {
194
- process.stderr.write(`Warning: failed to parse ${filePath}: ${err.message}\n`);
230
+ } catch (err) {
231
+ process.stderr.write(`Warning: failed to parse ${filePath}: ${getErrorMessage(err)}\n`);
195
232
  return null;
196
233
  }
197
234
  }
198
235
 
199
- function scanTs(filePath: string, site: string): ManifestEntry | null {
236
+ export function scanTs(filePath: string, site: string): ManifestEntry | null {
200
237
  // TS adapters self-register via cli() at import time.
201
238
  // We statically parse the source to extract metadata for the manifest stub.
202
239
  const baseName = path.basename(filePath, path.extname(filePath));
@@ -248,16 +285,29 @@ function scanTs(filePath: string, site: string): ManifestEntry | null {
248
285
  entry.args = parseTsArgsBlock(argsBlock);
249
286
  }
250
287
 
288
+ // Extract navigateBefore: false
289
+ const navMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
290
+ if (navMatch) entry.navigateBefore = navMatch[1] === 'true' ? true : false;
291
+
251
292
  return entry;
252
- } catch (err: any) {
293
+ } catch (err) {
253
294
  // If parsing fails, log a warning (matching scanYaml behaviour) and skip the entry.
254
- process.stderr.write(`Warning: failed to scan ${filePath}: ${err.message}\n`);
295
+ process.stderr.write(`Warning: failed to scan ${filePath}: ${getErrorMessage(err)}\n`);
255
296
  return null;
256
297
  }
257
298
  }
258
299
 
300
+ /**
301
+ * When both YAML and TS adapters exist for the same site/name,
302
+ * prefer the TS version (it self-registers and typically has richer logic).
303
+ */
304
+ export function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean {
305
+ if (current.type === next.type) return true;
306
+ return current.type === 'yaml' && next.type === 'ts';
307
+ }
308
+
259
309
  export function buildManifest(): ManifestEntry[] {
260
- const manifest: ManifestEntry[] = [];
310
+ const manifest = new Map<string, ManifestEntry>();
261
311
 
262
312
  if (fs.existsSync(CLIS_DIR)) {
263
313
  for (const site of fs.readdirSync(CLIS_DIR)) {
@@ -267,19 +317,37 @@ export function buildManifest(): ManifestEntry[] {
267
317
  const filePath = path.join(siteDir, file);
268
318
  if (file.endsWith('.yaml') || file.endsWith('.yml')) {
269
319
  const entry = scanYaml(filePath, site);
270
- if (entry) manifest.push(entry);
320
+ if (entry) {
321
+ const key = `${entry.site}/${entry.name}`;
322
+ const existing = manifest.get(key);
323
+ if (!existing || shouldReplaceManifestEntry(existing, entry)) {
324
+ if (existing && existing.type !== entry.type) {
325
+ process.stderr.write(`⚠️ Duplicate adapter ${key}: ${existing.type} superseded by ${entry.type}\n`);
326
+ }
327
+ manifest.set(key, entry);
328
+ }
329
+ }
271
330
  } else if (
272
331
  (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts') && file !== 'index.ts') ||
273
332
  (file.endsWith('.js') && !file.endsWith('.d.js') && !file.endsWith('.test.js') && file !== 'index.js')
274
333
  ) {
275
334
  const entry = scanTs(filePath, site);
276
- if (entry) manifest.push(entry);
335
+ if (entry) {
336
+ const key = `${entry.site}/${entry.name}`;
337
+ const existing = manifest.get(key);
338
+ if (!existing || shouldReplaceManifestEntry(existing, entry)) {
339
+ if (existing && existing.type !== entry.type) {
340
+ process.stderr.write(`⚠️ Duplicate adapter ${key}: ${existing.type} superseded by ${entry.type}\n`);
341
+ }
342
+ manifest.set(key, entry);
343
+ }
344
+ }
277
345
  }
278
346
  }
279
347
  }
280
348
  }
281
349
 
282
- return manifest;
350
+ return [...manifest.values()];
283
351
  }
284
352
 
285
353
  function main(): void {
package/src/cli.ts CHANGED
@@ -140,7 +140,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
140
140
  : undefined;
141
141
  const workspace = `explore:${inferHost(url, opts.site)}`;
142
142
  const result = await exploreUrl(url, {
143
- BrowserFactory: getBrowserFactory() as any,
143
+ BrowserFactory: getBrowserFactory(),
144
144
  site: opts.site,
145
145
  goal: opts.goal,
146
146
  waitSeconds: parseFloat(opts.wait),
@@ -172,7 +172,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
172
172
  const workspace = `generate:${inferHost(url, opts.site)}`;
173
173
  const r = await generateCliFromUrl({
174
174
  url,
175
- BrowserFactory: getBrowserFactory() as any,
175
+ BrowserFactory: getBrowserFactory(),
176
176
  builtinClis: BUILTIN_CLIS,
177
177
  userClis: USER_CLIS,
178
178
  goal: opts.goal,
@@ -231,6 +231,75 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
231
231
  printCompletionScript(shell);
232
232
  });
233
233
 
234
+ // ── Plugin management ──────────────────────────────────────────────────────
235
+
236
+ const pluginCmd = program.command('plugin').description('Manage opencli plugins');
237
+
238
+ pluginCmd
239
+ .command('install')
240
+ .description('Install a plugin from GitHub')
241
+ .argument('<source>', 'Plugin source (e.g. github:user/repo)')
242
+ .action(async (source: string) => {
243
+ const { installPlugin } = await import('./plugin.js');
244
+ try {
245
+ const name = installPlugin(source);
246
+ console.log(chalk.green(`✅ Plugin "${name}" installed successfully.`));
247
+ console.log(chalk.dim(` Restart opencli to use the new commands.`));
248
+ } catch (err: any) {
249
+ console.error(chalk.red(`Error: ${err.message}`));
250
+ process.exitCode = 1;
251
+ }
252
+ });
253
+
254
+ pluginCmd
255
+ .command('uninstall')
256
+ .description('Uninstall a plugin')
257
+ .argument('<name>', 'Plugin name')
258
+ .action(async (name: string) => {
259
+ const { uninstallPlugin } = await import('./plugin.js');
260
+ try {
261
+ uninstallPlugin(name);
262
+ console.log(chalk.green(`✅ Plugin "${name}" uninstalled.`));
263
+ } catch (err: any) {
264
+ console.error(chalk.red(`Error: ${err.message}`));
265
+ process.exitCode = 1;
266
+ }
267
+ });
268
+
269
+ pluginCmd
270
+ .command('list')
271
+ .description('List installed plugins')
272
+ .option('-f, --format <fmt>', 'Output format: table, json', 'table')
273
+ .action(async (opts) => {
274
+ const { listPlugins } = await import('./plugin.js');
275
+ const plugins = listPlugins();
276
+ if (plugins.length === 0) {
277
+ console.log(chalk.dim(' No plugins installed.'));
278
+ console.log(chalk.dim(` Install one with: opencli plugin install github:user/repo`));
279
+ return;
280
+ }
281
+ if (opts.format === 'json') {
282
+ renderOutput(plugins, {
283
+ fmt: 'json',
284
+ columns: ['name', 'commands', 'source'],
285
+ title: 'opencli/plugins',
286
+ source: 'opencli plugin list',
287
+ });
288
+ return;
289
+ }
290
+ console.log();
291
+ console.log(chalk.bold(' Installed plugins'));
292
+ console.log();
293
+ for (const p of plugins) {
294
+ const cmds = p.commands.length > 0 ? chalk.dim(` (${p.commands.join(', ')})`) : '';
295
+ const src = p.source ? chalk.dim(` ← ${p.source}`) : '';
296
+ console.log(` ${chalk.cyan(p.name)}${cmds}${src}`);
297
+ }
298
+ console.log();
299
+ console.log(chalk.dim(` ${plugins.length} plugin(s) installed`));
300
+ console.log();
301
+ });
302
+
234
303
  // ── External CLIs ─────────────────────────────────────────────────────────
235
304
 
236
305
  const externalClis = loadExternalClis();
@@ -12,7 +12,7 @@ cli({
12
12
  domain: 'www.barchart.com',
13
13
  strategy: Strategy.COOKIE,
14
14
  args: [
15
- { name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL)' },
15
+ { name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL)' },
16
16
  { name: 'expiration', type: 'str', help: 'Expiration date (YYYY-MM-DD). Defaults to the nearest available expiration.' },
17
17
  { name: 'limit', type: 'int', default: 10, help: 'Number of near-the-money strikes per type' },
18
18
  ],
@@ -11,7 +11,7 @@ cli({
11
11
  domain: 'www.barchart.com',
12
12
  strategy: Strategy.COOKIE,
13
13
  args: [
14
- { name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL)' },
14
+ { name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL)' },
15
15
  { name: 'type', type: 'str', default: 'Call', help: 'Option type: Call or Put', choices: ['Call', 'Put'] },
16
16
  { name: 'limit', type: 'int', default: 20, help: 'Max number of strikes to return' },
17
17
  ],
@@ -11,7 +11,7 @@ cli({
11
11
  domain: 'www.barchart.com',
12
12
  strategy: Strategy.COOKIE,
13
13
  args: [
14
- { name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
14
+ { name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
15
15
  ],
16
16
  columns: [
17
17
  'symbol', 'name', 'price', 'change', 'changePct',
@@ -28,7 +28,7 @@ cli({
28
28
  domain: 'www.bilibili.com',
29
29
  strategy: Strategy.COOKIE,
30
30
  args: [
31
- { name: 'bvid', required: true, help: 'Video BV ID (e.g., BV1xxx)' },
31
+ { name: 'bvid', required: true, positional: true, help: 'Video BV ID (e.g., BV1xxx)' },
32
32
  { name: 'output', default: './bilibili-downloads', help: 'Output directory' },
33
33
  { name: 'quality', default: 'best', help: 'Video quality (best, 1080p, 720p, 480p)' },
34
34
  ],
@@ -8,7 +8,7 @@ cli({
8
8
  description: '获取 Bilibili 用户的关注列表',
9
9
  strategy: Strategy.COOKIE,
10
10
  args: [
11
- { name: 'uid', required: false, help: '目标用户 ID(默认为当前登录用户)' },
11
+ { name: 'uid', positional: true, required: false, help: '目标用户 ID(默认为当前登录用户)' },
12
12
  { name: 'page', type: 'int', required: false, default: 1, help: '页码' },
13
13
  { name: 'limit', type: 'int', required: false, default: 50, help: '每页数量 (最大 50)' },
14
14
  ],
@@ -8,7 +8,7 @@ cli({
8
8
  description: '获取 Bilibili 视频的字幕',
9
9
  strategy: Strategy.COOKIE,
10
10
  args: [
11
- { name: 'bvid', required: true },
11
+ { name: 'bvid', required: true, positional: true },
12
12
  { name: 'lang', required: false, help: '字幕语言代码 (如 zh-CN, en-US, ai-zh),默认取第一个' },
13
13
  ],
14
14
  columns: ['index', 'from', 'to', 'content'],
@@ -8,7 +8,7 @@ cli({
8
8
  domain: 'www.bilibili.com',
9
9
  strategy: Strategy.COOKIE,
10
10
  args: [
11
- { name: 'uid', required: true, help: 'User UID or username' },
11
+ { name: 'uid', required: true, positional: true, help: 'User UID or username' },
12
12
  { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
13
13
  { name: 'order', default: 'pubdate', help: 'Sort: pubdate, click, stow' },
14
14
  { name: 'page', type: 'int', default: 1, help: 'Page number' },