@jackwener/opencli 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/.github/workflows/build-extension.yml +3 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/doc-check.yml +3 -3
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +3 -3
  7. package/.github/workflows/security.yml +2 -2
  8. package/CONTRIBUTING.md +39 -1
  9. package/README.md +13 -10
  10. package/README.zh-CN.md +43 -17
  11. package/SKILL.md +10 -5
  12. package/dist/browser/cdp.d.ts +4 -4
  13. package/dist/browser/cdp.js +39 -16
  14. package/dist/browser/daemon-client.d.ts +4 -2
  15. package/dist/browser/daemon-client.js +17 -4
  16. package/dist/browser/dom-helpers.js +38 -7
  17. package/dist/browser/dom-snapshot.d.ts +86 -0
  18. package/dist/browser/dom-snapshot.js +729 -0
  19. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  20. package/dist/browser/dom-snapshot.test.js +212 -0
  21. package/dist/browser/index.d.ts +2 -0
  22. package/dist/browser/index.js +1 -0
  23. package/dist/browser/mcp.js +3 -1
  24. package/dist/browser/page.d.ts +14 -24
  25. package/dist/browser/page.js +46 -6
  26. package/dist/build-manifest.d.ts +11 -4
  27. package/dist/build-manifest.js +59 -21
  28. package/dist/build-manifest.test.js +58 -2
  29. package/dist/cli-manifest.json +3856 -1509
  30. package/dist/cli.js +66 -0
  31. package/dist/clis/barchart/greeks.js +1 -1
  32. package/dist/clis/barchart/options.js +1 -1
  33. package/dist/clis/barchart/quote.js +1 -1
  34. package/dist/clis/bilibili/download.js +1 -1
  35. package/dist/clis/bilibili/following.js +1 -1
  36. package/dist/clis/bilibili/subtitle.js +1 -1
  37. package/dist/clis/bilibili/user-videos.js +1 -1
  38. package/dist/clis/boss/batchgreet.js +10 -97
  39. package/dist/clis/boss/chatlist.js +8 -25
  40. package/dist/clis/boss/chatmsg.js +11 -42
  41. package/dist/clis/boss/common.d.ts +92 -0
  42. package/dist/clis/boss/common.js +223 -0
  43. package/dist/clis/boss/detail.js +7 -49
  44. package/dist/clis/boss/exchange.js +13 -79
  45. package/dist/clis/boss/greet.js +18 -145
  46. package/dist/clis/boss/invite.js +26 -121
  47. package/dist/clis/boss/joblist.js +6 -31
  48. package/dist/clis/boss/mark.js +12 -85
  49. package/dist/clis/boss/recommend.js +10 -49
  50. package/dist/clis/boss/resume.js +18 -118
  51. package/dist/clis/boss/search.js +12 -60
  52. package/dist/clis/boss/send.js +17 -151
  53. package/dist/clis/boss/stats.js +18 -69
  54. package/dist/clis/coupang/add-to-cart.js +1 -1
  55. package/dist/clis/devto/tag.yaml +34 -0
  56. package/dist/clis/devto/top.yaml +29 -0
  57. package/dist/clis/devto/user.yaml +33 -0
  58. package/dist/clis/douban/book-hot.d.ts +1 -0
  59. package/dist/clis/douban/book-hot.js +14 -0
  60. package/dist/clis/douban/marks.d.ts +1 -0
  61. package/dist/clis/douban/marks.js +115 -0
  62. package/dist/clis/douban/movie-hot.d.ts +1 -0
  63. package/dist/clis/douban/movie-hot.js +14 -0
  64. package/dist/clis/douban/reviews.d.ts +1 -0
  65. package/dist/clis/douban/reviews.js +106 -0
  66. package/dist/clis/douban/search.d.ts +1 -0
  67. package/dist/clis/douban/search.js +16 -0
  68. package/dist/clis/douban/shared.d.ts +4 -0
  69. package/dist/clis/douban/shared.js +155 -0
  70. package/dist/clis/douban/subject.yaml +76 -0
  71. package/dist/clis/douban/top250.yaml +70 -0
  72. package/dist/clis/douban/utils.d.ts +35 -0
  73. package/dist/clis/douban/utils.js +48 -0
  74. package/dist/clis/facebook/add-friend.yaml +43 -0
  75. package/dist/clis/facebook/events.yaml +44 -0
  76. package/dist/clis/facebook/feed.yaml +63 -0
  77. package/dist/clis/facebook/friends.yaml +42 -0
  78. package/dist/clis/facebook/groups.yaml +50 -0
  79. package/dist/clis/facebook/join-group.yaml +44 -0
  80. package/dist/clis/facebook/memories.yaml +39 -0
  81. package/dist/clis/facebook/notifications.yaml +40 -0
  82. package/dist/clis/facebook/profile.yaml +37 -0
  83. package/dist/clis/facebook/search.yaml +46 -0
  84. package/dist/clis/google/news.d.ts +5 -0
  85. package/dist/clis/google/news.js +58 -0
  86. package/dist/clis/google/search.d.ts +10 -0
  87. package/dist/clis/google/search.js +127 -0
  88. package/dist/clis/google/suggest.d.ts +5 -0
  89. package/dist/clis/google/suggest.js +34 -0
  90. package/dist/clis/google/trends.d.ts +5 -0
  91. package/dist/clis/google/trends.js +38 -0
  92. package/dist/clis/google/utils.d.ts +9 -0
  93. package/dist/clis/google/utils.js +23 -0
  94. package/dist/clis/google/utils.test.d.ts +1 -0
  95. package/dist/clis/google/utils.test.js +75 -0
  96. package/dist/clis/grok/ask.d.ts +14 -0
  97. package/dist/clis/grok/ask.js +257 -65
  98. package/dist/clis/grok/ask.test.d.ts +1 -0
  99. package/dist/clis/grok/ask.test.js +36 -0
  100. package/dist/clis/instagram/comment.yaml +52 -0
  101. package/dist/clis/instagram/explore.yaml +43 -0
  102. package/dist/clis/instagram/follow.yaml +41 -0
  103. package/dist/clis/instagram/followers.yaml +51 -0
  104. package/dist/clis/instagram/following.yaml +51 -0
  105. package/dist/clis/instagram/like.yaml +46 -0
  106. package/dist/clis/instagram/profile.yaml +42 -0
  107. package/dist/clis/instagram/save.yaml +46 -0
  108. package/dist/clis/instagram/saved.yaml +40 -0
  109. package/dist/clis/instagram/search.yaml +43 -0
  110. package/dist/clis/instagram/unfollow.yaml +38 -0
  111. package/dist/clis/instagram/unlike.yaml +46 -0
  112. package/dist/clis/instagram/unsave.yaml +46 -0
  113. package/dist/clis/instagram/user.yaml +54 -0
  114. package/dist/clis/jike/repost.js +1 -1
  115. package/dist/clis/jimeng/generate.yaml +1 -0
  116. package/dist/clis/linux-do/category.yaml +1 -0
  117. package/dist/clis/lobsters/active.yaml +29 -0
  118. package/dist/clis/lobsters/hot.yaml +29 -0
  119. package/dist/clis/lobsters/newest.yaml +29 -0
  120. package/dist/clis/lobsters/tag.yaml +34 -0
  121. package/dist/clis/medium/feed.d.ts +1 -0
  122. package/dist/clis/medium/feed.js +15 -0
  123. package/dist/clis/medium/search.d.ts +1 -0
  124. package/dist/clis/medium/search.js +15 -0
  125. package/dist/clis/medium/shared.d.ts +5 -0
  126. package/dist/clis/medium/shared.js +78 -0
  127. package/dist/clis/medium/user.d.ts +1 -0
  128. package/dist/clis/medium/user.js +15 -0
  129. package/dist/clis/reddit/comment.js +1 -1
  130. package/dist/clis/reddit/read.js +1 -1
  131. package/dist/clis/reddit/save.js +1 -1
  132. package/dist/clis/reddit/subreddit.yaml +1 -0
  133. package/dist/clis/reddit/subscribe.js +1 -1
  134. package/dist/clis/reddit/upvote.js +1 -1
  135. package/dist/clis/sinablog/article.d.ts +1 -0
  136. package/dist/clis/sinablog/article.js +14 -0
  137. package/dist/clis/sinablog/hot.d.ts +1 -0
  138. package/dist/clis/sinablog/hot.js +14 -0
  139. package/dist/clis/sinablog/search.d.ts +1 -0
  140. package/dist/clis/sinablog/search.js +51 -0
  141. package/dist/clis/sinablog/shared.d.ts +7 -0
  142. package/dist/clis/sinablog/shared.js +187 -0
  143. package/dist/clis/sinablog/user.d.ts +1 -0
  144. package/dist/clis/sinablog/user.js +15 -0
  145. package/dist/clis/substack/feed.d.ts +1 -0
  146. package/dist/clis/substack/feed.js +15 -0
  147. package/dist/clis/substack/publication.d.ts +1 -0
  148. package/dist/clis/substack/publication.js +15 -0
  149. package/dist/clis/substack/search.d.ts +1 -0
  150. package/dist/clis/substack/search.js +77 -0
  151. package/dist/clis/substack/shared.d.ts +4 -0
  152. package/dist/clis/substack/shared.js +129 -0
  153. package/dist/clis/tiktok/comment.yaml +66 -0
  154. package/dist/clis/tiktok/explore.yaml +39 -0
  155. package/dist/clis/tiktok/follow.yaml +39 -0
  156. package/dist/clis/tiktok/following.yaml +46 -0
  157. package/dist/clis/tiktok/friends.yaml +47 -0
  158. package/dist/clis/tiktok/like.yaml +38 -0
  159. package/dist/clis/tiktok/live.yaml +51 -0
  160. package/dist/clis/tiktok/notifications.yaml +52 -0
  161. package/dist/clis/tiktok/profile.yaml +45 -0
  162. package/dist/clis/tiktok/save.yaml +34 -0
  163. package/dist/clis/tiktok/search.yaml +46 -0
  164. package/dist/clis/tiktok/unfollow.yaml +44 -0
  165. package/dist/clis/tiktok/unlike.yaml +38 -0
  166. package/dist/clis/tiktok/unsave.yaml +36 -0
  167. package/dist/clis/tiktok/user.yaml +44 -0
  168. package/dist/clis/twitter/download.d.ts +1 -1
  169. package/dist/clis/twitter/download.js +3 -3
  170. package/dist/clis/twitter/followers.js +1 -1
  171. package/dist/clis/twitter/following.js +1 -1
  172. package/dist/clis/twitter/thread.js +1 -1
  173. package/dist/clis/twitter/timeline.d.ts +23 -0
  174. package/dist/clis/twitter/timeline.js +42 -14
  175. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  176. package/dist/clis/twitter/timeline.test.js +102 -0
  177. package/dist/clis/wikipedia/random.d.ts +1 -0
  178. package/dist/clis/wikipedia/random.js +19 -0
  179. package/dist/clis/wikipedia/search.js +3 -3
  180. package/dist/clis/wikipedia/summary.js +4 -9
  181. package/dist/clis/wikipedia/trending.d.ts +1 -0
  182. package/dist/clis/wikipedia/trending.js +35 -0
  183. package/dist/clis/wikipedia/utils.d.ts +28 -0
  184. package/dist/clis/wikipedia/utils.js +13 -0
  185. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  186. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  187. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  188. package/dist/clis/xiaohongshu/download.js +1 -1
  189. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  190. package/dist/clis/xueqiu/search.yaml +2 -1
  191. package/dist/clis/xueqiu/stock.yaml +2 -0
  192. package/dist/clis/yahoo-finance/quote.js +1 -1
  193. package/dist/commanderAdapter.js +13 -7
  194. package/dist/daemon.js +21 -0
  195. package/dist/discovery.d.ts +8 -0
  196. package/dist/discovery.js +105 -19
  197. package/dist/doctor.js +3 -1
  198. package/dist/doctor.test.js +46 -2
  199. package/dist/engine.test.d.ts +0 -3
  200. package/dist/engine.test.js +74 -6
  201. package/dist/execution.d.ts +4 -2
  202. package/dist/execution.js +31 -7
  203. package/dist/explore.d.ts +76 -3
  204. package/dist/explore.js +11 -4
  205. package/dist/generate.d.ts +41 -2
  206. package/dist/generate.js +5 -4
  207. package/dist/main.js +2 -1
  208. package/dist/pipeline/executor.d.ts +4 -2
  209. package/dist/pipeline/executor.js +54 -15
  210. package/dist/pipeline/executor.test.js +33 -6
  211. package/dist/pipeline/registry.d.ts +1 -1
  212. package/dist/pipeline/steps/browser.d.ts +7 -7
  213. package/dist/pipeline/steps/browser.js +15 -7
  214. package/dist/pipeline/steps/fetch.d.ts +1 -1
  215. package/dist/pipeline/steps/fetch.js +11 -7
  216. package/dist/pipeline/steps/transform.d.ts +6 -5
  217. package/dist/pipeline/steps/transform.js +30 -9
  218. package/dist/pipeline/template.d.ts +6 -6
  219. package/dist/pipeline/template.js +43 -5
  220. package/dist/pipeline/template.test.js +18 -0
  221. package/dist/pipeline/transform.test.js +11 -0
  222. package/dist/plugin.d.ts +31 -0
  223. package/dist/plugin.js +216 -0
  224. package/dist/plugin.test.d.ts +4 -0
  225. package/dist/plugin.test.js +76 -0
  226. package/dist/registry-api.d.ts +11 -0
  227. package/dist/registry-api.js +9 -0
  228. package/dist/registry.d.ts +11 -0
  229. package/dist/registry.js +6 -1
  230. package/dist/synthesize.d.ts +94 -4
  231. package/dist/synthesize.js +5 -4
  232. package/dist/types.d.ts +39 -26
  233. package/dist/validate.js +8 -2
  234. package/docs/.vitepress/config.mts +6 -4
  235. package/docs/adapters/browser/barchart.md +6 -5
  236. package/docs/adapters/browser/bilibili.md +9 -0
  237. package/docs/adapters/browser/devto.md +35 -0
  238. package/docs/adapters/browser/douban.md +38 -0
  239. package/docs/adapters/browser/facebook.md +36 -0
  240. package/docs/adapters/browser/google.md +62 -0
  241. package/docs/adapters/browser/grok.md +26 -8
  242. package/docs/adapters/browser/instagram.md +46 -0
  243. package/docs/adapters/browser/lobsters.md +32 -0
  244. package/docs/adapters/browser/medium.md +32 -0
  245. package/docs/adapters/browser/reddit.md +9 -0
  246. package/docs/adapters/browser/sinablog.md +36 -0
  247. package/docs/adapters/browser/substack.md +38 -0
  248. package/docs/adapters/browser/tiktok.md +68 -0
  249. package/docs/adapters/browser/wikipedia.md +11 -2
  250. package/docs/adapters/browser/xueqiu.md +10 -0
  251. package/docs/adapters/browser/yahoo-finance.md +6 -5
  252. package/docs/adapters/desktop/antigravity.md +6 -0
  253. package/docs/adapters/desktop/chatgpt.md +2 -1
  254. package/docs/adapters/desktop/codex.md +5 -1
  255. package/docs/adapters/desktop/cursor.md +4 -0
  256. package/docs/adapters/desktop/discord.md +7 -7
  257. package/docs/adapters/index.md +1 -4
  258. package/docs/guide/getting-started.md +1 -0
  259. package/docs/guide/plugins.md +153 -0
  260. package/docs/zh/guide/plugins.md +107 -0
  261. package/extension/dist/background.js +91 -23
  262. package/extension/src/background.ts +82 -29
  263. package/extension/src/cdp.ts +42 -1
  264. package/package.json +10 -5
  265. package/scripts/clean-dist.cjs +13 -0
  266. package/src/browser/cdp.ts +71 -31
  267. package/src/browser/daemon-client.ts +21 -5
  268. package/src/browser/dom-helpers.ts +38 -7
  269. package/src/browser/dom-snapshot.test.ts +249 -0
  270. package/src/browser/dom-snapshot.ts +770 -0
  271. package/src/browser/index.ts +2 -0
  272. package/src/browser/mcp.ts +3 -1
  273. package/src/browser/page.ts +57 -21
  274. package/src/build-manifest.test.ts +70 -2
  275. package/src/build-manifest.ts +94 -26
  276. package/src/cli.ts +71 -2
  277. package/src/clis/barchart/greeks.ts +1 -1
  278. package/src/clis/barchart/options.ts +1 -1
  279. package/src/clis/barchart/quote.ts +1 -1
  280. package/src/clis/bilibili/download.ts +1 -1
  281. package/src/clis/bilibili/following.ts +1 -1
  282. package/src/clis/bilibili/subtitle.ts +1 -1
  283. package/src/clis/bilibili/user-videos.ts +1 -1
  284. package/src/clis/boss/batchgreet.ts +14 -106
  285. package/src/clis/boss/chatlist.ts +12 -26
  286. package/src/clis/boss/chatmsg.ts +16 -40
  287. package/src/clis/boss/common.ts +287 -0
  288. package/src/clis/boss/detail.ts +8 -54
  289. package/src/clis/boss/exchange.ts +15 -89
  290. package/src/clis/boss/greet.ts +23 -160
  291. package/src/clis/boss/invite.ts +36 -133
  292. package/src/clis/boss/joblist.ts +7 -36
  293. package/src/clis/boss/mark.ts +13 -94
  294. package/src/clis/boss/recommend.ts +12 -57
  295. package/src/clis/boss/resume.ts +19 -124
  296. package/src/clis/boss/search.ts +13 -66
  297. package/src/clis/boss/send.ts +21 -161
  298. package/src/clis/boss/stats.ts +19 -74
  299. package/src/clis/coupang/add-to-cart.ts +1 -1
  300. package/src/clis/devto/tag.yaml +34 -0
  301. package/src/clis/devto/top.yaml +29 -0
  302. package/src/clis/devto/user.yaml +33 -0
  303. package/src/clis/douban/book-hot.ts +15 -0
  304. package/src/clis/douban/marks.ts +135 -0
  305. package/src/clis/douban/movie-hot.ts +15 -0
  306. package/src/clis/douban/reviews.ts +127 -0
  307. package/src/clis/douban/search.ts +17 -0
  308. package/src/clis/douban/shared.ts +165 -0
  309. package/src/clis/douban/subject.yaml +76 -0
  310. package/src/clis/douban/top250.yaml +70 -0
  311. package/src/clis/douban/utils.ts +81 -0
  312. package/src/clis/facebook/add-friend.yaml +43 -0
  313. package/src/clis/facebook/events.yaml +44 -0
  314. package/src/clis/facebook/feed.yaml +63 -0
  315. package/src/clis/facebook/friends.yaml +42 -0
  316. package/src/clis/facebook/groups.yaml +50 -0
  317. package/src/clis/facebook/join-group.yaml +44 -0
  318. package/src/clis/facebook/memories.yaml +39 -0
  319. package/src/clis/facebook/notifications.yaml +40 -0
  320. package/src/clis/facebook/profile.yaml +37 -0
  321. package/src/clis/facebook/search.yaml +46 -0
  322. package/src/clis/google/news.ts +66 -0
  323. package/src/clis/google/search.ts +133 -0
  324. package/src/clis/google/suggest.ts +40 -0
  325. package/src/clis/google/trends.ts +44 -0
  326. package/src/clis/google/utils.test.ts +82 -0
  327. package/src/clis/google/utils.ts +24 -0
  328. package/src/clis/grok/ask.test.ts +53 -0
  329. package/src/clis/grok/ask.ts +300 -69
  330. package/src/clis/instagram/comment.yaml +52 -0
  331. package/src/clis/instagram/explore.yaml +43 -0
  332. package/src/clis/instagram/follow.yaml +41 -0
  333. package/src/clis/instagram/followers.yaml +51 -0
  334. package/src/clis/instagram/following.yaml +51 -0
  335. package/src/clis/instagram/like.yaml +46 -0
  336. package/src/clis/instagram/profile.yaml +42 -0
  337. package/src/clis/instagram/save.yaml +46 -0
  338. package/src/clis/instagram/saved.yaml +40 -0
  339. package/src/clis/instagram/search.yaml +43 -0
  340. package/src/clis/instagram/unfollow.yaml +38 -0
  341. package/src/clis/instagram/unlike.yaml +46 -0
  342. package/src/clis/instagram/unsave.yaml +46 -0
  343. package/src/clis/instagram/user.yaml +54 -0
  344. package/src/clis/jike/repost.ts +1 -1
  345. package/src/clis/jimeng/generate.yaml +1 -0
  346. package/src/clis/linux-do/category.yaml +1 -0
  347. package/src/clis/lobsters/active.yaml +29 -0
  348. package/src/clis/lobsters/hot.yaml +29 -0
  349. package/src/clis/lobsters/newest.yaml +29 -0
  350. package/src/clis/lobsters/tag.yaml +34 -0
  351. package/src/clis/medium/feed.ts +16 -0
  352. package/src/clis/medium/search.ts +16 -0
  353. package/src/clis/medium/shared.ts +83 -0
  354. package/src/clis/medium/user.ts +16 -0
  355. package/src/clis/reddit/comment.ts +1 -1
  356. package/src/clis/reddit/read.ts +1 -1
  357. package/src/clis/reddit/save.ts +1 -1
  358. package/src/clis/reddit/subreddit.yaml +1 -0
  359. package/src/clis/reddit/subscribe.ts +1 -1
  360. package/src/clis/reddit/upvote.ts +1 -1
  361. package/src/clis/sinablog/article.ts +15 -0
  362. package/src/clis/sinablog/hot.ts +15 -0
  363. package/src/clis/sinablog/search.ts +56 -0
  364. package/src/clis/sinablog/shared.ts +198 -0
  365. package/src/clis/sinablog/user.ts +16 -0
  366. package/src/clis/substack/feed.ts +16 -0
  367. package/src/clis/substack/publication.ts +16 -0
  368. package/src/clis/substack/search.ts +91 -0
  369. package/src/clis/substack/shared.ts +132 -0
  370. package/src/clis/tiktok/comment.yaml +66 -0
  371. package/src/clis/tiktok/explore.yaml +39 -0
  372. package/src/clis/tiktok/follow.yaml +39 -0
  373. package/src/clis/tiktok/following.yaml +46 -0
  374. package/src/clis/tiktok/friends.yaml +47 -0
  375. package/src/clis/tiktok/like.yaml +38 -0
  376. package/src/clis/tiktok/live.yaml +51 -0
  377. package/src/clis/tiktok/notifications.yaml +52 -0
  378. package/src/clis/tiktok/profile.yaml +45 -0
  379. package/src/clis/tiktok/save.yaml +34 -0
  380. package/src/clis/tiktok/search.yaml +46 -0
  381. package/src/clis/tiktok/unfollow.yaml +44 -0
  382. package/src/clis/tiktok/unlike.yaml +38 -0
  383. package/src/clis/tiktok/unsave.yaml +36 -0
  384. package/src/clis/tiktok/user.yaml +44 -0
  385. package/src/clis/twitter/download.ts +3 -3
  386. package/src/clis/twitter/followers.ts +1 -1
  387. package/src/clis/twitter/following.ts +1 -1
  388. package/src/clis/twitter/thread.ts +1 -1
  389. package/src/clis/twitter/timeline.test.ts +109 -0
  390. package/src/clis/twitter/timeline.ts +59 -19
  391. package/src/clis/wikipedia/random.ts +19 -0
  392. package/src/clis/wikipedia/search.ts +10 -4
  393. package/src/clis/wikipedia/summary.ts +4 -9
  394. package/src/clis/wikipedia/trending.ts +41 -0
  395. package/src/clis/wikipedia/utils.ts +31 -0
  396. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  397. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  398. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  399. package/src/clis/xiaohongshu/download.ts +1 -1
  400. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  401. package/src/clis/xueqiu/search.yaml +2 -1
  402. package/src/clis/xueqiu/stock.yaml +2 -0
  403. package/src/clis/yahoo-finance/quote.ts +1 -1
  404. package/src/commanderAdapter.ts +17 -10
  405. package/src/daemon.ts +23 -0
  406. package/src/discovery.ts +134 -24
  407. package/src/doctor.test.ts +59 -2
  408. package/src/doctor.ts +4 -2
  409. package/src/engine.test.ts +79 -6
  410. package/src/execution.ts +42 -16
  411. package/src/explore.ts +77 -9
  412. package/src/generate.ts +58 -9
  413. package/src/main.ts +2 -1
  414. package/src/pipeline/executor.test.ts +35 -6
  415. package/src/pipeline/executor.ts +68 -19
  416. package/src/pipeline/registry.ts +3 -3
  417. package/src/pipeline/steps/browser.ts +24 -15
  418. package/src/pipeline/steps/fetch.ts +18 -13
  419. package/src/pipeline/steps/transform.ts +40 -15
  420. package/src/pipeline/template.test.ts +18 -0
  421. package/src/pipeline/template.ts +86 -13
  422. package/src/pipeline/transform.test.ts +15 -2
  423. package/src/plugin.test.ts +86 -0
  424. package/src/plugin.ts +254 -0
  425. package/src/registry-api.ts +12 -0
  426. package/src/registry.ts +19 -1
  427. package/src/synthesize.ts +102 -21
  428. package/src/types.ts +44 -12
  429. package/src/validate.ts +19 -4
  430. package/tests/e2e/browser-public.test.ts +11 -0
  431. package/tests/e2e/public-commands.test.ts +64 -0
  432. package/dist/clis/feishu/new.d.ts +0 -1
  433. package/dist/clis/feishu/new.js +0 -27
  434. package/dist/clis/feishu/read.d.ts +0 -1
  435. package/dist/clis/feishu/read.js +0 -40
  436. package/dist/clis/feishu/search.d.ts +0 -1
  437. package/dist/clis/feishu/search.js +0 -30
  438. package/dist/clis/feishu/send.d.ts +0 -1
  439. package/dist/clis/feishu/send.js +0 -39
  440. package/dist/clis/feishu/status.d.ts +0 -1
  441. package/dist/clis/feishu/status.js +0 -28
  442. package/dist/clis/neteasemusic/like.d.ts +0 -1
  443. package/dist/clis/neteasemusic/like.js +0 -25
  444. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  445. package/dist/clis/neteasemusic/lyrics.js +0 -47
  446. package/dist/clis/neteasemusic/next.d.ts +0 -1
  447. package/dist/clis/neteasemusic/next.js +0 -26
  448. package/dist/clis/neteasemusic/play.d.ts +0 -1
  449. package/dist/clis/neteasemusic/play.js +0 -26
  450. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  451. package/dist/clis/neteasemusic/playing.js +0 -59
  452. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  453. package/dist/clis/neteasemusic/playlist.js +0 -46
  454. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  455. package/dist/clis/neteasemusic/prev.js +0 -25
  456. package/dist/clis/neteasemusic/search.d.ts +0 -1
  457. package/dist/clis/neteasemusic/search.js +0 -52
  458. package/dist/clis/neteasemusic/status.d.ts +0 -1
  459. package/dist/clis/neteasemusic/status.js +0 -16
  460. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  461. package/dist/clis/neteasemusic/volume.js +0 -54
  462. package/dist/clis/wechat/chats.d.ts +0 -1
  463. package/dist/clis/wechat/chats.js +0 -28
  464. package/dist/clis/wechat/contacts.d.ts +0 -1
  465. package/dist/clis/wechat/contacts.js +0 -28
  466. package/dist/clis/wechat/read.d.ts +0 -1
  467. package/dist/clis/wechat/read.js +0 -58
  468. package/dist/clis/wechat/search.d.ts +0 -1
  469. package/dist/clis/wechat/search.js +0 -31
  470. package/dist/clis/wechat/send.d.ts +0 -1
  471. package/dist/clis/wechat/send.js +0 -42
  472. package/dist/clis/wechat/status.d.ts +0 -1
  473. package/dist/clis/wechat/status.js +0 -29
  474. package/dist/pipeline.d.ts +0 -7
  475. package/dist/pipeline.js +0 -7
  476. package/docs/adapters/browser/github.md +0 -26
  477. package/docs/adapters/desktop/feishu.md +0 -20
  478. package/docs/adapters/desktop/neteasemusic.md +0 -31
  479. package/docs/adapters/desktop/wechat.md +0 -28
  480. package/src/clis/antigravity/README.md +0 -5
  481. package/src/clis/antigravity/README.zh-CN.md +0 -51
  482. package/src/clis/chaoxing/README.md +0 -14
  483. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  484. package/src/clis/chatgpt/README.md +0 -5
  485. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  486. package/src/clis/chatwise/README.md +0 -5
  487. package/src/clis/chatwise/README.zh-CN.md +0 -38
  488. package/src/clis/codex/README.md +0 -5
  489. package/src/clis/codex/README.zh-CN.md +0 -33
  490. package/src/clis/cursor/README.md +0 -5
  491. package/src/clis/cursor/README.zh-CN.md +0 -33
  492. package/src/clis/discord-app/README.md +0 -5
  493. package/src/clis/discord-app/README.zh-CN.md +0 -28
  494. package/src/clis/feishu/README.md +0 -5
  495. package/src/clis/feishu/README.zh-CN.md +0 -20
  496. package/src/clis/feishu/new.ts +0 -32
  497. package/src/clis/feishu/read.ts +0 -48
  498. package/src/clis/feishu/search.ts +0 -35
  499. package/src/clis/feishu/send.ts +0 -46
  500. package/src/clis/feishu/status.ts +0 -34
  501. package/src/clis/neteasemusic/README.md +0 -5
  502. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  503. package/src/clis/neteasemusic/like.ts +0 -28
  504. package/src/clis/neteasemusic/lyrics.ts +0 -53
  505. package/src/clis/neteasemusic/next.ts +0 -30
  506. package/src/clis/neteasemusic/play.ts +0 -30
  507. package/src/clis/neteasemusic/playing.ts +0 -62
  508. package/src/clis/neteasemusic/playlist.ts +0 -51
  509. package/src/clis/neteasemusic/prev.ts +0 -29
  510. package/src/clis/neteasemusic/search.ts +0 -58
  511. package/src/clis/neteasemusic/status.ts +0 -18
  512. package/src/clis/neteasemusic/volume.ts +0 -61
  513. package/src/clis/notion/README.md +0 -5
  514. package/src/clis/notion/README.zh-CN.md +0 -29
  515. package/src/clis/wechat/README.md +0 -5
  516. package/src/clis/wechat/README.zh-CN.md +0 -28
  517. package/src/clis/wechat/chats.ts +0 -33
  518. package/src/clis/wechat/contacts.ts +0 -33
  519. package/src/clis/wechat/read.ts +0 -72
  520. package/src/clis/wechat/search.ts +0 -36
  521. package/src/clis/wechat/send.ts +0 -49
  522. package/src/clis/wechat/status.ts +0 -35
  523. package/src/pipeline.ts +0 -8
