@jackwener/opencli 1.3.3 → 1.4.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 (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -33,6 +33,15 @@ describe('parseCommand', () => {
33
33
  'Install command contains unsafe shell operators',
34
34
  );
35
35
  });
36
+
37
+ it('rejects command substitution and multiline input', () => {
38
+ expect(() => parseCommand('brew install $(whoami)')).toThrow(
39
+ 'Install command contains unsafe shell operators',
40
+ );
41
+ expect(() => parseCommand('brew install gh\nrm -rf /')).toThrow(
42
+ 'Install command contains unsafe shell operators',
43
+ );
44
+ });
36
45
  });
37
46
 
38
47
  describe('installExternalCli', () => {
package/src/external.ts CHANGED
@@ -6,6 +6,7 @@ import { spawnSync, execFileSync } from 'node:child_process';
6
6
  import yaml from 'js-yaml';
7
7
  import chalk from 'chalk';
8
8
  import { log } from './logger.js';
9
+ import { getErrorMessage } from './errors.js';
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
 
@@ -41,8 +42,8 @@ export function loadExternalClis(): ExternalCliConfig[] {
41
42
  const parsed = (yaml.load(raw) || []) as ExternalCliConfig[];
42
43
  for (const item of parsed) configs.set(item.name, item);
43
44
  }
44
- } catch (err: any) {
45
- log.warn(`Failed to parse built-in external-clis.yaml: ${err.message}`);
45
+ } catch (err) {
46
+ log.warn(`Failed to parse built-in external-clis.yaml: ${getErrorMessage(err)}`);
46
47
  }
47
48
 
48
49
  // 2. Load user custom
@@ -55,8 +56,8 @@ export function loadExternalClis(): ExternalCliConfig[] {
55
56
  configs.set(item.name, item); // Overwrite built-in if duplicated
56
57
  }
57
58
  }
58
- } catch (err: any) {
59
- log.warn(`Failed to parse user external-clis.yaml: ${err.message}`);
59
+ } catch (err) {
60
+ log.warn(`Failed to parse user external-clis.yaml: ${getErrorMessage(err)}`);
60
61
  }
61
62
 
62
63
  return Array.from(configs.values()).sort((a, b) => a.name.localeCompare(b.name));
@@ -94,7 +95,7 @@ export function getInstallCmd(installConfig?: ExternalCliInstall): string | null
94
95
  * Object with `binary` and `args` fields, or throws on unsafe input.
95
96
  */
