@jackwener/opencli 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (508) hide show
  1. package/.github/pull_request_template.md +3 -1
  2. package/.github/workflows/build-extension.yml +7 -1
  3. package/.github/workflows/ci.yml +29 -3
  4. package/.github/workflows/docs.yml +1 -1
  5. package/.github/workflows/e2e-headed.yml +20 -0
  6. package/.github/workflows/release.yml +1 -1
  7. package/.github/workflows/security.yml +0 -3
  8. package/CHANGELOG.md +55 -0
  9. package/CONTRIBUTING.md +6 -3
  10. package/README.md +37 -10
  11. package/README.zh-CN.md +37 -10
  12. package/SKILL.md +7 -2
  13. package/TESTING.md +1 -0
  14. package/chatwise-opencli.ps1 +82 -0
  15. package/dist/analysis.d.ts +38 -0
  16. package/dist/analysis.js +166 -0
  17. package/dist/browser/cdp.d.ts +0 -4
  18. package/dist/browser/cdp.js +59 -38
  19. package/dist/browser/cdp.test.d.ts +1 -0
  20. package/dist/browser/cdp.test.js +52 -0
  21. package/dist/browser/daemon-client.js +2 -1
  22. package/dist/browser/discover.js +2 -1
  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/errors.js +2 -1
  27. package/dist/browser/index.d.ts +3 -2
  28. package/dist/browser/index.js +2 -1
  29. package/dist/browser/mcp.d.ts +0 -2
  30. package/dist/browser/mcp.js +2 -3
  31. package/dist/browser/page.d.ts +4 -3
  32. package/dist/browser/page.js +44 -35
  33. package/dist/browser/stealth.d.ts +16 -0
  34. package/dist/browser/stealth.js +155 -0
  35. package/dist/browser.test.js +47 -1
  36. package/dist/build-manifest.js +15 -9
  37. package/dist/build-manifest.test.js +12 -0
  38. package/dist/cascade.js +4 -2
  39. package/dist/cli-manifest.json +639 -258
  40. package/dist/cli.js +57 -29
  41. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  42. package/dist/clis/_shared/desktop-commands.js +108 -0
  43. package/dist/clis/antigravity/serve.js +5 -2
  44. package/dist/clis/arxiv/search.js +1 -1
  45. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  46. package/dist/clis/bilibili/dynamic.test.js +68 -0
  47. package/dist/clis/bilibili/favorite.js +4 -2
  48. package/dist/clis/bilibili/following.js +3 -2
  49. package/dist/clis/bilibili/subtitle.js +8 -7
  50. package/dist/clis/bilibili/utils.js +2 -2
  51. package/dist/clis/boss/batchgreet.js +1 -1
  52. package/dist/clis/boss/chatlist.js +1 -1
  53. package/dist/clis/boss/chatmsg.js +1 -1
  54. package/dist/clis/boss/detail.js +1 -1
  55. package/dist/clis/boss/exchange.js +1 -1
  56. package/dist/clis/boss/greet.js +1 -1
  57. package/dist/clis/boss/invite.js +1 -1
  58. package/dist/clis/boss/joblist.js +1 -1
  59. package/dist/clis/boss/mark.js +4 -3
  60. package/dist/clis/boss/recommend.js +1 -1
  61. package/dist/clis/boss/resume.js +1 -1
  62. package/dist/clis/boss/search.js +1 -1
  63. package/dist/clis/boss/send.js +5 -4
  64. package/dist/clis/boss/stats.js +1 -1
  65. package/dist/clis/chatgpt/ask.js +4 -0
  66. package/dist/clis/chatgpt/new.js +5 -1
  67. package/dist/clis/chatgpt/read.js +5 -1
  68. package/dist/clis/chatgpt/send.js +2 -1
  69. package/dist/clis/chatgpt/status.js +5 -1
  70. package/dist/clis/chatwise/ask.js +8 -2
  71. package/dist/clis/chatwise/export.js +2 -0
  72. package/dist/clis/chatwise/history.js +2 -0
  73. package/dist/clis/chatwise/model.js +8 -3
  74. package/dist/clis/chatwise/new.js +3 -18
  75. package/dist/clis/chatwise/read.js +2 -0
  76. package/dist/clis/chatwise/screenshot.js +3 -27
  77. package/dist/clis/chatwise/send.js +8 -2
  78. package/dist/clis/chatwise/shared.d.ts +2 -0
  79. package/dist/clis/chatwise/shared.js +6 -0
  80. package/dist/clis/chatwise/status.js +3 -22
  81. package/dist/clis/codex/ask.js +6 -2
  82. package/dist/clis/codex/dump.js +2 -25
  83. package/dist/clis/codex/new.js +2 -25
  84. package/dist/clis/codex/screenshot.js +2 -27
  85. package/dist/clis/codex/send.js +6 -4
  86. package/dist/clis/codex/status.js +2 -22
  87. package/dist/clis/cursor/ask.js +2 -1
  88. package/dist/clis/cursor/composer.js +2 -1
  89. package/dist/clis/cursor/dump.js +2 -25
  90. package/dist/clis/cursor/new.js +2 -18
  91. package/dist/clis/cursor/read.js +2 -1
  92. package/dist/clis/cursor/screenshot.js +1 -30
  93. package/dist/clis/cursor/send.js +2 -1
  94. package/dist/clis/cursor/status.js +2 -21
  95. package/dist/clis/dictionary/examples.yaml +25 -0
  96. package/dist/clis/dictionary/search.yaml +27 -0
  97. package/dist/clis/dictionary/synonyms.yaml +25 -0
  98. package/dist/clis/douban/book-hot.js +1 -1
  99. package/dist/clis/douban/movie-hot.js +1 -1
  100. package/dist/clis/douban/search.js +1 -1
  101. package/dist/clis/douban/utils.d.ts +4 -1
  102. package/dist/clis/douban/utils.js +156 -1
  103. package/dist/clis/doubao/ask.js +1 -1
  104. package/dist/clis/doubao/new.js +1 -1
  105. package/dist/clis/doubao/read.js +1 -1
  106. package/dist/clis/doubao/send.js +1 -1
  107. package/dist/clis/doubao/status.js +1 -1
  108. package/dist/clis/doubao-app/ask.js +1 -1
  109. package/dist/clis/doubao-app/new.js +1 -1
  110. package/dist/clis/doubao-app/read.js +1 -1
  111. package/dist/clis/doubao-app/send.js +1 -1
  112. package/dist/clis/grok/ask.d.ts +4 -0
  113. package/dist/clis/grok/ask.js +28 -10
  114. package/dist/clis/grok/ask.test.js +18 -0
  115. package/dist/clis/jd/item.d.ts +1 -0
  116. package/dist/clis/jd/item.js +96 -0
  117. package/dist/clis/jd/item.test.d.ts +1 -0
  118. package/dist/clis/jd/item.test.js +28 -0
  119. package/dist/clis/jike/feed.js +1 -1
  120. package/dist/clis/jike/search.js +1 -1
  121. package/dist/clis/linkedin/search.js +5 -4
  122. package/dist/clis/linkedin/timeline.d.ts +21 -0
  123. package/dist/clis/linkedin/timeline.js +503 -0
  124. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  125. package/dist/clis/linkedin/timeline.test.js +81 -0
  126. package/dist/clis/medium/feed.js +1 -1
  127. package/dist/clis/medium/search.js +1 -1
  128. package/dist/clis/medium/user.js +1 -1
  129. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  130. package/dist/clis/pixiv/detail.yaml +49 -0
  131. package/dist/clis/pixiv/download.d.ts +7 -0
  132. package/dist/clis/pixiv/download.js +78 -0
  133. package/dist/clis/pixiv/download.test.d.ts +1 -0
  134. package/dist/clis/pixiv/download.test.js +87 -0
  135. package/dist/clis/pixiv/illusts.d.ts +8 -0
  136. package/dist/clis/pixiv/illusts.js +65 -0
  137. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  138. package/dist/clis/pixiv/illusts.test.js +99 -0
  139. package/dist/clis/pixiv/ranking.yaml +53 -0
  140. package/dist/clis/pixiv/search.d.ts +6 -0
  141. package/dist/clis/pixiv/search.js +43 -0
  142. package/dist/clis/pixiv/search.test.d.ts +1 -0
  143. package/dist/clis/pixiv/search.test.js +83 -0
  144. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  145. package/dist/clis/pixiv/test-utils.js +23 -0
  146. package/dist/clis/pixiv/user.yaml +46 -0
  147. package/dist/clis/pixiv/utils.d.ts +27 -0
  148. package/dist/clis/pixiv/utils.js +49 -0
  149. package/dist/clis/reddit/comment.js +2 -1
  150. package/dist/clis/reddit/read.js +4 -3
  151. package/dist/clis/reddit/read.test.d.ts +1 -0
  152. package/dist/clis/reddit/read.test.js +28 -0
  153. package/dist/clis/reddit/save.js +2 -1
  154. package/dist/clis/reddit/saved.js +7 -3
  155. package/dist/clis/reddit/subscribe.js +2 -1
  156. package/dist/clis/reddit/upvote.js +2 -1
  157. package/dist/clis/reddit/upvoted.js +7 -3
  158. package/dist/clis/sinablog/article.js +1 -1
  159. package/dist/clis/sinablog/hot.js +1 -1
  160. package/dist/clis/sinablog/user.js +1 -1
  161. package/dist/clis/substack/feed.js +1 -1
  162. package/dist/clis/substack/publication.js +1 -1
  163. package/dist/clis/substack/search.js +3 -2
  164. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  165. package/dist/clis/tiktok/search.yaml +2 -1
  166. package/dist/clis/twitter/accept.js +2 -1
  167. package/dist/clis/twitter/article.js +4 -1
  168. package/dist/clis/twitter/block.js +2 -1
  169. package/dist/clis/twitter/bookmark.js +2 -1
  170. package/dist/clis/twitter/bookmarks.js +3 -2
  171. package/dist/clis/twitter/delete.js +2 -1
  172. package/dist/clis/twitter/follow.js +2 -1
  173. package/dist/clis/twitter/followers.js +3 -2
  174. package/dist/clis/twitter/following.js +3 -2
  175. package/dist/clis/twitter/hide-reply.js +2 -1
  176. package/dist/clis/twitter/like.js +2 -1
  177. package/dist/clis/twitter/notifications.js +2 -1
  178. package/dist/clis/twitter/post.js +2 -1
  179. package/dist/clis/twitter/profile.js +5 -2
  180. package/dist/clis/twitter/reply-dm.js +2 -1
  181. package/dist/clis/twitter/reply.js +2 -1
  182. package/dist/clis/twitter/search.js +30 -13
  183. package/dist/clis/twitter/search.test.d.ts +1 -0
  184. package/dist/clis/twitter/search.test.js +104 -0
  185. package/dist/clis/twitter/thread.js +2 -2
  186. package/dist/clis/twitter/timeline.js +3 -2
  187. package/dist/clis/twitter/trending.js +3 -2
  188. package/dist/clis/twitter/unblock.js +2 -1
  189. package/dist/clis/twitter/unbookmark.js +2 -1
  190. package/dist/clis/twitter/unfollow.js +2 -1
  191. package/dist/clis/v2ex/daily.js +3 -2
  192. package/dist/clis/v2ex/me.js +3 -2
  193. package/dist/clis/v2ex/notifications.js +4 -4
  194. package/dist/clis/web/read.d.ts +16 -0
  195. package/dist/clis/web/read.js +202 -0
  196. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  197. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  198. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  199. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  200. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  201. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  202. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  203. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  204. package/dist/clis/youtube/transcript.js +5 -4
  205. package/dist/clis/youtube/video.js +3 -2
  206. package/dist/constants.d.ts +2 -0
  207. package/dist/constants.js +2 -0
  208. package/dist/daemon.js +9 -4
  209. package/dist/discovery.js +11 -10
  210. package/dist/doctor.js +4 -2
  211. package/dist/download/index.d.ts +4 -12
  212. package/dist/download/index.js +33 -12
  213. package/dist/download/index.test.js +79 -2
  214. package/dist/download/media-download.js +4 -2
  215. package/dist/engine.test.js +76 -4
  216. package/dist/execution.d.ts +1 -9
  217. package/dist/execution.js +56 -46
  218. package/dist/explore.js +12 -111
  219. package/dist/external-clis.yaml +0 -8
  220. package/dist/external.js +7 -5
  221. package/dist/external.test.js +4 -0
  222. package/dist/generate.d.ts +0 -9
  223. package/dist/generate.js +4 -20
  224. package/dist/hooks.d.ts +46 -0
  225. package/dist/hooks.js +56 -0
  226. package/dist/hooks.test.d.ts +4 -0
  227. package/dist/hooks.test.js +92 -0
  228. package/dist/interceptor.js +70 -23
  229. package/dist/main.js +2 -0
  230. package/dist/output.js +12 -6
  231. package/dist/pipeline/executor.js +1 -1
  232. package/dist/pipeline/steps/browser.js +1 -3
  233. package/dist/pipeline/steps/download.js +42 -26
  234. package/dist/pipeline/steps/download.test.d.ts +1 -0
  235. package/dist/pipeline/steps/download.test.js +101 -0
  236. package/dist/pipeline/steps/fetch.js +40 -22
  237. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  238. package/dist/pipeline/steps/fetch.test.js +123 -0
  239. package/dist/pipeline/steps/transform.js +2 -6
  240. package/dist/pipeline/template.js +66 -52
  241. package/dist/pipeline/template.test.js +28 -0
  242. package/dist/pipeline/transform.test.js +18 -0
  243. package/dist/plugin.d.ts +40 -1
  244. package/dist/plugin.js +214 -17
  245. package/dist/plugin.test.d.ts +1 -1
  246. package/dist/plugin.test.js +219 -3
  247. package/dist/record.js +6 -98
  248. package/dist/registry-api.d.ts +2 -0
  249. package/dist/registry-api.js +1 -0
  250. package/dist/registry.d.ts +5 -2
  251. package/dist/registry.js +1 -2
  252. package/dist/runtime.d.ts +0 -1
  253. package/dist/runtime.js +14 -4
  254. package/dist/snapshotFormatter.d.ts +7 -14
  255. package/dist/snapshotFormatter.js +38 -78
  256. package/dist/utils.d.ts +9 -0
  257. package/dist/utils.js +29 -0
  258. package/dist/validate.js +3 -5
  259. package/dist/yaml-schema.d.ts +26 -0
  260. package/dist/yaml-schema.js +5 -0
  261. package/docs/.vitepress/config.mts +3 -0
  262. package/docs/adapters/browser/dictionary.md +27 -0
  263. package/docs/adapters/browser/douban.md +18 -8
  264. package/docs/adapters/browser/jd.md +27 -0
  265. package/docs/adapters/browser/linkedin.md +6 -0
  266. package/docs/adapters/browser/pixiv.md +92 -0
  267. package/docs/adapters/browser/web.md +30 -0
  268. package/docs/adapters/browser/wikipedia.md +0 -9
  269. package/docs/adapters/browser/xueqiu.md +27 -9
  270. package/docs/adapters/desktop/antigravity.md +0 -3
  271. package/docs/adapters/index.md +11 -9
  272. package/docs/comparison.md +125 -0
  273. package/docs/developer/contributing.md +21 -2
  274. package/docs/developer/testing.md +14 -8
  275. package/docs/developer/ts-adapter.md +18 -0
  276. package/docs/developer/yaml-adapter.md +16 -0
  277. package/docs/guide/plugins.md +10 -0
  278. package/docs/zh/guide/plugins.md +10 -0
  279. package/extension/dist/background.js +519 -444
  280. package/extension/manifest.json +1 -1
  281. package/extension/package.json +1 -1
  282. package/extension/src/background.test.ts +46 -1
  283. package/extension/src/background.ts +108 -33
  284. package/extension/src/cdp.ts +9 -9
  285. package/package.json +3 -2
  286. package/scripts/check-doc-coverage.sh +2 -0
  287. package/src/analysis.ts +170 -0
  288. package/src/browser/cdp.test.ts +66 -0
  289. package/src/browser/cdp.ts +64 -41
  290. package/src/browser/daemon-client.ts +4 -3
  291. package/src/browser/discover.ts +2 -1
  292. package/src/browser/dom-snapshot.test.ts +42 -0
  293. package/src/browser/dom-snapshot.ts +56 -3
  294. package/src/browser/errors.ts +2 -1
  295. package/src/browser/index.ts +3 -2
  296. package/src/browser/mcp.ts +2 -4
  297. package/src/browser/page.ts +43 -35
  298. package/src/browser/stealth.ts +156 -0
  299. package/src/browser.test.ts +51 -1
  300. package/src/build-manifest.test.ts +14 -0
  301. package/src/build-manifest.ts +13 -32
  302. package/src/cascade.ts +5 -3
  303. package/src/cli.ts +66 -34
  304. package/src/clis/_shared/desktop-commands.ts +121 -0
  305. package/src/clis/antigravity/serve.ts +6 -3
  306. package/src/clis/arxiv/search.ts +1 -1
  307. package/src/clis/bilibili/dynamic.test.ts +79 -0
  308. package/src/clis/bilibili/favorite.ts +5 -2
  309. package/src/clis/bilibili/following.ts +3 -2
  310. package/src/clis/bilibili/subtitle.ts +8 -7
  311. package/src/clis/bilibili/utils.ts +2 -2
  312. package/src/clis/boss/batchgreet.ts +1 -1
  313. package/src/clis/boss/chatlist.ts +1 -1
  314. package/src/clis/boss/chatmsg.ts +1 -1
  315. package/src/clis/boss/detail.ts +1 -1
  316. package/src/clis/boss/exchange.ts +1 -1
  317. package/src/clis/boss/greet.ts +1 -1
  318. package/src/clis/boss/invite.ts +1 -1
  319. package/src/clis/boss/joblist.ts +1 -1
  320. package/src/clis/boss/mark.ts +4 -3
  321. package/src/clis/boss/recommend.ts +1 -1
  322. package/src/clis/boss/resume.ts +1 -1
  323. package/src/clis/boss/search.ts +1 -1
  324. package/src/clis/boss/send.ts +5 -4
  325. package/src/clis/boss/stats.ts +1 -1
  326. package/src/clis/chatgpt/ask.ts +5 -0
  327. package/src/clis/chatgpt/new.ts +7 -2
  328. package/src/clis/chatgpt/read.ts +7 -2
  329. package/src/clis/chatgpt/send.ts +3 -2
  330. package/src/clis/chatgpt/status.ts +6 -1
  331. package/src/clis/chatwise/ask.ts +7 -2
  332. package/src/clis/chatwise/export.ts +2 -0
  333. package/src/clis/chatwise/history.ts +2 -0
  334. package/src/clis/chatwise/model.ts +7 -3
  335. package/src/clis/chatwise/new.ts +3 -20
  336. package/src/clis/chatwise/read.ts +2 -0
  337. package/src/clis/chatwise/screenshot.ts +3 -32
  338. package/src/clis/chatwise/send.ts +7 -2
  339. package/src/clis/chatwise/shared.ts +8 -0
  340. package/src/clis/chatwise/status.ts +3 -24
  341. package/src/clis/codex/ask.ts +5 -2
  342. package/src/clis/codex/dump.ts +2 -27
  343. package/src/clis/codex/new.ts +2 -28
  344. package/src/clis/codex/screenshot.ts +2 -32
  345. package/src/clis/codex/send.ts +5 -4
  346. package/src/clis/codex/status.ts +2 -24
  347. package/src/clis/cursor/ask.ts +2 -1
  348. package/src/clis/cursor/composer.ts +2 -1
  349. package/src/clis/cursor/dump.ts +2 -27
  350. package/src/clis/cursor/new.ts +2 -20
  351. package/src/clis/cursor/read.ts +2 -1
  352. package/src/clis/cursor/screenshot.ts +1 -36
  353. package/src/clis/cursor/send.ts +2 -1
  354. package/src/clis/cursor/status.ts +2 -22
  355. package/src/clis/dictionary/examples.yaml +25 -0
  356. package/src/clis/dictionary/search.yaml +27 -0
  357. package/src/clis/dictionary/synonyms.yaml +25 -0
  358. package/src/clis/douban/book-hot.ts +1 -1
  359. package/src/clis/douban/movie-hot.ts +1 -1
  360. package/src/clis/douban/search.ts +1 -1
  361. package/src/clis/douban/utils.ts +165 -1
  362. package/src/clis/doubao/ask.ts +1 -1
  363. package/src/clis/doubao/new.ts +1 -1
  364. package/src/clis/doubao/read.ts +1 -1
  365. package/src/clis/doubao/send.ts +1 -1
  366. package/src/clis/doubao/status.ts +1 -1
  367. package/src/clis/doubao-app/ask.ts +1 -1
  368. package/src/clis/doubao-app/new.ts +1 -1
  369. package/src/clis/doubao-app/read.ts +1 -1
  370. package/src/clis/doubao-app/send.ts +1 -1
  371. package/src/clis/grok/ask.test.ts +25 -0
  372. package/src/clis/grok/ask.ts +25 -12
  373. package/src/clis/jd/item.test.ts +35 -0
  374. package/src/clis/jd/item.ts +101 -0
  375. package/src/clis/jike/feed.ts +1 -1
  376. package/src/clis/jike/search.ts +1 -1
  377. package/src/clis/linkedin/search.ts +5 -4
  378. package/src/clis/linkedin/timeline.test.ts +99 -0
  379. package/src/clis/linkedin/timeline.ts +532 -0
  380. package/src/clis/medium/feed.ts +1 -1
  381. package/src/clis/medium/search.ts +1 -1
  382. package/src/clis/medium/user.ts +1 -1
  383. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  384. package/src/clis/pixiv/detail.yaml +49 -0
  385. package/src/clis/pixiv/download.test.ts +114 -0
  386. package/src/clis/pixiv/download.ts +91 -0
  387. package/src/clis/pixiv/illusts.test.ts +115 -0
  388. package/src/clis/pixiv/illusts.ts +78 -0
  389. package/src/clis/pixiv/ranking.yaml +53 -0
  390. package/src/clis/pixiv/search.test.ts +97 -0
  391. package/src/clis/pixiv/search.ts +53 -0
  392. package/src/clis/pixiv/test-utils.ts +29 -0
  393. package/src/clis/pixiv/user.yaml +46 -0
  394. package/src/clis/pixiv/utils.ts +62 -0
  395. package/src/clis/reddit/comment.ts +2 -1
  396. package/src/clis/reddit/read.test.ts +34 -0
  397. package/src/clis/reddit/read.ts +4 -3
  398. package/src/clis/reddit/save.ts +2 -1
  399. package/src/clis/reddit/saved.ts +6 -2
  400. package/src/clis/reddit/subscribe.ts +2 -1
  401. package/src/clis/reddit/upvote.ts +2 -1
  402. package/src/clis/reddit/upvoted.ts +6 -2
  403. package/src/clis/sinablog/article.ts +1 -1
  404. package/src/clis/sinablog/hot.ts +1 -1
  405. package/src/clis/sinablog/user.ts +1 -1
  406. package/src/clis/substack/feed.ts +1 -1
  407. package/src/clis/substack/publication.ts +1 -1
  408. package/src/clis/substack/search.ts +3 -2
  409. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  410. package/src/clis/tiktok/search.yaml +2 -1
  411. package/src/clis/twitter/accept.ts +2 -1
  412. package/src/clis/twitter/article.ts +3 -1
  413. package/src/clis/twitter/block.ts +2 -1
  414. package/src/clis/twitter/bookmark.ts +2 -1
  415. package/src/clis/twitter/bookmarks.ts +3 -2
  416. package/src/clis/twitter/delete.ts +2 -1
  417. package/src/clis/twitter/follow.ts +2 -1
  418. package/src/clis/twitter/followers.ts +3 -2
  419. package/src/clis/twitter/following.ts +3 -2
  420. package/src/clis/twitter/hide-reply.ts +2 -1
  421. package/src/clis/twitter/like.ts +2 -1
  422. package/src/clis/twitter/notifications.ts +2 -1
  423. package/src/clis/twitter/post.ts +2 -1
  424. package/src/clis/twitter/profile.ts +4 -2
  425. package/src/clis/twitter/reply-dm.ts +2 -1
  426. package/src/clis/twitter/reply.ts +2 -1
  427. package/src/clis/twitter/search.test.ts +113 -0
  428. package/src/clis/twitter/search.ts +38 -14
  429. package/src/clis/twitter/thread.ts +2 -2
  430. package/src/clis/twitter/timeline.ts +3 -2
  431. package/src/clis/twitter/trending.ts +3 -2
  432. package/src/clis/twitter/unblock.ts +2 -1
  433. package/src/clis/twitter/unbookmark.ts +2 -1
  434. package/src/clis/twitter/unfollow.ts +2 -1
  435. package/src/clis/v2ex/daily.ts +3 -2
  436. package/src/clis/v2ex/me.ts +3 -2
  437. package/src/clis/v2ex/notifications.ts +3 -4
  438. package/src/clis/web/read.ts +210 -0
  439. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  440. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  441. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  442. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  443. package/src/clis/youtube/transcript.ts +5 -4
  444. package/src/clis/youtube/video.ts +3 -2
  445. package/src/constants.ts +3 -0
  446. package/src/daemon.ts +7 -5
  447. package/src/discovery.ts +12 -34
  448. package/src/doctor.ts +5 -3
  449. package/src/download/index.test.ts +93 -2
  450. package/src/download/index.ts +44 -23
  451. package/src/download/media-download.ts +5 -3
  452. package/src/engine.test.ts +84 -3
  453. package/src/execution.ts +62 -46
  454. package/src/explore.ts +21 -90
  455. package/src/external-clis.yaml +0 -8
  456. package/src/external.test.ts +9 -0
  457. package/src/external.ts +12 -10
  458. package/src/generate.ts +4 -41
  459. package/src/hooks.test.ts +126 -0
  460. package/src/hooks.ts +90 -0
  461. package/src/interceptor.ts +73 -23
  462. package/src/main.ts +2 -0
  463. package/src/output.ts +14 -6
  464. package/src/pipeline/executor.ts +1 -1
  465. package/src/pipeline/steps/browser.ts +1 -3
  466. package/src/pipeline/steps/download.test.ts +136 -0
  467. package/src/pipeline/steps/download.ts +47 -34
  468. package/src/pipeline/steps/fetch.test.ts +179 -0
  469. package/src/pipeline/steps/fetch.ts +39 -23
  470. package/src/pipeline/steps/transform.ts +2 -6
  471. package/src/pipeline/template.test.ts +28 -0
  472. package/src/pipeline/template.ts +67 -79
  473. package/src/pipeline/transform.test.ts +20 -0
  474. package/src/plugin.test.ts +251 -3
  475. package/src/plugin.ts +265 -21
  476. package/src/record.ts +12 -84
  477. package/src/registry-api.ts +2 -0
  478. package/src/registry.ts +7 -4
  479. package/src/runtime.ts +14 -4
  480. package/src/snapshotFormatter.ts +43 -121
  481. package/src/utils.ts +39 -0
  482. package/src/validate.ts +3 -6
  483. package/src/yaml-schema.ts +28 -0
  484. package/tests/e2e/browser-auth.test.ts +25 -0
  485. package/tests/e2e/plugin-management.test.ts +137 -0
  486. package/tests/e2e/public-commands.test.ts +34 -1
  487. package/vitest.config.ts +19 -1
  488. package/.github/workflows/pkg-pr-new.yml +0 -30
  489. package/dist/clis/douban/shared.d.ts +0 -4
  490. package/dist/clis/douban/shared.js +0 -155
  491. package/src/clis/douban/shared.ts +0 -165
  492. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  493. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  494. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  495. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  496. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  497. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  498. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  499. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  500. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  501. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  502. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  503. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  504. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  505. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  506. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  507. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  508. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -25,6 +25,10 @@ describe('parseCommand', () => {
25
25
  it('rejects shell operators', () => {
26
26
  expect(() => parseCommand('brew install gh && rm -rf /')).toThrow('Install command contains unsafe shell operators');
27
27
  });
28
+ it('rejects command substitution and multiline input', () => {
29
+ expect(() => parseCommand('brew install $(whoami)')).toThrow('Install command contains unsafe shell operators');
30
+ expect(() => parseCommand('brew install gh\nrm -rf /')).toThrow('Install command contains unsafe shell operators');
31
+ });
28
32
  });