@@ -15,6 +15,9 @@ import { formatRegistryHelpText } from './serialization.js';
15
15
  import { render as renderOutput } from './output.js';
16
16
  import { executeCommand } from './execution.js';
17
17
  import { CliError } from './errors.js';
18
+ function getErrorMessage(error) {
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
18
21
  /**
19
22
  * Register a single CliCommand as a Commander subcommand.
20
23
  */
@@ -46,6 +49,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
46
49
  subCmd.addHelpText('after', formatRegistryHelpText(cmd));
47
50
  subCmd.action(async (...actionArgs) => {
48
51
  const actionOpts = actionArgs[positionalArgs.length] ?? {};
52
+ const optionsRecord = typeof actionOpts === 'object' && actionOpts !== null ? actionOpts : {};
49
53
  const startTime = Date.now();
50
54
  // ── Collect kwargs ──────────────────────────────────────────────────
51
55
  const kwargs = {};
@@ -58,21 +62,23 @@ export function registerCommandToProgram(siteCmd, cmd) {
58
62
  if (arg.positional)
59
63
  continue;
60
64
  const camelName = arg.name.replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
61
- const v = actionOpts[arg.name] ?? actionOpts[camelName];
65
+ const v = optionsRecord[arg.name] ?? optionsRecord[camelName];
62
66
  if (v !== undefined)
63
67
  kwargs[arg.name] = v;
64
68
  }
65
69
  // ── Execute + render ────────────────────────────────────────────────
66
70
  try {
67
- if (actionOpts.verbose)
71
+ const verbose = optionsRecord.verbose === true;
72
+ const format = typeof optionsRecord.format === 'string' ? optionsRecord.format : 'table';
73
+ if (verbose)
68
74
  process.env.OPENCLI_VERBOSE = '1';
69
- const result = await executeCommand(cmd, kwargs, actionOpts.verbose);
70
- if (actionOpts.verbose && (!result || (Array.isArray(result) && result.length === 0))) {
75
+ const result = await executeCommand(cmd, kwargs, verbose);
76
+ if (verbose && (!result || (Array.isArray(result) && result.length === 0))) {
71
77
  console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
72
78
  }
73
79
  const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
74
80
  renderOutput(result, {
75
- fmt: actionOpts.format,
81
+ fmt: format,
76
82
  columns: resolved.columns,
77
83
  title: `${resolved.site}/${resolved.name}`,
78
84
  elapsed: (Date.now() - startTime) / 1000,
@@ -86,11 +92,11 @@ export function registerCommandToProgram(siteCmd, cmd) {
86
92
  if (err.hint)
87
93
  console.error(chalk.yellow(`Hint: ${err.hint}`));
88
94
  }
89
- else if (actionOpts.verbose && err.stack) {
95
+ else if (optionsRecord.verbose === true && err instanceof Error && err.stack) {
90
96
  console.error(chalk.red(err.stack));
91
97
  }
92
98
  else {
93
- console.error(chalk.red(`Error: ${err.message ?? err}`));
99
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
94
100
  }
95
101
  process.exitCode = 1;
96
102
  }
package/dist/daemon.js CHANGED
@@ -118,6 +118,25 @@ const wss = new WebSocketServer({ server: httpServer, path: '/ext' });
118
118
  wss.on('connection', (ws) => {
119
119
  console.error('[daemon] Extension connected');
120
120
  extensionWs = ws;
121
+ // ── Heartbeat: ping every 15s, close if 2 pongs missed ──
122
+ let missedPongs = 0;
123
+ const heartbeatInterval = setInterval(() => {
124
+ if (ws.readyState !== WebSocket.OPEN) {
125
+ clearInterval(heartbeatInterval);
126
+ return;
127
+ }
128
+ if (missedPongs >= 2) {
129
+ console.error('[daemon] Extension heartbeat lost, closing connection');
130
+ clearInterval(heartbeatInterval);
131
+ ws.terminate();
132
+ return;
133
+ }
134
+ missedPongs++;
135
+ ws.ping();
136
+ }, 15000);
137
+ ws.on('pong', () => {
138
+ missedPongs = 0;
139
+ });
121
140
  ws.on('message', (data) => {
122
141
  try {
123
142
  const msg = JSON.parse(data.toString());
@@ -142,6 +161,7 @@ wss.on('connection', (ws) => {
142
161
  });
143
162
  ws.on('close', () => {
144
163
  console.error('[daemon] Extension disconnected');
164
+ clearInterval(heartbeatInterval);
145
165
  if (extensionWs === ws) {
146
166
  extensionWs = null;
147
167
  // Reject all pending requests since the extension is gone
@@ -153,6 +173,7 @@ wss.on('connection', (ws) => {
153
173
  }
154
174
  });
155
175
  ws.on('error', () => {
176
+ clearInterval(heartbeatInterval);
156
177
  if (extensionWs === ws)
157
178
  extensionWs = null;
158
179
  });
@@ -7,8 +7,16 @@
7
7
  * TS modules are loaded lazily only when their command is executed.
8
8
  * 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
9
9
  */
10
+ /** Plugins directory: ~/.opencli/plugins/ */
11
+ export declare const PLUGINS_DIR: string;
10
12
  /**
11
13
  * Discover and register CLI commands.
12
14
  * Uses pre-compiled manifest when available for instant startup.
13
15
  */
14
16
  export declare function discoverClis(...dirs: string[]): Promise<void>;
17
+ /**
18
+ * Discover and register plugins from ~/.opencli/plugins/.
19
+ * Each subdirectory is treated as a plugin (site = directory name).
20
+ * Files inside are scanned flat (no nested site subdirs).
21
+ */
22
+ export declare function discoverPlugins(): Promise<void>;
package/dist/discovery.js CHANGED
@@ -8,10 +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
16
  import { log } from './logger.js';
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
+ }
15
32
  /**
16
33
  * Discover and register CLI commands.
17
34
  * Uses pre-compiled manifest when available for instant startup.
@@ -43,7 +60,7 @@ async function loadFromManifest(manifestPath, clisDir) {
43
60
  for (const entry of manifest) {
44
61
  if (entry.type === 'yaml') {
45
62
  // YAML pipelines fully inlined in manifest — register directly
46
- const strategy = Strategy[entry.strategy.toUpperCase()] ?? Strategy.COOKIE;
63
+ const strategy = parseStrategy(entry.strategy);
47
64
  const cmd = {
48
65
  site: entry.site,
49
66
  name: entry.name,
@@ -56,13 +73,14 @@ async function loadFromManifest(manifestPath, clisDir) {
56
73
  pipeline: entry.pipeline,
57
74
  timeoutSeconds: entry.timeout,
58
75
  source: `manifest:${entry.site}/${entry.name}`,
76
+ navigateBefore: entry.navigateBefore,
59
77
  };
60
78
  registerCommand(cmd);
61
79
  }
62
80
  else if (entry.type === 'ts' && entry.modulePath) {
63
81
  // TS adapters: register a lightweight stub.
64
82
  // The actual module is loaded lazily on first executeCommand().
65
- const strategy = Strategy[(entry.strategy ?? 'cookie').toUpperCase()] ?? Strategy.COOKIE;
83
+ const strategy = parseStrategy(entry.strategy ?? 'cookie');
66
84
  const modulePath = path.resolve(clisDir, entry.modulePath);
67
85
  const cmd = {
68
86
  site: entry.site,
@@ -75,6 +93,7 @@ async function loadFromManifest(manifestPath, clisDir) {
75
93
  columns: entry.columns,
76
94
  timeoutSeconds: entry.timeout,
77
95
  source: modulePath,
96
+ navigateBefore: entry.navigateBefore,
78
97
  _lazy: true,
79
98
  _modulePath: modulePath,
80
99
  };
@@ -83,7 +102,7 @@ async function loadFromManifest(manifestPath, clisDir) {
83
102
  }
84
103
  }
85
104
  catch (err) {
86
- log.warn(`Failed to load manifest ${manifestPath}: ${err.message}`);
105
+ log.warn(`Failed to load manifest ${manifestPath}: ${getErrorMessage(err)}`);
87
106
  }
88
107
  }
89
108
  /**
@@ -111,8 +130,10 @@ async function discoverClisFromFs(dir) {
111
130
  }
112
131
  else if ((file.endsWith('.js') && !file.endsWith('.d.js')) ||
113
132
  (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts'))) {
114
- promises.push(import(`file://${filePath}`).catch((err) => {
115
- 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)}`);
116
137
  }));
117
138
  }
118
139
  }
@@ -123,16 +144,17 @@ async function registerYamlCli(filePath, defaultSite) {
123
144
  try {
124
145
  const raw = await fs.promises.readFile(filePath, 'utf-8');
125
146
  const def = yaml.load(raw);
126
- if (!def || typeof def !== 'object')
147
+ if (!isRecord(def))
127
148
  return;
128
- const site = def.site ?? defaultSite;
129
- const name = def.name ?? path.basename(filePath, path.extname(filePath));
130
- const strategyStr = def.strategy ?? (def.browser === false ? 'public' : 'cookie');
131
- const strategy = Strategy[strategyStr.toUpperCase()] ?? Strategy.COOKIE;
132
- 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);
133
155
  const args = [];
134
- if (def.args && typeof def.args === 'object') {
135
- 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)) {
136
158
  args.push({
137
159
  name: argName,
138
160
  type: argDef?.type ?? 'str',
@@ -147,19 +169,83 @@ async function registerYamlCli(filePath, defaultSite) {
147
169
  const cmd = {
148
170
  site,
149
171
  name,
150
- description: def.description ?? '',
151
- domain: def.domain,
172
+ description: cliDef.description ?? '',
173
+ domain: cliDef.domain,
152
174
  strategy,
153
175
  browser,
154
176
  args,
155
- columns: def.columns,
156
- pipeline: def.pipeline,
157
- timeoutSeconds: def.timeout,
177
+ columns: cliDef.columns,
178
+ pipeline: cliDef.pipeline,
179
+ timeoutSeconds: cliDef.timeout,
158
180
  source: filePath,
181
+ navigateBefore: cliDef.navigateBefore,
159
182
  };
160
183
  registerCommand(cmd);
161
184
  }
162
185
  catch (err) {
163
- log.warn(`Failed to load ${filePath}: ${err.message}`);
186
+ log.warn(`Failed to load ${filePath}: ${getErrorMessage(err)}`);
187
+ }
188
+ }
189
+ /**
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).
193
+ */
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);
206
+ }
207
+ }
208
+ /**
209
+ * Flat scan: read yaml/ts files directly in a plugin directory.
210
+ * Unlike discoverClisFromFs, this does NOT expect nested site subdirectories.
211
+ */
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));
220
+ }
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
+ }));
227
+ }
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
+ }));
238
+ }
239
+ }
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);
246
+ }
247
+ catch (err) {
248
+ log.warn(`Failed to inspect module ${filePath}: ${getErrorMessage(err)}`);
249
+ return false;
164
250
  }