96
97
  export function parseCommand(cmd: string): { binary: string; args: string[] } {
97
- const shellOperators = /&&|\|\|?|;|[><`]/;
98
+ const shellOperators = /&&|\|\|?|;|[><`$#\n\r]|\$\(/;
98
99
  if (shellOperators.test(cmd)) {
99
100
  throw new Error(
100
101
  `Install command contains unsafe shell operators and cannot be executed securely: "${cmd}". ` +
@@ -118,8 +119,9 @@ export function parseCommand(cmd: string): { binary: string; args: string[] } {
118
119
  return { binary, args };
119
120
  }
120
121
 
121
- function shouldRetryWithCmdShim(binary: string, err: NodeJS.ErrnoException): boolean {
122
- return os.platform() === 'win32' && !path.extname(binary) && err.code === 'ENOENT';
122
+ function shouldRetryWithCmdShim(binary: string, err: unknown): boolean {
123
+ const code = err instanceof Error ? (err as NodeJS.ErrnoException).code : undefined;
124
+ return os.platform() === 'win32' && !path.extname(binary) && code === 'ENOENT';
123
125
  }
124
126
 
125
127
  function runInstallCommand(cmd: string): void {
@@ -127,7 +129,7 @@ function runInstallCommand(cmd: string): void {
127
129
 
128
130
  try {
129
131
  execFileSync(binary, args, { stdio: 'inherit' });
130
- } catch (err: any) {
132
+ } catch (err) {
131
133
  if (shouldRetryWithCmdShim(binary, err)) {
132
134
  execFileSync(`${binary}.cmd`, args, { stdio: 'inherit' });
133
135
  return;
@@ -156,8 +158,8 @@ export function installExternalCli(cli: ExternalCliConfig): boolean {
156
158
  runInstallCommand(cmd);
157
159
  console.log(chalk.green(`✅ Installed '${cli.name}' successfully.\n`));
158
160
  return true;
159
- } catch (err: any) {
160
- console.error(chalk.red(`❌ Failed to install '${cli.name}': ${err.message}`));
161
+ } catch (err) {
162
+ console.error(chalk.red(`❌ Failed to install '${cli.name}': ${getErrorMessage(err)}`));
161
163
  return false;
162
164
  }
163
165
  }
package/src/generate.ts CHANGED
@@ -12,30 +12,13 @@ import { exploreUrl } from './explore.js';
12
12
  import type { IBrowserFactory } from './runtime.js';
13
13
  import { synthesizeFromExplore, type SynthesizeCandidateSummary, type SynthesizeResult } from './synthesize.js';
14
14
 
15
- // Registration is a no-op stub — candidates are written to disk by synthesize,
16
- // but not yet auto-copied into the user clis dir.
17
- interface RegisterCandidatesOptions {
18
- target: string;
19
- builtinClis?: string;
20
- userClis?: string;
21
- name?: string;
22
- }
23
-
24
- interface RegisterCandidatesResult {
25
- ok: boolean;
26
- count: number;
27
- }
28
-
29
15
  export interface GenerateCliOptions {
30
16
  url: string;
31
17
  BrowserFactory: new () => IBrowserFactory;
32
- builtinClis?: string;
33
- userClis?: string;
34
18
  goal?: string | null;
35
19
  site?: string;
36
20
  waitSeconds?: number;
37
21
  top?: number;
38
- register?: boolean;
39
22
  workspace?: string;
40
23
  }
41
24
 
@@ -57,11 +40,6 @@ export interface GenerateCliResult {
57
40
  candidate_count: number;
58
41
  candidates: Array<Pick<SynthesizeCandidateSummary, 'name' | 'strategy' | 'confidence'>>;
59
42
  };
60
- register: RegisterCandidatesResult | null;
61
- }
62
-
63
- function registerCandidates(_opts: RegisterCandidatesOptions): RegisterCandidatesResult {
64
- return { ok: true, count: 0 };
65
43
  }
66
44
 
67
45
  const CAPABILITY_ALIASES: Record<string, string[]> = {
@@ -102,9 +80,10 @@ function selectCandidate(candidates: SynthesizeResult['candidates'], goal?: stri
102
80
  }
103
81
 
104
82
  const lower = (goal ?? '').trim().toLowerCase();
105
- const partial = candidates.find(c =>
106
- c.name?.toLowerCase().includes(lower) || lower.includes(c.name?.toLowerCase())
107
- );
83
+ const partial = candidates.find(c => {
84
+ const cName = c.name?.toLowerCase() ?? '';
85
+ return cName.includes(lower) || lower.includes(cName);
86
+ });
108
87
  return partial ?? candidates[0];
109
88
  }
110
89
 
@@ -127,19 +106,6 @@ export async function generateCliFromUrl(opts: GenerateCliOptions): Promise<Gene
127
106
  const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
128
107
  const selectedSite = synthesizeResult.site ?? exploreResult.site;
129
108
 
130
- // Step 4: Register (if requested)
131
- let registerResult: RegisterCandidatesResult | null = null;
132
- if (opts.register !== false && synthesizeResult.candidate_count > 0) {
133
- try {
134
- registerResult = registerCandidates({
135
- target: synthesizeResult.out_dir,
136
- builtinClis: opts.builtinClis,
137
- userClis: opts.userClis,
138
- name: selected?.name,
139
- });
140
- } catch {}
141
- }
142
-
143
109
  const ok = exploreResult.endpoint_count > 0 && synthesizeResult.candidate_count > 0;
144
110
 
145
111
  return {
@@ -164,7 +130,6 @@ export async function generateCliFromUrl(opts: GenerateCliOptions): Promise<Gene
164
130
  confidence: c.confidence,
165
131
  })),
166
132
  },
167
- register: registerResult,
168
133
  };
169
134
  }
170
135
 
@@ -188,8 +153,6 @@ export function renderGenerateSummary(r: GenerateCliResult): string {
188
153
  lines.push(` • ${c.name} (${c.strategy}, ${((c.confidence ?? 0) * 100).toFixed(0)}%)`);
189
154
  }
190
155
 
191
- if (r.register) lines.push(`\nRegistered: ${r.register.count ?? 0}`);
192
-
193
156
  const fw = r.explore?.framework ?? {};
194
157
  const fwNames = Object.entries(fw).filter(([, v]) => v).map(([k]) => k);
195
158
  if (fwNames.length) lines.push(`Framework: ${fwNames.join(', ')}`);
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Tests for the plugin lifecycle hooks system.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest';
6
+ import {
7
+ onStartup,
8
+ onBeforeExecute,
9
+ onAfterExecute,
10
+ emitHook,
11
+ clearAllHooks,
12
+ type HookContext,
13
+ } from './hooks.js';
14
+
15
+ beforeEach(() => {
16
+ clearAllHooks();
17
+ });
18
+
19
+ describe('hook registration and emission', () => {
20
+ it('onBeforeExecute hook is called with context', async () => {
21
+ const calls: HookContext[] = [];
22
+ onBeforeExecute((ctx) => { calls.push({ ...ctx }); });
23
+
24
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: { limit: 5 }, startedAt: 100 });
25
+
26
+ expect(calls).toHaveLength(1);
27
+ expect(calls[0].command).toBe('test/cmd');
28
+ expect(calls[0].args).toEqual({ limit: 5 });
29
+ expect(calls[0].startedAt).toBe(100);
30
+ });
31
+
32
+ it('onAfterExecute hook receives result', async () => {
33
+ const results: unknown[] = [];
34
+ onAfterExecute((_ctx, result) => { results.push(result); });
35
+
36
+ const mockResult = [{ title: 'item1' }, { title: 'item2' }];
37
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, mockResult);
38
+
39
+ expect(results).toHaveLength(1);
40
+ expect(results[0]).toEqual(mockResult);
41
+ });
42
+
43
+ it('onStartup hook fires', async () => {
44
+ let fired = false;
45
+ onStartup(() => { fired = true; });
46
+
47
+ await emitHook('onStartup', { command: '__startup__', args: {} });
48
+ expect(fired).toBe(true);
49
+ });
50
+
51
+ it('multiple hooks on the same event fire in order', async () => {
52
+ const order: number[] = [];
53
+ onBeforeExecute(() => { order.push(1); });
54
+ onBeforeExecute(() => { order.push(2); });
55
+ onBeforeExecute(() => { order.push(3); });
56
+
57
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
58
+ expect(order).toEqual([1, 2, 3]);
59
+ });
60
+
61
+ it('async hooks are awaited', async () => {
62
+ const order: string[] = [];
63
+ onBeforeExecute(async () => {
64
+ await new Promise((r) => setTimeout(r, 10));
65
+ order.push('async-done');
66
+ });
67
+ onBeforeExecute(() => { order.push('sync'); });
68
+
69
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
70
+ expect(order).toEqual(['async-done', 'sync']);
71
+ });
72
+ });
73
+
74
+ describe('hook error isolation', () => {
75
+ it('failing hook does not prevent other hooks from running', async () => {
76
+ const calls: string[] = [];
77
+
78
+ onBeforeExecute(() => { calls.push('first'); });
79
+ onBeforeExecute(() => { throw new Error('boom'); });
80
+ onBeforeExecute(() => { calls.push('third'); });
81
+
82
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
83
+
84
+ // First and third should still run despite the second throwing
85
+ expect(calls).toEqual(['first', 'third']);
86
+ });
87
+
88
+ it('async hook rejection does not prevent other hooks', async () => {
89
+ const calls: string[] = [];
90
+
91
+ onAfterExecute(() => { calls.push('before-reject'); });
92
+ onAfterExecute(async () => { throw new Error('async boom'); });
93
+ onAfterExecute(() => { calls.push('after-reject'); });
94
+
95
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, null);
96
+
97
+ expect(calls).toEqual(['before-reject', 'after-reject']);
98
+ });
99
+ });
100
+
101
+ describe('no-op when no hooks registered', () => {
102
+ it('emitHook with no registered hooks does nothing', async () => {
103
+ // Should not throw
104
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
105
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, []);
106
+ await emitHook('onStartup', { command: '__startup__', args: {} });
107
+ });
108
+ });
109
+
110
+ describe('clearAllHooks', () => {
111
+ it('removes all hooks', async () => {
112
+ let called = false;
113
+ onStartup(() => { called = true; });
114
+
115
+ clearAllHooks();
116
+ await emitHook('onStartup', { command: '__startup__', args: {} });
117
+
118
+ expect(called).toBe(false);
119
+ });
120
+ });
121
+
122
+ describe('globalThis singleton', () => {
123
+ it('uses globalThis.__opencli_hooks__ for shared state', () => {
124
+ expect(globalThis.__opencli_hooks__).toBeInstanceOf(Map);
125
+ });
126
+ });
package/src/hooks.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Plugin lifecycle hooks: allows plugins to tap into opencli's execution lifecycle.
3
+ *
4
+ * Hooks use globalThis (like the command registry) to guarantee a single shared
5
+ * instance across all module copies — critical when TS plugins are loaded via
6
+ * npm link / peerDependency symlinks.
7
+ *
8
+ * Available hooks:
9
+ * onStartup — fired once after all commands & plugins are discovered
10
+ * onBeforeExecute — fired before every command execution
11
+ * onAfterExecute — fired after every command execution (receives result)
12
+ */
13
+
14
+ import { log } from './logger.js';
15
+
16
+ export type HookName = 'onStartup' | 'onBeforeExecute' | 'onAfterExecute';
17
+
18
+ export interface HookContext {
19
+ /** Command full name in "site/name" format, or "__startup__" for onStartup */
20
+ command: string;
21
+ /** Coerced and validated arguments */
22
+ args: Record<string, unknown>;
23
+ /** Epoch ms when execution started (set by executeCommand) */
24
+ startedAt?: number;
25
+ /** Epoch ms when execution finished (set by executeCommand) */
26
+ finishedAt?: number;
27
+ /** Error thrown by the command, if execution failed */
28
+ error?: unknown;
29
+ /** Plugins can attach arbitrary data here for cross-hook communication */
30
+ [key: string]: unknown;
31
+ }
32
+
33
+ export type HookFn = (ctx: HookContext, result?: unknown) => void | Promise<void>;
34
+
35
+ // ── Singleton hook store (shared across module instances via globalThis) ──
36
+ declare global {
37
+ // eslint-disable-next-line no-var
38
+ var __opencli_hooks__: Map<HookName, HookFn[]> | undefined;
39
+ }
40
+ const _hooks: Map<HookName, HookFn[]> =
41
+ globalThis.__opencli_hooks__ ??= new Map();
42
+
43
+ // ── Registration API (used by plugins) ─────────────────────────────────────
44
+
45
+ function addHook(name: HookName, fn: HookFn): void {
46
+ const list = _hooks.get(name) ?? [];
47
+ list.push(fn);
48
+ _hooks.set(name, list);
49
+ }
50
+
51
+ /** Register a hook that fires once after all plugins are discovered. */
52
+ export function onStartup(fn: HookFn): void {
53
+ addHook('onStartup', fn);
54
+ }
55
+
56
+ /** Register a hook that fires before every command execution. */
57
+ export function onBeforeExecute(fn: HookFn): void {
58
+ addHook('onBeforeExecute', fn);
59
+ }
60
+
61
+ /** Register a hook that fires after every command execution with the result. */
62
+ export function onAfterExecute(fn: HookFn): void {
63
+ addHook('onAfterExecute', fn);
64
+ }
65
+
66
+ // ── Emit API (used internally by opencli core) ─────────────────────────────
67
+
68
+ /**
69
+ * Trigger all registered handlers for a hook.
70
+ * Each handler is wrapped in try/catch — a failing hook never blocks command execution.
71
+ */
72
+ export async function emitHook(name: HookName, ctx: HookContext, result?: unknown): Promise<void> {
73
+ const handlers = _hooks.get(name);
74
+ if (!handlers || handlers.length === 0) return;
75
+
76
+ for (const fn of handlers) {
77
+ try {
78
+ await fn(ctx, result);
79
+ } catch (err) {
80
+ log.warn(`Hook ${name} handler failed: ${err instanceof Error ? err.message : String(err)}`);
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Remove all registered hooks. Intended for testing only.
87
+ */
88
+ export function clearAllHooks(): void {
89
+ _hooks.clear();
90
+ }
@@ -9,6 +9,44 @@
9
9
  * - stepTap (pipeline/steps/tap.ts)
10
10
  */
11
11
 
12
+ /**
13
+ * Helper: define a non-enumerable property on window.
14
+ * Avoids detection via Object.keys(window) or for..in loops.
15
+ */
16
+ const DEFINE_HIDDEN = `
17
+ function __defHidden(obj, key, val) {
18
+ try {
19
+ Object.defineProperty(obj, key, { value: val, writable: true, enumerable: false, configurable: true });
20
+ } catch { obj[key] = val; }
21
+ }`;
22
+
23
+ /**
24
+ * Helper: disguise a patched function so toString() returns native code signature.
25
+ */
26
+ const DISGUISE_FN = `
27
+ function __disguise(fn, name) {
28
+ const nativeStr = 'function ' + name + '() { [native code] }';
29
+ // Override toString on the instance AND patch Function.prototype.toString
30
+ // to handle Function.prototype.toString.call(fn) bypasses.
31
+ const _origToString = Function.prototype.toString;
32
+ const _patchedFns = window.__dFns || (function() {
33
+ const m = new Map();
34
+ Object.defineProperty(window, '__dFns', { value: m, enumerable: false, configurable: true });
35
+ // Patch Function.prototype.toString once to consult the map
36
+ Object.defineProperty(Function.prototype, 'toString', {
37
+ value: function() {
38
+ const override = m.get(this);
39
+ return override !== undefined ? override : _origToString.call(this);
40
+ },
41
+ writable: true, configurable: true
42
+ });
43
+ return m;
44
+ })();
45
+ _patchedFns.set(fn, nativeStr);
46
+ try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
47
+ return fn;
48
+ }`;
49
+
12
50
  /**
13
51
  * Generate JavaScript source that installs a fetch/XHR interceptor.
14
52
  * Captured responses are pushed to `window.__opencli_intercepted`.
@@ -24,18 +62,24 @@ export function generateInterceptorJs(
24
62
  const arr = opts.arrayName ?? '__opencli_intercepted';
25
63
  const guard = opts.patchGuard ?? '__opencli_interceptor_patched';
26
64
 
65
+ // Store the current pattern in a separate global so it can be updated
66
+ // without re-patching fetch/XHR (the patchGuard only prevents double-patching).
67
+ const patternVar = `${guard}_pattern`;
68
+
27
69
  return `
28
70
  () => {
29
- window.${arr} = window.${arr} || [];
30
- window.${arr}_errors = window.${arr}_errors || [];
31
- const __pattern = ${patternExpr};
71
+ ${DEFINE_HIDDEN}
72
+ ${DISGUISE_FN}
32
73
 
33
- if (!window.${guard}) {
34
- const __checkMatch = (url) => __pattern && url.includes(__pattern);
74
+ if (!window.${arr}) __defHidden(window, '${arr}', []);
75
+ if (!window.${arr}_errors) __defHidden(window, '${arr}_errors', []);
76
+ __defHidden(window, '${patternVar}', ${patternExpr});
77
+ const __checkMatch = (url) => window.${patternVar} && url.includes(window.${patternVar});
35
78
 
79
+ if (!window.${guard}) {
36
80
  // ── Patch fetch ──
37
81
  const __origFetch = window.fetch;
38
- window.fetch = async function(...args) {
82
+ window.fetch = __disguise(async function(...args) {
39
83
  const reqUrl = typeof args[0] === 'string' ? args[0]
40
84
  : (args[0] && args[0].url) || '';
41
85
  const response = await __origFetch.apply(this, args);
@@ -47,28 +91,28 @@ export function generateInterceptorJs(
47
91
  } catch(e) { window.${arr}_errors.push({ url: reqUrl, error: String(e) }); }
48
92
  }
49
93
  return response;
50
- };
94
+ }, 'fetch');
51
95
 
52
96
  // ── Patch XMLHttpRequest ──
53
97
  const __XHR = XMLHttpRequest.prototype;
54
98
  const __origOpen = __XHR.open;
55
99
  const __origSend = __XHR.send;
56
- __XHR.open = function(method, url) {
57
- this.__opencli_url = String(url);
100
+ __XHR.open = __disguise(function(method, url) {
101
+ Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
58
102
  return __origOpen.apply(this, arguments);
59
- };
60
- __XHR.send = function() {
61
- if (__checkMatch(this.__opencli_url)) {
103
+ }, 'open');
104
+ __XHR.send = __disguise(function() {
105
+ if (__checkMatch(this.__iurl)) {
62
106
  this.addEventListener('load', function() {
63
107
  try {
64
108
  window.${arr}.push(JSON.parse(this.responseText));
65
- } catch(e) { window.${arr}_errors.push({ url: this.__opencli_url, error: String(e) }); }
109
+ } catch(e) { window.${arr}_errors.push({ url: this.__iurl, error: String(e) }); }
66
110
  });
67
111
  }
68
112
  return __origSend.apply(this, arguments);
69
- };
113
+ }, 'send');
70
114
 
71
- window.${guard} = true;
115
+ __defHidden(window, '${guard}', true);
72
116
  }
73
117
  }
74
118
  `;
@@ -109,13 +153,19 @@ export function generateTapInterceptorJs(patternExpr: string): {
109
153
  let captureResolve;
110
154
  const capturePromise = new Promise(r => { captureResolve = r; });
111
155
  const capturePattern = ${patternExpr};
156
+ function __disguise(fn, name) {
157
+ const s = 'function ' + name + '() { [native code] }';
158
+ Object.defineProperty(fn, 'toString', { value: function() { return s; }, writable: true, configurable: true, enumerable: false });
159
+ try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
160
+ return fn;
161
+ }
112
162
  `,
113
163
  capturedVar: 'captured',
114
164
  promiseVar: 'capturePromise',
115
165
  resolveVar: 'captureResolve',
116
166
  fetchPatch: `
117
167
  const origFetch = window.fetch;
118
- window.fetch = async function(...fetchArgs) {
168
+ window.fetch = __disguise(async function(...fetchArgs) {
119
169
  const resp = await origFetch.apply(this, fetchArgs);
120
170
  try {
121
171
  const url = typeof fetchArgs[0] === 'string' ? fetchArgs[0]
@@ -125,17 +175,17 @@ export function generateTapInterceptorJs(patternExpr: string): {
125
175
  }
126
176
  } catch {}
127
177
  return resp;
128
- };
178
+ }, 'fetch');
129
179
  `,
130
180
  xhrPatch: `
131
181
  const origXhrOpen = XMLHttpRequest.prototype.open;
132
182
  const origXhrSend = XMLHttpRequest.prototype.send;
133
- XMLHttpRequest.prototype.open = function(method, url) {
134
- this.__tapUrl = String(url);
183
+ XMLHttpRequest.prototype.open = __disguise(function(method, url) {
184
+ Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
135
185
  return origXhrOpen.apply(this, arguments);
136
- };
137
- XMLHttpRequest.prototype.send = function(body) {
138
- if (capturePattern && this.__tapUrl?.includes(capturePattern)) {
186
+ }, 'open');
187
+ XMLHttpRequest.prototype.send = __disguise(function(body) {
188
+ if (capturePattern && this.__iurl?.includes(capturePattern)) {
139
189
  this.addEventListener('load', function() {
140
190
  if (!captured) {
141
191
  try { captured = JSON.parse(this.responseText); captureResolve(); } catch {}
@@ -143,7 +193,7 @@ export function generateTapInterceptorJs(patternExpr: string): {
143
193
  });
144
194
  }
145
195
  return origXhrSend.apply(this, arguments);
146
- };
196
+ }, 'send');
147
197
  `,
148
198
  restorePatch: `
149
199
  window.fetch = origFetch;
package/src/main.ts CHANGED
@@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url';
19
19
  import { discoverClis, discoverPlugins } from './discovery.js';
20
20
  import { getCompletions } from './completion.js';
21
21
  import { runCli } from './cli.js';
22
+ import { emitHook } from './hooks.js';
22
23
 
23
24
  const __filename = fileURLToPath(import.meta.url);
24
25
  const __dirname = path.dirname(__filename);
@@ -49,4 +50,5 @@ if (getCompIdx !== -1) {
49
50
  process.exit(0);
50
51
  }
51
52
 
53
+ await emitHook('onStartup', { command: '__startup__', args: {} });
52
54
  runCli(BUILTIN_CLIS, USER_CLIS);
package/src/output.ts CHANGED
@@ -15,6 +15,14 @@ export interface RenderOptions {
15
15
  footerExtra?: string;
16
16
  }
17
17
 
18
+ function normalizeRows(data: unknown): Record<string, unknown>[] {
19
+ return Array.isArray(data) ? data : [data as Record<string, unknown>];
20
+ }
21
+
22
+ function resolveColumns(rows: Record<string, unknown>[], opts: RenderOptions): string[] {
23
+ return opts.columns ?? Object.keys(rows[0] ?? {});
24
+ }
25
+
18
26
  export function render(data: unknown, opts: RenderOptions = {}): void {
19
27
  const fmt = opts.fmt ?? 'table';
20
28
  if (data === null || data === undefined) {
@@ -31,9 +39,9 @@ export function render(data: unknown, opts: RenderOptions = {}): void {
31
39
  }
32
40
 
33
41
  function renderTable(data: unknown, opts: RenderOptions): void {
34
- const rows = Array.isArray(data) ? data : [data as Record<string, unknown>];
42
+ const rows = normalizeRows(data);
35
43
  if (!rows.length) { console.log(chalk.dim('(no data)')); return; }
36
- const columns = opts.columns ?? Object.keys(rows[0]);
44
+ const columns = resolveColumns(rows, opts);
37
45
 
38
46
  const header = columns.map(c => capitalize(c));
39
47
  const table = new Table({
@@ -66,9 +74,9 @@ function renderJson(data: unknown): void {
66
74
  }
67
75
 
68
76
  function renderMarkdown(data: unknown, opts: RenderOptions): void {
69
- const rows = Array.isArray(data) ? data : [data as Record<string, unknown>];
77
+ const rows = normalizeRows(data);
70
78
  if (!rows.length) return;
71
- const columns = opts.columns ?? Object.keys(rows[0]);
79
+ const columns = resolveColumns(rows, opts);
72
80
  console.log('| ' + columns.join(' | ') + ' |');
73
81
  console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
74
82
  for (const row of rows) {
@@ -77,9 +85,9 @@ function renderMarkdown(data: unknown, opts: RenderOptions): void {
77
85
  }
78
86
 
79
87
  function renderCsv(data: unknown, opts: RenderOptions): void {
80
- const rows = Array.isArray(data) ? data : [data as Record<string, unknown>];
88
+ const rows = normalizeRows(data);
81
89
  if (!rows.length) return;
82
- const columns = opts.columns ?? Object.keys(rows[0]);
90
+ const columns = resolveColumns(rows, opts);
83
91
  console.log(columns.join(','));
84
92
  for (const row of rows) {
85
93
  console.log(columns.map(c => {
@@ -16,7 +16,7 @@ export interface PipelineContext {
16
16
  }
17
17
 
18
18
  /** Steps that interact with the browser and may fail transiently */
19
- const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot', 'scroll']);
19
+ const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot']);
20
20
 
21
21
  export async function executePipeline(
22
22
  page: IPage | null,
@@ -6,9 +6,7 @@
6
6
  import type { IPage } from '../../types.js';
7
7
  import { render } from '../template.js';
8
8
 
9
- function isRecord(value: unknown): value is Record<string, unknown> {
10
- return typeof value === 'object' && value !== null && !Array.isArray(value);
11
- }
9
+ import { isRecord } from '../../utils.js';
12
10
 
13
11
  export async function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
14
12
  if (isRecord(params) && 'url' in params) {