29
33
  describe('installExternalCli', () => {
30
34
  const cli = {
@@ -9,20 +9,13 @@
9
9
  */
10
10
  import type { IBrowserFactory } from './runtime.js';
11
11
  import { type SynthesizeCandidateSummary } from './synthesize.js';
12
- interface RegisterCandidatesResult {
13
- ok: boolean;
14
- count: number;
15
- }
16
12
  export interface GenerateCliOptions {
17
13
  url: string;
18
14
  BrowserFactory: new () => IBrowserFactory;
19
- builtinClis?: string;
20
- userClis?: string;
21
15
  goal?: string | null;
22
16
  site?: string;
23
17
  waitSeconds?: number;
24
18
  top?: number;
25
- register?: boolean;
26
19
  workspace?: string;
27
20
  }
28
21
  export interface GenerateCliResult {
@@ -43,8 +36,6 @@ export interface GenerateCliResult {
43
36
  candidate_count: number;
44
37
  candidates: Array<Pick<SynthesizeCandidateSummary, 'name' | 'strategy' | 'confidence'>>;
45
38
  };
46
- register: RegisterCandidatesResult | null;
47
39
  }
48
40
  export declare function generateCliFromUrl(opts: GenerateCliOptions): Promise<GenerateCliResult>;
49
41
  export declare function renderGenerateSummary(r: GenerateCliResult): string;
50
- export {};
package/dist/generate.js CHANGED
@@ -9,9 +9,6 @@
9
9
  */
10
10
  import { exploreUrl } from './explore.js';
11
11
  import { synthesizeFromExplore } from './synthesize.js';
12
- function registerCandidates(_opts) {
13
- return { ok: true, count: 0 };
14
- }
15
12
  const CAPABILITY_ALIASES = {
16
13
  search: ['search', '搜索', '查找', 'query', 'keyword'],
17
14
  hot: ['hot', '热门', '热榜', '热搜', 'popular', 'top', 'ranking'],
@@ -51,7 +48,10 @@ function selectCandidate(candidates, goal) {
51
48
  return exact;
52
49
  }
53
50
  const lower = (goal ?? '').trim().toLowerCase();
54
- const partial = candidates.find(c => c.name?.toLowerCase().includes(lower) || lower.includes(c.name?.toLowerCase()));
51
+ const partial = candidates.find(c => {
52
+ const cName = c.name?.toLowerCase() ?? '';
53
+ return cName.includes(lower) || lower.includes(cName);
54
+ });
55
55
  return partial ?? candidates[0];
56
56
  }
57
57
  export async function generateCliFromUrl(opts) {
@@ -70,19 +70,6 @@ export async function generateCliFromUrl(opts) {
70
70
  // Step 3: Select best candidate for goal
71
71
  const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
72
72
  const selectedSite = synthesizeResult.site ?? exploreResult.site;
73
- // Step 4: Register (if requested)
74
- let registerResult = null;
75
- if (opts.register !== false && synthesizeResult.candidate_count > 0) {
76
- try {
77
- registerResult = registerCandidates({
78
- target: synthesizeResult.out_dir,
79
- builtinClis: opts.builtinClis,
80
- userClis: opts.userClis,
81
- name: selected?.name,
82
- });
83
- }
84
- catch { }
85
- }
86
73
  const ok = exploreResult.endpoint_count > 0 && synthesizeResult.candidate_count > 0;
87
74
  return {
88
75
  ok,
@@ -106,7 +93,6 @@ export async function generateCliFromUrl(opts) {
106
93
  confidence: c.confidence,
107
94
  })),
108
95
  },
109
- register: registerResult,
110
96
  };
111
97
  }
112
98
  export function renderGenerateSummary(r) {
@@ -127,8 +113,6 @@ export function renderGenerateSummary(r) {
127
113
  for (const c of r.synthesize?.candidates ?? []) {
128
114
  lines.push(` • ${c.name} (${c.strategy}, ${((c.confidence ?? 0) * 100).toFixed(0)}%)`);
129
115
  }
130
- if (r.register)
131
- lines.push(`\nRegistered: ${r.register.count ?? 0}`);
132
116
  const fw = r.explore?.framework ?? {};
133
117
  const fwNames = Object.entries(fw).filter(([, v]) => v).map(([k]) => k);
134
118
  if (fwNames.length)
@@ -0,0 +1,46 @@
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
+ export type HookName = 'onStartup' | 'onBeforeExecute' | 'onAfterExecute';
14
+ export interface HookContext {
15
+ /** Command full name in "site/name" format, or "__startup__" for onStartup */
16
+ command: string;
17
+ /** Coerced and validated arguments */
18
+ args: Record<string, unknown>;
19
+ /** Epoch ms when execution started (set by executeCommand) */
20
+ startedAt?: number;
21
+ /** Epoch ms when execution finished (set by executeCommand) */
22
+ finishedAt?: number;
23
+ /** Error thrown by the command, if execution failed */
24
+ error?: unknown;
25
+ /** Plugins can attach arbitrary data here for cross-hook communication */
26
+ [key: string]: unknown;
27
+ }
28
+ export type HookFn = (ctx: HookContext, result?: unknown) => void | Promise<void>;
29
+ declare global {
30
+ var __opencli_hooks__: Map<HookName, HookFn[]> | undefined;
31
+ }
32
+ /** Register a hook that fires once after all plugins are discovered. */
33
+ export declare function onStartup(fn: HookFn): void;
34
+ /** Register a hook that fires before every command execution. */
35
+ export declare function onBeforeExecute(fn: HookFn): void;
36
+ /** Register a hook that fires after every command execution with the result. */
37
+ export declare function onAfterExecute(fn: HookFn): void;
38
+ /**
39
+ * Trigger all registered handlers for a hook.
40
+ * Each handler is wrapped in try/catch — a failing hook never blocks command execution.
41
+ */
42
+ export declare function emitHook(name: HookName, ctx: HookContext, result?: unknown): Promise<void>;
43
+ /**
44
+ * Remove all registered hooks. Intended for testing only.
45
+ */
46
+ export declare function clearAllHooks(): void;
package/dist/hooks.js ADDED
@@ -0,0 +1,56 @@
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
+ import { log } from './logger.js';
14
+ const _hooks = globalThis.__opencli_hooks__ ??= new Map();
15
+ // ── Registration API (used by plugins) ─────────────────────────────────────
16
+ function addHook(name, fn) {
17
+ const list = _hooks.get(name) ?? [];
18
+ list.push(fn);
19
+ _hooks.set(name, list);
20
+ }
21
+ /** Register a hook that fires once after all plugins are discovered. */
22
+ export function onStartup(fn) {
23
+ addHook('onStartup', fn);
24
+ }
25
+ /** Register a hook that fires before every command execution. */
26
+ export function onBeforeExecute(fn) {
27
+ addHook('onBeforeExecute', fn);
28
+ }
29
+ /** Register a hook that fires after every command execution with the result. */
30
+ export function onAfterExecute(fn) {
31
+ addHook('onAfterExecute', fn);
32
+ }
33
+ // ── Emit API (used internally by opencli core) ─────────────────────────────
34
+ /**
35
+ * Trigger all registered handlers for a hook.
36
+ * Each handler is wrapped in try/catch — a failing hook never blocks command execution.
37
+ */
38
+ export async function emitHook(name, ctx, result) {
39
+ const handlers = _hooks.get(name);
40
+ if (!handlers || handlers.length === 0)
41
+ return;
42
+ for (const fn of handlers) {
43
+ try {
44
+ await fn(ctx, result);
45
+ }
46
+ catch (err) {
47
+ log.warn(`Hook ${name} handler failed: ${err instanceof Error ? err.message : String(err)}`);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Remove all registered hooks. Intended for testing only.
53
+ */
54
+ export function clearAllHooks() {
55
+ _hooks.clear();
56
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for the plugin lifecycle hooks system.
3
+ */
4
+ export {};
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tests for the plugin lifecycle hooks system.
3
+ */
4
+ import { describe, it, expect, beforeEach } from 'vitest';
5
+ import { onStartup, onBeforeExecute, onAfterExecute, emitHook, clearAllHooks, } from './hooks.js';
6
+ beforeEach(() => {
7
+ clearAllHooks();
8
+ });
9
+ describe('hook registration and emission', () => {
10
+ it('onBeforeExecute hook is called with context', async () => {
11
+ const calls = [];
12
+ onBeforeExecute((ctx) => { calls.push({ ...ctx }); });
13
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: { limit: 5 }, startedAt: 100 });
14
+ expect(calls).toHaveLength(1);
15
+ expect(calls[0].command).toBe('test/cmd');
16
+ expect(calls[0].args).toEqual({ limit: 5 });
17
+ expect(calls[0].startedAt).toBe(100);
18
+ });
19
+ it('onAfterExecute hook receives result', async () => {
20
+ const results = [];
21
+ onAfterExecute((_ctx, result) => { results.push(result); });
22
+ const mockResult = [{ title: 'item1' }, { title: 'item2' }];
23
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, mockResult);
24
+ expect(results).toHaveLength(1);
25
+ expect(results[0]).toEqual(mockResult);
26
+ });
27
+ it('onStartup hook fires', async () => {
28
+ let fired = false;
29
+ onStartup(() => { fired = true; });
30
+ await emitHook('onStartup', { command: '__startup__', args: {} });
31
+ expect(fired).toBe(true);
32
+ });
33
+ it('multiple hooks on the same event fire in order', async () => {
34
+ const order = [];
35
+ onBeforeExecute(() => { order.push(1); });
36
+ onBeforeExecute(() => { order.push(2); });
37
+ onBeforeExecute(() => { order.push(3); });
38
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
39
+ expect(order).toEqual([1, 2, 3]);
40
+ });
41
+ it('async hooks are awaited', async () => {
42
+ const order = [];
43
+ onBeforeExecute(async () => {
44
+ await new Promise((r) => setTimeout(r, 10));
45
+ order.push('async-done');
46
+ });
47
+ onBeforeExecute(() => { order.push('sync'); });
48
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
49
+ expect(order).toEqual(['async-done', 'sync']);
50
+ });
51
+ });
52
+ describe('hook error isolation', () => {
53
+ it('failing hook does not prevent other hooks from running', async () => {
54
+ const calls = [];
55
+ onBeforeExecute(() => { calls.push('first'); });
56
+ onBeforeExecute(() => { throw new Error('boom'); });
57
+ onBeforeExecute(() => { calls.push('third'); });
58
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
59
+ // First and third should still run despite the second throwing
60
+ expect(calls).toEqual(['first', 'third']);
61
+ });
62
+ it('async hook rejection does not prevent other hooks', async () => {
63
+ const calls = [];
64
+ onAfterExecute(() => { calls.push('before-reject'); });
65
+ onAfterExecute(async () => { throw new Error('async boom'); });
66
+ onAfterExecute(() => { calls.push('after-reject'); });
67
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, null);
68
+ expect(calls).toEqual(['before-reject', 'after-reject']);
69
+ });
70
+ });
71
+ describe('no-op when no hooks registered', () => {
72
+ it('emitHook with no registered hooks does nothing', async () => {
73
+ // Should not throw
74
+ await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
75
+ await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, []);
76
+ await emitHook('onStartup', { command: '__startup__', args: {} });
77
+ });
78
+ });
79
+ describe('clearAllHooks', () => {
80
+ it('removes all hooks', async () => {
81
+ let called = false;
82
+ onStartup(() => { called = true; });
83
+ clearAllHooks();
84
+ await emitHook('onStartup', { command: '__startup__', args: {} });
85
+ expect(called).toBe(false);
86
+ });
87
+ });
88
+ describe('globalThis singleton', () => {
89
+ it('uses globalThis.__opencli_hooks__ for shared state', () => {
90
+ expect(globalThis.__opencli_hooks__).toBeInstanceOf(Map);
91
+ });
92
+ });
@@ -8,6 +8,42 @@
8
8
  * - stepIntercept (pipeline/steps/intercept.ts)
9
9
  * - stepTap (pipeline/steps/tap.ts)
10
10
  */
11
+ /**
12
+ * Helper: define a non-enumerable property on window.
13
+ * Avoids detection via Object.keys(window) or for..in loops.
14
+ */
15
+ const DEFINE_HIDDEN = `
16
+ function __defHidden(obj, key, val) {
17
+ try {
18
+ Object.defineProperty(obj, key, { value: val, writable: true, enumerable: false, configurable: true });
19
+ } catch { obj[key] = val; }
20
+ }`;
21
+ /**
22
+ * Helper: disguise a patched function so toString() returns native code signature.
23
+ */
24
+ const DISGUISE_FN = `
25
+ function __disguise(fn, name) {
26
+ const nativeStr = 'function ' + name + '() { [native code] }';
27
+ // Override toString on the instance AND patch Function.prototype.toString
28
+ // to handle Function.prototype.toString.call(fn) bypasses.
29
+ const _origToString = Function.prototype.toString;
30
+ const _patchedFns = window.__dFns || (function() {
31
+ const m = new Map();
32
+ Object.defineProperty(window, '__dFns', { value: m, enumerable: false, configurable: true });
33
+ // Patch Function.prototype.toString once to consult the map
34
+ Object.defineProperty(Function.prototype, 'toString', {
35
+ value: function() {
36
+ const override = m.get(this);
37
+ return override !== undefined ? override : _origToString.call(this);
38
+ },
39
+ writable: true, configurable: true
40
+ });
41
+ return m;
42
+ })();
43
+ _patchedFns.set(fn, nativeStr);
44
+ try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
45
+ return fn;
46
+ }`;
11
47
  /**
12
48
  * Generate JavaScript source that installs a fetch/XHR interceptor.
13
49
  * Captured responses are pushed to `window.__opencli_intercepted`.
@@ -19,18 +55,23 @@
19
55
  export function generateInterceptorJs(patternExpr, opts = {}) {
20
56
  const arr = opts.arrayName ?? '__opencli_intercepted';
21
57
  const guard = opts.patchGuard ?? '__opencli_interceptor_patched';
58
+ // Store the current pattern in a separate global so it can be updated
59
+ // without re-patching fetch/XHR (the patchGuard only prevents double-patching).
60
+ const patternVar = `${guard}_pattern`;
22
61
  return `
23
62
  () => {
24
- window.${arr} = window.${arr} || [];
25
- window.${arr}_errors = window.${arr}_errors || [];
26
- const __pattern = ${patternExpr};
63
+ ${DEFINE_HIDDEN}
64
+ ${DISGUISE_FN}
27
65
 
28
- if (!window.${guard}) {
29
- const __checkMatch = (url) => __pattern && url.includes(__pattern);
66
+ if (!window.${arr}) __defHidden(window, '${arr}', []);
67
+ if (!window.${arr}_errors) __defHidden(window, '${arr}_errors', []);
68
+ __defHidden(window, '${patternVar}', ${patternExpr});
69
+ const __checkMatch = (url) => window.${patternVar} && url.includes(window.${patternVar});
30
70
 
71
+ if (!window.${guard}) {
31
72
  // ── Patch fetch ──
32
73
  const __origFetch = window.fetch;
33
- window.fetch = async function(...args) {
74
+ window.fetch = __disguise(async function(...args) {
34
75
  const reqUrl = typeof args[0] === 'string' ? args[0]
35
76
  : (args[0] && args[0].url) || '';
36
77
  const response = await __origFetch.apply(this, args);
@@ -42,28 +83,28 @@ export function generateInterceptorJs(patternExpr, opts = {}) {
42
83
  } catch(e) { window.${arr}_errors.push({ url: reqUrl, error: String(e) }); }
43
84
  }
44
85
  return response;
45
- };
86
+ }, 'fetch');
46
87
 
47
88
  // ── Patch XMLHttpRequest ──
48
89
  const __XHR = XMLHttpRequest.prototype;
49
90
  const __origOpen = __XHR.open;
50
91
  const __origSend = __XHR.send;
51
- __XHR.open = function(method, url) {
52
- this.__opencli_url = String(url);
92
+ __XHR.open = __disguise(function(method, url) {
93
+ Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
53
94
  return __origOpen.apply(this, arguments);
54
- };
55
- __XHR.send = function() {
56
- if (__checkMatch(this.__opencli_url)) {
95
+ }, 'open');
96
+ __XHR.send = __disguise(function() {
97
+ if (__checkMatch(this.__iurl)) {
57
98
  this.addEventListener('load', function() {
58
99
  try {
59
100
  window.${arr}.push(JSON.parse(this.responseText));
60
- } catch(e) { window.${arr}_errors.push({ url: this.__opencli_url, error: String(e) }); }
101
+ } catch(e) { window.${arr}_errors.push({ url: this.__iurl, error: String(e) }); }
61
102
  });
62
103
  }
63
104
  return __origSend.apply(this, arguments);
64
- };
105
+ }, 'send');
65
106
 
66
- window.${guard} = true;
107
+ __defHidden(window, '${guard}', true);
67
108
  }
68
109
  }
69
110
  `;
@@ -94,13 +135,19 @@ export function generateTapInterceptorJs(patternExpr) {
94
135
  let captureResolve;
95
136
  const capturePromise = new Promise(r => { captureResolve = r; });
96
137
  const capturePattern = ${patternExpr};
138
+ function __disguise(fn, name) {
139
+ const s = 'function ' + name + '() { [native code] }';
140
+ Object.defineProperty(fn, 'toString', { value: function() { return s; }, writable: true, configurable: true, enumerable: false });
141
+ try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
142
+ return fn;
143
+ }
97
144
  `,
98
145
  capturedVar: 'captured',
99
146
  promiseVar: 'capturePromise',
100
147
  resolveVar: 'captureResolve',
101
148
  fetchPatch: `
102
149
  const origFetch = window.fetch;
103
- window.fetch = async function(...fetchArgs) {
150
+ window.fetch = __disguise(async function(...fetchArgs) {
104
151
  const resp = await origFetch.apply(this, fetchArgs);
105
152
  try {
106
153
  const url = typeof fetchArgs[0] === 'string' ? fetchArgs[0]
@@ -110,17 +157,17 @@ export function generateTapInterceptorJs(patternExpr) {
110
157
  }
111
158
  } catch {}
112
159
  return resp;
113
- };
160
+ }, 'fetch');
114
161
  `,
115
162
  xhrPatch: `
116
163
  const origXhrOpen = XMLHttpRequest.prototype.open;
117
164
  const origXhrSend = XMLHttpRequest.prototype.send;
118
- XMLHttpRequest.prototype.open = function(method, url) {
119
- this.__tapUrl = String(url);
165
+ XMLHttpRequest.prototype.open = __disguise(function(method, url) {
166
+ Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
120
167
  return origXhrOpen.apply(this, arguments);
121
- };
122
- XMLHttpRequest.prototype.send = function(body) {
123
- if (capturePattern && this.__tapUrl?.includes(capturePattern)) {
168
+ }, 'open');
169
+ XMLHttpRequest.prototype.send = __disguise(function(body) {
170
+ if (capturePattern && this.__iurl?.includes(capturePattern)) {
124
171
  this.addEventListener('load', function() {
125
172
  if (!captured) {
126
173
  try { captured = JSON.parse(this.responseText); captureResolve(); } catch {}
@@ -128,7 +175,7 @@ export function generateTapInterceptorJs(patternExpr) {
128
175
  });
129
176
  }
130
177
  return origXhrSend.apply(this, arguments);
131
- };
178
+ }, 'send');
132
179
  `,
133
180
  restorePatch: `
134
181
  window.fetch = origFetch;
package/dist/main.js CHANGED
@@ -18,6 +18,7 @@ import { fileURLToPath } from 'node:url';
18
18
  import { discoverClis, discoverPlugins } from './discovery.js';
19
19
  import { getCompletions } from './completion.js';
20
20
  import { runCli } from './cli.js';
21
+ import { emitHook } from './hooks.js';
21
22
  const __filename = fileURLToPath(import.meta.url);
22
23
  const __dirname = path.dirname(__filename);
23
24
  const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
@@ -46,4 +47,5 @@ if (getCompIdx !== -1) {
46
47
  process.stdout.write(candidates.join('\n') + '\n');
47
48
  process.exit(0);
48
49
  }
50
+ await emitHook('onStartup', { command: '__startup__', args: {} });
49
51
  runCli(BUILTIN_CLIS, USER_CLIS);
package/dist/output.js CHANGED
@@ -4,6 +4,12 @@
4
4
  import chalk from 'chalk';
5
5
  import Table from 'cli-table3';
6
6
  import yaml from 'js-yaml';
7
+ function normalizeRows(data) {
8
+ return Array.isArray(data) ? data : [data];
9
+ }
10
+ function resolveColumns(rows, opts) {
11
+ return opts.columns ?? Object.keys(rows[0] ?? {});
12
+ }
7
13
  export function render(data, opts = {}) {
8
14
  const fmt = opts.fmt ?? 'table';
9
15
  if (data === null || data === undefined) {
@@ -31,12 +37,12 @@ export function render(data, opts = {}) {
31
37
  }
32
38
  }
33
39
  function renderTable(data, opts) {
34
- const rows = Array.isArray(data) ? data : [data];
40
+ const rows = normalizeRows(data);
35
41
  if (!rows.length) {
36
42
  console.log(chalk.dim('(no data)'));
37
43
  return;
38
44
  }
39
- const columns = opts.columns ?? Object.keys(rows[0]);
45
+ const columns = resolveColumns(rows, opts);
40
46
  const header = columns.map(c => capitalize(c));
41
47
  const table = new Table({
42
48
  head: header.map(h => chalk.bold(h)),
@@ -68,10 +74,10 @@ function renderJson(data) {
68
74
  console.log(JSON.stringify(data, null, 2));
69
75
  }
70
76
  function renderMarkdown(data, opts) {
71
- const rows = Array.isArray(data) ? data : [data];
77
+ const rows = normalizeRows(data);
72
78
  if (!rows.length)
73
79
  return;
74
- const columns = opts.columns ?? Object.keys(rows[0]);
80
+ const columns = resolveColumns(rows, opts);
75
81
  console.log('| ' + columns.join(' | ') + ' |');
76
82
  console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
77
83
  for (const row of rows) {
@@ -79,10 +85,10 @@ function renderMarkdown(data, opts) {
79
85
  }
80
86
  }
81
87
  function renderCsv(data, opts) {
82
- const rows = Array.isArray(data) ? data : [data];
88
+ const rows = normalizeRows(data);
83
89
  if (!rows.length)
84
90
  return;
85
- const columns = opts.columns ?? Object.keys(rows[0]);
91
+ const columns = resolveColumns(rows, opts);
86
92
  console.log(columns.join(','));
87
93
  for (const row of rows) {
88
94
  console.log(columns.map(c => {
@@ -5,7 +5,7 @@ import { getStep } from './registry.js';
5
5
  import { log } from '../logger.js';
6
6
  import { ConfigError } from '../errors.js';
7
7
  /** Steps that interact with the browser and may fail transiently */
8
- const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot', 'scroll']);
8
+ const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot']);
9
9
  export async function executePipeline(page, pipeline, ctx = {}) {
10
10
  const args = ctx.args ?? {};
11
11
  const debug = ctx.debug ?? false;
@@ -3,9 +3,7 @@
3
3
  * Browser interaction primitives.
4
4
  */
5
5
  import { render } from '../template.js';
6
- function isRecord(value) {
7
- return typeof value === 'object' && value !== null && !Array.isArray(value);
8
- }
6
+ import { isRecord } from '../../utils.js';
9
7
  export async function stepNavigate(page, params, data, args) {
10
8
  if (isRecord(params) && 'url' in params) {
11
9
  const url = String(render(params.url, { args, data }));