165
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
  });
@@ -1,4 +1 @@
1
- /**
2
- * Tests for discovery and execution modules.
3
- */
4
1
  export {};
@@ -1,15 +1,83 @@
1
- /**
2
- * Tests for discovery and execution modules.
3
- */
4
- import { describe, it, expect } from 'vitest';
5
- import { discoverClis } from './discovery.js';
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
6
3
  import { executeCommand } from './execution.js';
7
- import { cli, Strategy } from './registry.js';
4
+ import { getRegistry, cli, Strategy } from './registry.js';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
8
7
  describe('discoverClis', () => {
9
8
  it('handles non-existent directories gracefully', async () => {
10
9
  // Should not throw for missing directories
11
10
  await expect(discoverClis('/tmp/nonexistent-opencli-test-dir')).resolves.not.toThrow();
12
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
+ });
13
81
  });
14
82
  describe('executeCommand', () => {
15
83
  it('accepts kebab-case option names after Commander camelCases them', async () => {
@@ -9,14 +9,16 @@
9
9
  * 5. Lazy-loading of TS modules from manifest
10
10
  */
11
11
  import { type CliCommand, type Arg } from './registry.js';
12
+ type CommandArgs = Record<string, unknown>;
12
13
  /**
13
14
  * Validates and coerces arguments based on the command's Arg definitions.
14
15
  */
15
- export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: Record<string, any>): Record<string, any>;
16
+ export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs;
16
17
  /**
17
18
  * Execute a CLI command. Automatically manages browser sessions when needed.
18
19
  *
19
20
  * This is the unified entry point — callers don't need to care about
20
21
  * whether the command requires a browser or not.
21
22
  */
22
- export declare function executeCommand(cmd: CliCommand, rawKwargs: Record<string, any>, debug?: boolean): Promise<any>;
23
+ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs, debug?: boolean): Promise<unknown>;
24
+ export {};
package/dist/execution.js CHANGED
@@ -9,12 +9,16 @@
9
9
  * 5. Lazy-loading of TS modules from manifest
10
10
  */
11
11
  import { Strategy, getRegistry, fullName } from './registry.js';
12
- import { executePipeline } from './pipeline.js';
12
+ import { pathToFileURL } from 'node:url';
13
+ import { executePipeline } from './pipeline/index.js';
13
14
  import { AdapterLoadError } from './errors.js';
14
15
  import { shouldUseBrowserSession } from './capabilityRouting.js';
15
16
  import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
16
17
  /** Set of TS module paths that have been loaded */
17
18
  const _loadedModules = new Set();
19
+ function getErrorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
18
22
  /**
19
23
  * Validates and coerces arguments based on the command's Arg definitions.
20
24
  */
@@ -73,11 +77,11 @@ async function runCommand(cmd, page, kwargs, debug) {
73
77
  const modulePath = internal._modulePath;
74
78
  if (!_loadedModules.has(modulePath)) {
75
79
  try {
76
- await import(`file://${modulePath}`);
80
+ await import(pathToFileURL(modulePath).href);
77
81
  _loadedModules.add(modulePath);
78
82
  }
79
83
  catch (err) {
80
- throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${err.message}`, 'Check that the adapter file exists and has no syntax errors.');
84
+ throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${getErrorMessage(err)}`, 'Check that the adapter file exists and has no syntax errors.');
81
85
  }
82
86
  }
83
87
  // After loading, the module's cli() call will have updated the registry.
@@ -93,6 +97,24 @@ async function runCommand(cmd, page, kwargs, debug) {
93
97
  return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
94
98
  throw new Error(`Command ${fullName(cmd)} has no func or pipeline`);
95
99
  }
100
+ /**
101
+ * Resolve the pre-navigation URL for a command, or null to skip.
102
+ *
103
+ * COOKIE/HEADER strategies need the browser on the target domain so
104
+ * `fetch(url, { credentials: 'include' })` carries cookies.
105
+ * Adapters that handle their own navigation set `navigateBefore: false`.
106
+ */
107
+ function resolvePreNav(cmd) {
108
+ if (cmd.navigateBefore === false)
109
+ return null;
110
+ if (typeof cmd.navigateBefore === 'string')
111
+ return cmd.navigateBefore;
112
+ // Default: pre-navigate for COOKIE/HEADER strategies with a domain
113
+ if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
114
+ return `https://${cmd.domain}`;
115
+ }
116
+ return null;
117
+ }
96
118
  /**
97
119
  * Execute a CLI command. Automatically manages browser sessions when needed.
98
120
  *
@@ -105,15 +127,17 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
105
127
  kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
106
128
  }
107
129
  catch (err) {
108
- throw new Error(`[Argument Validation Error]\n${err.message}`);
130
+ throw new Error(`[Argument Validation Error]\n${getErrorMessage(err)}`);
109
131
  }
110
132
  if (shouldUseBrowserSession(cmd)) {
111
133
  const BrowserFactory = getBrowserFactory();
112
134
  return browserSession(BrowserFactory, async (page) => {
113
- // Cookie/header strategies require same-origin context for credentialed fetch.
114
- if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
135
+ // Pre-navigate to target domain for cookie/header context if needed.
136
+ // Each adapter controls this via `navigateBefore` (see CliCommand docs).
137
+ const preNavUrl = resolvePreNav(cmd);
138
+ if (preNavUrl) {
115
139
  try {
116
- await page.goto(`https://${cmd.domain}`);
140
+ await page.goto(preNavUrl);
117
141
  await page.wait(2);
118
142
  }
119
143
  catch { }