@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
@@ -1,11 +1,13 @@
1
1
  /**
2
- * Tests for plugin management: install, uninstall, list.
2
+ * Tests for plugin management: install, uninstall, list, and lock file support.
3
3
  */
4
- import { describe, it, expect, afterEach } from 'vitest';
4
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
5
5
  import * as fs from 'node:fs';
6
+ import * as os from 'node:os';
6
7
  import * as path from 'node:path';
7
8
  import { PLUGINS_DIR } from './discovery.js';
8
- import { listPlugins, uninstallPlugin, updatePlugin, _parseSource } from './plugin.js';
9
+ import * as pluginModule from './plugin.js';
10
+ const { LOCK_FILE, _getCommitHash, listPlugins, _readLockFile, _resolveEsbuildBin, uninstallPlugin, updatePlugin, _parseSource, _updateAllPlugins, _validatePluginStructure, _writeLockFile, } = pluginModule;
9
11
  describe('parseSource', () => {
10
12
  it('parses github:user/repo format', () => {
11
13
  const result = _parseSource('github:ByteYue/opencli-plugin-github-trending');
@@ -34,6 +36,131 @@ describe('parseSource', () => {
34
36
  expect(_parseSource('npm:some-package')).toBeNull();
35
37
  });
36
38
  });
39
+ describe('validatePluginStructure', () => {
40
+ const testDir = path.join(PLUGINS_DIR, '__test-validate__');
41
+ beforeEach(() => {
42
+ fs.mkdirSync(testDir, { recursive: true });
43
+ });
44
+ afterEach(() => {
45
+ try {
46
+ fs.rmSync(testDir, { recursive: true });
47
+ }
48
+ catch { }
49
+ });
50
+ it('returns invalid for non-existent directory', () => {
51
+ const res = _validatePluginStructure(path.join(PLUGINS_DIR, '__does_not_exist__'));
52
+ expect(res.valid).toBe(false);
53
+ expect(res.errors[0]).toContain('does not exist');
54
+ });
55
+ it('returns invalid for empty directory', () => {
56
+ const res = _validatePluginStructure(testDir);
57
+ expect(res.valid).toBe(false);
58
+ expect(res.errors[0]).toContain('No command files found');
59
+ });
60
+ it('returns valid for YAML plugin', () => {
61
+ fs.writeFileSync(path.join(testDir, 'cmd.yaml'), 'site: test');
62
+ const res = _validatePluginStructure(testDir);
63
+ expect(res.valid).toBe(true);
64
+ expect(res.errors).toHaveLength(0);
65
+ });
66
+ it('returns valid for JS plugin', () => {
67
+ fs.writeFileSync(path.join(testDir, 'cmd.js'), 'console.log("hi");');
68
+ const res = _validatePluginStructure(testDir);
69
+ expect(res.valid).toBe(true);
70
+ expect(res.errors).toHaveLength(0);
71
+ });
72
+ it('returns invalid for TS plugin without package.json', () => {
73
+ fs.writeFileSync(path.join(testDir, 'cmd.ts'), 'console.log("hi");');
74
+ const res = _validatePluginStructure(testDir);
75
+ expect(res.valid).toBe(false);
76
+ expect(res.errors[0]).toContain('contains .ts files but no package.json');
77
+ });
78
+ it('returns invalid for TS plugin with missing type: module', () => {
79
+ fs.writeFileSync(path.join(testDir, 'cmd.ts'), 'console.log("hi");');
80
+ fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify({ name: 'test' }));
81
+ const res = _validatePluginStructure(testDir);
82
+ expect(res.valid).toBe(false);
83
+ expect(res.errors[0]).toContain('must have "type": "module"');
84
+ });
85
+ it('returns valid for TS plugin with correct package.json', () => {
86
+ fs.writeFileSync(path.join(testDir, 'cmd.ts'), 'console.log("hi");');
87
+ fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify({ type: 'module' }));
88
+ const res = _validatePluginStructure(testDir);
89
+ expect(res.valid).toBe(true);
90
+ expect(res.errors).toHaveLength(0);
91
+ });
92
+ });
93
+ describe('lock file', () => {
94
+ const backupPath = `${LOCK_FILE}.test-backup`;
95
+ let hadOriginal = false;
96
+ beforeEach(() => {
97
+ hadOriginal = fs.existsSync(LOCK_FILE);
98
+ if (hadOriginal) {
99
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
100
+ fs.copyFileSync(LOCK_FILE, backupPath);
101
+ }
102
+ });
103
+ afterEach(() => {
104
+ if (hadOriginal) {
105
+ fs.copyFileSync(backupPath, LOCK_FILE);
106
+ fs.unlinkSync(backupPath);
107
+ return;
108
+ }
109
+ try {
110
+ fs.unlinkSync(LOCK_FILE);
111
+ }
112
+ catch { }
113
+ });
114
+ it('reads empty lock when file does not exist', () => {
115
+ try {
116
+ fs.unlinkSync(LOCK_FILE);
117
+ }
118
+ catch { }
119
+ expect(_readLockFile()).toEqual({});
120
+ });
121
+ it('round-trips lock entries', () => {
122
+ const entries = {
123
+ 'test-plugin': {
124
+ source: 'https://github.com/user/repo.git',
125
+ commitHash: 'abc1234567890def',
126
+ installedAt: '2025-01-01T00:00:00.000Z',
127
+ },
128
+ 'another-plugin': {
129
+ source: 'https://github.com/user/another.git',
130
+ commitHash: 'def4567890123abc',
131
+ installedAt: '2025-02-01T00:00:00.000Z',
132
+ updatedAt: '2025-03-01T00:00:00.000Z',
133
+ },
134
+ };
135
+ _writeLockFile(entries);
136
+ expect(_readLockFile()).toEqual(entries);
137
+ });
138
+ it('handles malformed lock file gracefully', () => {
139
+ fs.mkdirSync(path.dirname(LOCK_FILE), { recursive: true });
140
+ fs.writeFileSync(LOCK_FILE, 'not valid json');
141
+ expect(_readLockFile()).toEqual({});
142
+ });
143
+ });
144
+ describe('getCommitHash', () => {
145
+ it('returns a hash for a git repo', () => {
146
+ const hash = _getCommitHash(process.cwd());
147
+ expect(hash).toBeDefined();
148
+ expect(hash).toMatch(/^[0-9a-f]{40}$/);
149
+ });
150
+ it('returns undefined for non-git directory', () => {
151
+ expect(_getCommitHash(os.tmpdir())).toBeUndefined();
152
+ });
153
+ });
154
+ describe('resolveEsbuildBin', () => {
155
+ it('resolves a usable esbuild executable path', () => {
156
+ const binPath = _resolveEsbuildBin();
157
+ expect(binPath).not.toBeNull();
158
+ expect(typeof binPath).toBe('string');
159
+ expect(fs.existsSync(binPath)).toBe(true);
160
+ // On Windows the resolved path ends with 'esbuild.cmd', on Unix 'esbuild'
161
+ expect(binPath).toMatch(/esbuild(\.cmd)?$/);
162
+ });
163
+ });
37
164
  describe('listPlugins', () => {
38
165
  const testDir = path.join(PLUGINS_DIR, '__test-list-plugin__');
39
166
  afterEach(() => {
@@ -50,6 +177,24 @@ describe('listPlugins', () => {
50
177
  expect(found).toBeDefined();
51
178
  expect(found.commands).toContain('hello');
52
179
  });
180
+ it('includes version metadata from the lock file', () => {
181
+ fs.mkdirSync(testDir, { recursive: true });
182
+ fs.writeFileSync(path.join(testDir, 'hello.yaml'), 'site: test\nname: hello\n');
183
+ const lock = _readLockFile();
184
+ lock['__test-list-plugin__'] = {
185
+ source: 'https://github.com/user/repo.git',
186
+ commitHash: 'abcdef1234567890abcdef1234567890abcdef12',
187
+ installedAt: '2025-01-01T00:00:00.000Z',
188
+ };
189
+ _writeLockFile(lock);
190
+ const plugins = listPlugins();
191
+ const found = plugins.find(p => p.name === '__test-list-plugin__');
192
+ expect(found).toBeDefined();
193
+ expect(found.version).toBe('abcdef1');
194
+ expect(found.installedAt).toBe('2025-01-01T00:00:00.000Z');
195
+ delete lock['__test-list-plugin__'];
196
+ _writeLockFile(lock);
197
+ });
53
198
  it('returns empty array when no plugins dir', () => {
54
199
  // listPlugins should handle missing dir gracefully
55
200
  const plugins = listPlugins();
@@ -70,6 +215,19 @@ describe('uninstallPlugin', () => {
70
215
  uninstallPlugin('__test-uninstall__');
71
216
  expect(fs.existsSync(testDir)).toBe(false);
72
217
  });
218
+ it('removes lock entry on uninstall', () => {
219
+ fs.mkdirSync(testDir, { recursive: true });
220
+ fs.writeFileSync(path.join(testDir, 'test.yaml'), 'site: test');
221
+ const lock = _readLockFile();
222
+ lock['__test-uninstall__'] = {
223
+ source: 'https://github.com/user/repo.git',
224
+ commitHash: 'abc123',
225
+ installedAt: '2025-01-01T00:00:00.000Z',
226
+ };
227
+ _writeLockFile(lock);
228
+ uninstallPlugin('__test-uninstall__');
229
+ expect(_readLockFile()['__test-uninstall__']).toBeUndefined();
230
+ });
73
231
  it('throws for non-existent plugin', () => {
74
232
  expect(() => uninstallPlugin('__nonexistent__')).toThrow('not installed');
75
233
  });
@@ -79,3 +237,61 @@ describe('updatePlugin', () => {
79
237
  expect(() => updatePlugin('__nonexistent__')).toThrow('not installed');
80
238
  });
81
239
  });
240
+ vi.mock('node:child_process', () => {
241
+ return {
242
+ execFileSync: vi.fn((_cmd, args, opts) => {
243
+ if (Array.isArray(args) && args[0] === 'rev-parse' && args[1] === 'HEAD') {
244
+ if (opts?.cwd === os.tmpdir()) {
245
+ throw new Error('not a git repository');
246
+ }
247
+ return '1234567890abcdef1234567890abcdef12345678\n';
248
+ }
249
+ if (opts && opts.cwd && String(opts.cwd).endsWith('plugin-b')) {
250
+ throw new Error('Network error');
251
+ }
252
+ return '';
253
+ }),
254
+ execSync: vi.fn(() => ''),
255
+ };
256
+ });
257
+ describe('updateAllPlugins', () => {
258
+ const testDirA = path.join(PLUGINS_DIR, 'plugin-a');
259
+ const testDirB = path.join(PLUGINS_DIR, 'plugin-b');
260
+ const testDirC = path.join(PLUGINS_DIR, 'plugin-c');
261
+ beforeEach(() => {
262
+ fs.mkdirSync(testDirA, { recursive: true });
263
+ fs.mkdirSync(testDirB, { recursive: true });
264
+ fs.mkdirSync(testDirC, { recursive: true });
265
+ fs.writeFileSync(path.join(testDirA, 'cmd.yaml'), 'site: a');
266
+ fs.writeFileSync(path.join(testDirB, 'cmd.yaml'), 'site: b');
267
+ fs.writeFileSync(path.join(testDirC, 'cmd.yaml'), 'site: c');
268
+ });
269
+ afterEach(() => {
270
+ try {
271
+ fs.rmSync(testDirA, { recursive: true });
272
+ }
273
+ catch { }
274
+ try {
275
+ fs.rmSync(testDirB, { recursive: true });
276
+ }
277
+ catch { }
278
+ try {
279
+ fs.rmSync(testDirC, { recursive: true });
280
+ }
281
+ catch { }
282
+ vi.clearAllMocks();
283
+ });
284
+ it('collects successes and failures without throwing', () => {
285
+ const results = _updateAllPlugins();
286
+ const resA = results.find(r => r.name === 'plugin-a');
287
+ const resB = results.find(r => r.name === 'plugin-b');
288
+ const resC = results.find(r => r.name === 'plugin-c');
289
+ expect(resA).toBeDefined();
290
+ expect(resA.success).toBe(true);
291
+ expect(resB).toBeDefined();
292
+ expect(resB.success).toBe(false);
293
+ expect(resB.error).toContain('Network error');
294
+ expect(resC).toBeDefined();
295
+ expect(resC.success).toBe(true);
296
+ });
297
+ });
package/dist/record.js CHANGED
@@ -17,7 +17,8 @@ import * as readline from 'node:readline';
17
17
  import chalk from 'chalk';
18
18
  import yaml from 'js-yaml';
19
19
  import { sendCommand } from './browser/daemon-client.js';
20
- import { VOLATILE_PARAMS, SEARCH_PARAMS, PAGINATION_PARAMS, FIELD_ROLES, } from './constants.js';
20
+ import { SEARCH_PARAMS, PAGINATION_PARAMS, FIELD_ROLES } from './constants.js';
21
+ import { urlToPattern, findArrayPath, inferCapabilityName, inferStrategy, detectAuthFromContent, classifyQueryParams, } from './analysis.js';
21
22
  // ── Interceptor JS ─────────────────────────────────────────────────────────
22
23
  /**
23
24
  * Generates a full-capture interceptor that stores {url, method, status, body}
@@ -110,92 +111,6 @@ function generateReadRecordedJs() {
110
111
  `;
111
112
  }
112
113
  // ── Analysis helpers ───────────────────────────────────────────────────────
113
- function urlToPattern(url) {
114
- try {
115
- const p = new URL(url);
116
- const pathNorm = p.pathname
117
- .replace(/\/\d+/g, '/{id}')
118
- .replace(/\/[0-9a-fA-F]{8,}/g, '/{hex}')
119
- .replace(/\/BV[a-zA-Z0-9]{10}/g, '/{bvid}');
120
- const params = [];
121
- p.searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
122
- params.push(k); });
123
- return `${p.host}${pathNorm}${params.length ? '?' + params.sort().map(k => `${k}={}`).join('&') : ''}`;
124
- }
125
- catch {
126
- return url;
127
- }
128
- }
129
- function detectAuthIndicators(url, body) {
130
- const indicators = [];
131
- // Heuristic: if body contains sign/w_rid fields, it's likely signed
132
- if (body && typeof body === 'object') {
133
- const keys = Object.keys(body).map(k => k.toLowerCase());
134
- if (keys.some(k => k.includes('sign') || k === 'w_rid' || k.includes('token'))) {
135
- indicators.push('signature');
136
- }
137
- }
138
- // Check URL for common auth patterns
139
- if (url.includes('/wbi/') || url.includes('w_rid='))
140
- indicators.push('signature');
141
- if (url.includes('bearer') || url.includes('access_token'))
142
- indicators.push('bearer');
143
- return indicators;
144
- }
145
- function findArrayPath(obj, depth = 0) {
146
- if (depth > 5 || !obj || typeof obj !== 'object')
147
- return null;
148
- if (Array.isArray(obj)) {
149
- if (obj.length >= 2 && obj.some(i => i && typeof i === 'object' && !Array.isArray(i))) {
150
- return { path: '', items: obj };
151
- }
152
- return null;
153
- }
154
- let best = null;
155
- for (const [key, val] of Object.entries(obj)) {
156
- const found = findArrayPath(val, depth + 1);
157
- if (found) {
158
- const fullPath = found.path ? `${key}.${found.path}` : key;
159
- const candidate = { path: fullPath, items: found.items };
160
- if (!best || candidate.items.length > best.items.length)
161
- best = candidate;
162
- }
163
- }
164
- return best;
165
- }
166
- function inferCapabilityName(url) {
167
- const u = url.toLowerCase();
168
- if (u.includes('hot') || u.includes('popular') || u.includes('ranking') || u.includes('trending'))
169
- return 'hot';
170
- if (u.includes('search'))
171
- return 'search';
172
- if (u.includes('feed') || u.includes('timeline') || u.includes('dynamic'))
173
- return 'feed';
174
- if (u.includes('comment') || u.includes('reply'))
175
- return 'comments';
176
- if (u.includes('history'))
177
- return 'history';
178
- if (u.includes('profile') || u.includes('me'))
179
- return 'me';
180
- if (u.includes('favorite') || u.includes('collect') || u.includes('bookmark'))
181
- return 'favorite';
182
- try {
183
- const segs = new URL(url).pathname
184
- .split('/')
185
- .filter(s => s && !s.match(/^\d+$/) && !s.match(/^[0-9a-f]{8,}$/i) && !s.match(/^v\d+$/));
186
- if (segs.length)
187
- return segs[segs.length - 1].replace(/[^a-z0-9]/gi, '_').toLowerCase();
188
- }
189
- catch { }
190
- return 'data';
191
- }
192
- function inferStrategy(authIndicators) {
193
- if (authIndicators.includes('signature'))
194
- return 'intercept';
195
- if (authIndicators.includes('bearer') || authIndicators.includes('csrf'))
196
- return 'header';
197
- return 'cookie';
198
- }
199
114
  function scoreRequest(req, arrayResult) {
200
115
  let s = 0;
201
116
  if (arrayResult) {
@@ -247,14 +162,7 @@ function buildRecordedYaml(site, pageUrl, req, capName, arrayResult, authIndicat
247
162
  ? ''
248
163
  : itemPath.split('.').map(p => `?.${p}`).join('');
249
164
  // Detect search/limit/page params (must be before fetch URL building to use hasSearch/hasPage)
250
- const qp = [];
251
- try {
252
- new URL(req.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
253
- qp.push(k); });
254
- }
255
- catch { }
256
- const hasSearch = qp.some(p => SEARCH_PARAMS.has(p));
257
- const hasPage = qp.some(p => PAGINATION_PARAMS.has(p));
165
+ const { hasSearch, hasPagination: hasPage } = classifyQueryParams(req.url);
258
166
  // Build evaluate script
259
167
  const mapLines = Object.entries(detectedFields)
260
168
  .map(([role, field]) => ` ${role}: item?.${field}`)
@@ -363,10 +271,9 @@ export async function recordSession(opts) {
363
271
  let stopped = false;
364
272
  const stop = () => { stopped = true; };
365
273
  const { promise: enterPromise, cleanup: cleanupEnter } = waitForEnter();
366
- const enterRace = enterPromise.then(stop);
274
+ enterPromise.then(stop);
367
275
  const timeoutPromise = new Promise(r => setTimeout(() => {
368
276
  stop();
369
- cleanupEnter(); // close readline to prevent process from hanging
370
277
  r();
371
278
  }, timeoutMs));
372
279
  // Poll loop: drain captured data + inject interceptor into any new tabs
@@ -394,6 +301,7 @@ export async function recordSession(opts) {
394
301
  }
395
302
  }, pollMs);
396
303
  await Promise.race([enterPromise, timeoutPromise]);
304
+ cleanupEnter(); // Always clean up readline to prevent process from hanging
397
305
  clearInterval(pollInterval);
398
306
  // Final drain from all known tabs
399
307
  for (const tabId of injectedTabs) {
@@ -476,7 +384,7 @@ function analyzeAndWrite(site, pageUrl, requests, outDir) {
476
384
  const scored = [];
477
385
  for (const [pattern, req] of seen) {
478
386
  const arrayResult = findArrayPath(req.body);
479
- const authIndicators = detectAuthIndicators(req.url, req.body);
387
+ const authIndicators = detectAuthFromContent(req.url, req.body);
480
388
  const score = scoreRequest(req, arrayResult);
481
389
  if (score > 0) {
482
390
  scored.push({ req, pattern, arrayResult, authIndicators, score });
@@ -9,3 +9,5 @@
9
9
  export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
10
10
  export type { CliCommand, Arg, CliOptions } from './registry.js';
11
11
  export type { IPage } from './types.js';
12
+ export { onStartup, onBeforeExecute, onAfterExecute } from './hooks.js';
13
+ export type { HookFn, HookContext, HookName } from './hooks.js';
@@ -7,3 +7,4 @@
7
7
  * plugins are dynamically imported during discoverPlugins().
8
8
  */
9
9
  export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
10
+ export { onStartup, onBeforeExecute, onAfterExecute } from './hooks.js';
@@ -18,6 +18,10 @@ export interface Arg {
18
18
  help?: string;
19
19
  choices?: string[];
20
20
  }
21
+ export interface RequiredEnv {
22
+ name: string;
23
+ help?: string;
24
+ }
21
25
  export type CommandArgs = Record<string, any>;
22
26
  export interface CliCommand {
23
27
  site: string;
@@ -34,6 +38,7 @@ export interface CliCommand {
34
38
  /** Origin of this command: 'yaml', 'ts', or plugin name. */
35
39
  source?: string;
36
40
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
41
+ requiredEnv?: RequiredEnv[];
37
42
  /**
38
43
  * Control pre-navigation for cookie/header context before command execution.
39
44
  *
@@ -65,5 +70,3 @@ export declare function getRegistry(): Map<string, CliCommand>;
65
70
  export declare function fullName(cmd: CliCommand): string;
66
71
  export declare function strategyLabel(cmd: CliCommand): string;
67
72
  export declare function registerCommand(cmd: CliCommand): void;
68
- export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
69
- export type { SerializedArg } from './serialization.js';
package/dist/registry.js CHANGED
@@ -26,6 +26,7 @@ export function cli(opts) {
26
26
  pipeline: opts.pipeline,
27
27
  timeoutSeconds: opts.timeoutSeconds,
28
28
  footerExtra: opts.footerExtra,
29
+ requiredEnv: opts.requiredEnv,
29
30
  navigateBefore: opts.navigateBefore,
30
31
  };
31
32
  const key = fullName(cmd);
@@ -44,5 +45,3 @@ export function strategyLabel(cmd) {
44
45
  export function registerCommand(cmd) {
45
46
  _registry.set(fullName(cmd), cmd);
46
47
  }
47
- // Re-export serialization helpers from their dedicated module
48
- export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
package/dist/runtime.d.ts CHANGED
@@ -7,7 +7,6 @@ export declare function getBrowserFactory(): new () => IBrowserFactory;
7
7
  export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
8
8
  export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
9
9
  export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;
10
- export declare const DEFAULT_BROWSER_SMOKE_TIMEOUT: number;
11
10
  /**
12
11
  * Timeout with seconds unit. Used for high-level command timeouts.
13
12
  */
package/dist/runtime.js CHANGED
@@ -7,10 +7,20 @@ import { TimeoutError } from './errors.js';
7
7
  export function getBrowserFactory() {
8
8
  return (process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge);
9
9
  }
10
- export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
11
- export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
12
- export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_EXPLORE_TIMEOUT ?? '120', 10);
13
- export const DEFAULT_BROWSER_SMOKE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_SMOKE_TIMEOUT ?? '60', 10);
10
+ function parseEnvTimeout(envVar, fallback) {
11
+ const raw = process.env[envVar];
12
+ if (raw === undefined)
13
+ return fallback;
14
+ const parsed = parseInt(raw, 10);
15
+ if (Number.isNaN(parsed) || parsed <= 0) {
16
+ console.error(`[runtime] Invalid ${envVar}="${raw}", using default ${fallback}s`);
17
+ return fallback;
18
+ }
19
+ return parsed;
20
+ }
21
+ export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_CONNECT_TIMEOUT', 30);
22
+ export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_COMMAND_TIMEOUT', 60);
23
+ export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_EXPLORE_TIMEOUT', 120);
14
24
  /**
15
25
  * Timeout with seconds unit. Used for high-level command timeouts.
16
26
  */
@@ -1,18 +1,11 @@
1
1
  /**
2
2
  * Aria snapshot formatter: parses Playwright MCP snapshot text into clean format.
3
3
  *
4
- * Multi-pass pipeline:
5
- * 1. Parse & filter: strip annotations, metadata, noise roles, ads, decorators
6
- * 2. Deduplicate: generic/text child matching parent label
7
- * 3. Deduplicate: heading + link with identical labels
8
- * 4. Deduplicate: nested identical links
9
- * 5. Prune: empty containers (iterative bottom-up)
10
- * 6. Collapse: single-child containers
4
+ * 4-pass pipeline:
5
+ * 1. Parse & filter: strip annotations, metadata, noise, ads, boilerplate subtrees
6
+ * 2. Deduplicate: generic/text parent match, heading+link, nested identical links
7
+ * 3. Prune: empty containers (iterative bottom-up)
8
+ * 4. Collapse: single-child containers
11
9
  */
12
- export interface FormatOptions {
13
- interactive?: boolean;
14
- compact?: boolean;
15
- maxDepth?: number;
16
- maxTextLength?: number;
17
- }
18
- export declare function formatSnapshot(raw: string, opts?: FormatOptions): string;
10
+ import type { SnapshotOptions } from './types.js';
11
+ export declare function formatSnapshot(raw: string, opts?: SnapshotOptions): string;