@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
@@ -14,7 +14,9 @@ import { formatSnapshot } from '../snapshotFormatter.js';
14
14
  import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
15
15
  import { sendCommand } from './daemon-client.js';
16
16
  import { wrapForEval } from './utils.js';
17
+ import { saveBase64ToFile } from '../utils.js';
17
18
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
19
+ import { generateStealthJs } from './stealth.js';
18
20
  import {
19
21
  clickJs,
20
22
  typeTextJs,
@@ -35,33 +37,44 @@ export class Page implements IPage {
35
37
  /** Active tab ID, set after navigate and used in all subsequent commands */
36
38
  private _tabId: number | undefined;
37
39
 
38
- /** Helper: spread tabId into command params if we have one */
39
- private _tabOpt(): { tabId: number } | Record<string, never> {
40
- return this._tabId !== undefined ? { tabId: this._tabId } : {};
40
+ /** Helper: spread workspace into command params */
41
+ private _wsOpt(): { workspace: string } {
42
+ return { workspace: this.workspace };
41
43
  }
42
44
 
43
- private _workspaceOpt(): { workspace: string } {
44
- return { workspace: this.workspace };
45
+ /** Helper: spread workspace + tabId into command params */
46
+ private _cmdOpts(): Record<string, unknown> {
47
+ return {
48
+ workspace: this.workspace,
49
+ ...(this._tabId !== undefined && { tabId: this._tabId }),
50
+ };
45
51
  }
46
52
 
47
53
  async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
48
54
  const result = await sendCommand('navigate', {
49
55
  url,
50
- ...this._workspaceOpt(),
51
- ...this._tabOpt(),
56
+ ...this._cmdOpts(),
52
57
  }) as { tabId?: number };
53
58
  // Remember the tabId for subsequent exec calls
54
59
  if (result?.tabId) {
55
60
  this._tabId = result.tabId;
56
61
  }
62
+ // Inject stealth anti-detection patches (guard flag prevents double-injection).
63
+ try {
64
+ await sendCommand('exec', {
65
+ code: generateStealthJs(),
66
+ ...this._cmdOpts(),
67
+ });
68
+ } catch {
69
+ // Non-fatal: stealth is best-effort
70
+ }
57
71
  // Smart settle: use DOM stability detection instead of fixed sleep.
58
72
  // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
59
73
  if (options?.waitUntil !== 'none') {
60
74
  const maxMs = options?.settleMs ?? 1000;
61
75
  await sendCommand('exec', {
62
76
  code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
63
- ...this._workspaceOpt(),
64
- ...this._tabOpt(),
77
+ ...this._cmdOpts(),
65
78
  });
66
79
  }
67
80
  }
@@ -69,7 +82,7 @@ export class Page implements IPage {
69
82
  /** Close the automation window in the extension */
70
83
  async closeWindow(): Promise<void> {
71
84
  try {
72
- await sendCommand('close-window', { ...this._workspaceOpt() });
85
+ await sendCommand('close-window', { ...this._wsOpt() });
73
86
  } catch {
74
87
  // Window may already be closed or daemon may be down
75
88
  }
@@ -77,11 +90,11 @@ export class Page implements IPage {
77
90
 
78
91
  async evaluate(js: string): Promise<unknown> {
79
92
  const code = wrapForEval(js);
80
- return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
93
+ return sendCommand('exec', { code, ...this._cmdOpts() });
81
94
  }
82
95
 
83
96
  async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
84
- const result = await sendCommand('cookies', { ...this._workspaceOpt(), ...opts });
97
+ const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
85
98
  return Array.isArray(result) ? result : [];
86
99
  }
87
100
 
@@ -97,7 +110,7 @@ export class Page implements IPage {
97
110
  });
98
111
 
99
112
  try {
100
- const result = await sendCommand('exec', { code: snapshotJs, ...this._workspaceOpt(), ...this._tabOpt() });
113
+ const result = await sendCommand('exec', { code: snapshotJs, ...this._cmdOpts() });
101
114
  // The advanced engine already produces a clean, pruned, LLM-friendly output.
102
115
  // Do NOT pass through formatSnapshot — its format is incompatible.
103
116
  return result;
@@ -137,7 +150,7 @@ export class Page implements IPage {
137
150
  return buildTree(document.body, 0);
138
151
  })()
139
152
  `;
140
- const raw = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
153
+ const raw = await sendCommand('exec', { code, ...this._cmdOpts() });
141
154
  if (opts.raw) return raw;
142
155
  if (typeof raw === 'string') return formatSnapshot(raw, opts);
143
156
  return raw;
@@ -145,27 +158,27 @@ export class Page implements IPage {
145
158
 
146
159
  async click(ref: string): Promise<void> {
147
160
  const code = clickJs(ref);
148
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
161
+ await sendCommand('exec', { code, ...this._cmdOpts() });
149
162
  }
150
163
 
151
164
  async typeText(ref: string, text: string): Promise<void> {
152
165
  const code = typeTextJs(ref, text);
153
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
166
+ await sendCommand('exec', { code, ...this._cmdOpts() });
154
167
  }
155
168
 
156
169
  async pressKey(key: string): Promise<void> {
157
170
  const code = pressKeyJs(key);
158
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
171
+ await sendCommand('exec', { code, ...this._cmdOpts() });
159
172
  }
160
173
 
161
174
  async scrollTo(ref: string): Promise<unknown> {
162
175
  const code = scrollToRefJs(ref);
163
- return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
176
+ return sendCommand('exec', { code, ...this._cmdOpts() });
164
177
  }
165
178
 
166
179
  async getFormState(): Promise<Record<string, unknown>> {
167
180
  const code = getFormStateJs();
168
- return (await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() })) as Record<string, unknown>;
181
+ return (await sendCommand('exec', { code, ...this._cmdOpts() })) as Record<string, unknown>;
169
182
  }
170
183
 
171
184
  async wait(options: number | WaitOptions): Promise<void> {
@@ -173,42 +186,42 @@ export class Page implements IPage {
173
186
  await new Promise(resolve => setTimeout(resolve, options * 1000));
174
187
  return;
175
188
  }
176
- if (options.time) {
189
+ if (typeof options.time === 'number') {
177
190
  await new Promise(resolve => setTimeout(resolve, options.time! * 1000));
178
191
  return;
179
192
  }
180
193
  if (options.text) {
181
194
  const timeout = (options.timeout ?? 30) * 1000;
182
195
  const code = waitForTextJs(options.text, timeout);
183
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
196
+ await sendCommand('exec', { code, ...this._cmdOpts() });
184
197
  }
185
198
  }
186
199
 
187
200
  async tabs(): Promise<unknown[]> {
188
- const result = await sendCommand('tabs', { op: 'list', ...this._workspaceOpt() });
201
+ const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
189
202
  return Array.isArray(result) ? result : [];
190
203
  }
191
204
 
192
205
  async closeTab(index?: number): Promise<void> {
193
- await sendCommand('tabs', { op: 'close', ...this._workspaceOpt(), ...(index !== undefined ? { index } : {}) });
206
+ await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
194
207
  // Invalidate cached tabId — the closed tab might have been our active one.
195
208
  // We can't know for sure (close-by-index doesn't return tabId), so reset.
196
209
  this._tabId = undefined;
197
210
  }
198
211
 
199
212
  async newTab(): Promise<void> {
200
- const result = await sendCommand('tabs', { op: 'new', ...this._workspaceOpt() }) as { tabId?: number };
213
+ const result = await sendCommand('tabs', { op: 'new', ...this._wsOpt() }) as { tabId?: number };
201
214
  if (result?.tabId) this._tabId = result.tabId;
202
215
  }
203
216
 
204
217
  async selectTab(index: number): Promise<void> {
205
- const result = await sendCommand('tabs', { op: 'select', index, ...this._workspaceOpt() }) as { selected?: number };
218
+ const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() }) as { selected?: number };
206
219
  if (result?.selected) this._tabId = result.selected;
207
220
  }
208
221
 
209
222
  async networkRequests(includeStatic: boolean = false): Promise<unknown[]> {
210
223
  const code = networkRequestsJs(includeStatic);
211
- const result = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
224
+ const result = await sendCommand('exec', { code, ...this._cmdOpts() });
212
225
  return Array.isArray(result) ? result : [];
213
226
  }
214
227
 
@@ -230,19 +243,14 @@ export class Page implements IPage {
230
243
  */
231
244
  async screenshot(options: ScreenshotOptions = {}): Promise<string> {
232
245
  const base64 = await sendCommand('screenshot', {
233
- ...this._workspaceOpt(),
246
+ ...this._cmdOpts(),
234
247
  format: options.format,
235
248
  quality: options.quality,
236
249
  fullPage: options.fullPage,
237
- ...this._tabOpt(),
238
250
  }) as string;
239
251
 
240
252
  if (options.path) {
241
- const fs = await import('node:fs');
242
- const path = await import('node:path');
243
- const dir = path.dirname(options.path);
244
- await fs.promises.mkdir(dir, { recursive: true });
245
- await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
253
+ await saveBase64ToFile(base64, options.path);
246
254
  }
247
255
 
248
256
  return base64;
@@ -250,14 +258,14 @@ export class Page implements IPage {
250
258
 
251
259
  async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
252
260
  const code = scrollJs(direction, amount);
253
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
261
+ await sendCommand('exec', { code, ...this._cmdOpts() });
254
262
  }
255
263
 
256
264
  async autoScroll(options: { times?: number; delayMs?: number } = {}): Promise<void> {
257
265
  const times = options.times ?? 3;
258
266
  const delayMs = options.delayMs ?? 2000;
259
267
  const code = autoScrollJs(times, delayMs);
260
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
268
+ await sendCommand('exec', { code, ...this._cmdOpts() });
261
269
  }
262
270
 
263
271
  async installInterceptor(pattern: string): Promise<void> {
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Stealth anti-detection module.
3
+ *
4
+ * Generates JS code that patches browser globals to hide automation
5
+ * fingerprints (e.g. navigator.webdriver, missing chrome object, empty
6
+ * plugin list). Injected before page scripts run so that websites cannot
7
+ * detect CDP / extension-based control.
8
+ *
9
+ * Inspired by puppeteer-extra-plugin-stealth.
10
+ */
11
+
12
+ /**
13
+ * Return a self-contained JS string that, when evaluated in a page context,
14
+ * applies all stealth patches. Safe to call multiple times — the guard flag
15
+ * ensures patches are applied only once.
16
+ */
17
+ export function generateStealthJs(): string {
18
+ return `
19
+ (() => {
20
+ // Guard: prevent double-injection across separate CDP evaluations.
21
+ // We cannot use a closure variable (each eval is a fresh scope), and
22
+ // window properties / Symbols are discoverable by anti-bot scripts.
23
+ // Instead, stash the flag in a non-enumerable getter on a built-in
24
+ // prototype that fingerprinters are unlikely to scan.
25
+ const _gProto = EventTarget.prototype;
26
+ const _gKey = '__lsn'; // looks like an internal listener cache
27
+ if (_gProto[_gKey]) return 'skipped';
28
+ try {
29
+ Object.defineProperty(_gProto, _gKey, { value: true, enumerable: false, configurable: true });
30
+ } catch {}
31
+
32
+ // 1. navigator.webdriver → false
33
+ // Most common check; Playwright/Puppeteer/CDP set this to true.
34
+ // Real Chrome returns false (not undefined) — returning undefined is
35
+ // itself a detection signal for advanced fingerprinters.
36
+ try {
37
+ Object.defineProperty(navigator, 'webdriver', {
38
+ get: () => false,
39
+ configurable: true,
40
+ });
41
+ } catch {}
42
+
43
+ // 2. window.chrome stub
44
+ // Real Chrome exposes window.chrome with runtime, loadTimes, csi.
45
+ // Headless/automated Chrome may not have it.
46
+ try {
47
+ if (!window.chrome) {
48
+ window.chrome = {
49
+ runtime: {
50
+ onConnect: { addListener: () => {}, removeListener: () => {} },
51
+ onMessage: { addListener: () => {}, removeListener: () => {} },
52
+ },
53
+ loadTimes: () => ({}),
54
+ csi: () => ({}),
55
+ };
56
+ }
57
+ } catch {}
58
+
59
+ // 3. navigator.plugins — fake population only if empty
60
+ // Real user browser already has plugins; only patch in automated/headless
61
+ // contexts where the list is empty (overwriting real plugins with fakes
62
+ // would be counterproductive and detectable).
63
+ try {
64
+ if (!navigator.plugins || navigator.plugins.length === 0) {
65
+ const fakePlugins = [
66
+ { name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
67
+ { name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
68
+ { name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
69
+ { name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
70
+ { name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: '' },
71
+ ];
72
+ fakePlugins.item = (i) => fakePlugins[i] || null;
73
+ fakePlugins.namedItem = (n) => fakePlugins.find(p => p.name === n) || null;
74
+ fakePlugins.refresh = () => {};
75
+ Object.defineProperty(navigator, 'plugins', {
76
+ get: () => fakePlugins,
77
+ configurable: true,
78
+ });
79
+ }
80
+ } catch {}
81
+
82
+ // 4. navigator.languages — guarantee non-empty
83
+ // Some automated contexts return undefined or empty array.
84
+ try {
85
+ if (!navigator.languages || navigator.languages.length === 0) {
86
+ Object.defineProperty(navigator, 'languages', {
87
+ get: () => ['en-US', 'en'],
88
+ configurable: true,
89
+ });
90
+ }
91
+ } catch {}
92
+
93
+ // 5. Permissions.query — normalize notification permission
94
+ // Headless Chrome throws on Permissions.query({ name: 'notifications' }).
95
+ try {
96
+ const origQuery = window.Permissions?.prototype?.query;
97
+ if (origQuery) {
98
+ window.Permissions.prototype.query = function (parameters) {
99
+ if (parameters?.name === 'notifications') {
100
+ return Promise.resolve({ state: Notification.permission, onchange: null });
101
+ }
102
+ return origQuery.call(this, parameters);
103
+ };
104
+ }
105
+ } catch {}
106
+
107
+ // 6. Clean automation artifacts
108
+ // Remove properties left by Playwright, Puppeteer, or CDP injection.
109
+ try {
110
+ delete window.__playwright;
111
+ delete window.__puppeteer;
112
+ // ChromeDriver injects cdc_ prefixed globals; the suffix varies by version,
113
+ // so scan window for any matching property rather than hardcoding names.
114
+ for (const prop of Object.getOwnPropertyNames(window)) {
115
+ if (prop.startsWith('cdc_') || prop.startsWith('__cdc_')) {
116
+ try { delete window[prop]; } catch {}
117
+ }
118
+ }
119
+ } catch {}
120
+
121
+ // 7. CDP stack trace cleanup
122
+ // Runtime.evaluate injects scripts whose source URLs appear in Error
123
+ // stack traces (e.g. __puppeteer_evaluation_script__, pptr:, debugger://).
124
+ // Websites detect automation by doing: new Error().stack and inspecting it.
125
+ // We override the stack property getter on Error.prototype to filter them.
126
+ // Note: Error.prepareStackTrace is V8/Node-only and not available in
127
+ // browser page context, so we use a property descriptor approach instead.
128
+ // We use generic protocol patterns instead of product-specific names to
129
+ // also catch our own injected code frames without leaking identifiers.
130
+ try {
131
+ const _origDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
132
+ const _cdpPatterns = [
133
+ 'puppeteer_evaluation_script',
134
+ 'pptr:',
135
+ 'debugger://',
136
+ '__playwright',
137
+ '__puppeteer',
138
+ ];
139
+ if (_origDescriptor && _origDescriptor.get) {
140
+ Object.defineProperty(Error.prototype, 'stack', {
141
+ get: function () {
142
+ const raw = _origDescriptor.get.call(this);
143
+ if (typeof raw !== 'string') return raw;
144
+ return raw.split('\\n').filter(line =>
145
+ !_cdpPatterns.some(p => line.includes(p))
146
+ ).join('\\n');
147
+ },
148
+ configurable: true,
149
+ });
150
+ }
151
+ } catch {}
152
+
153
+ return 'applied';
154
+ })()
155
+ `;
156
+ }
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, it, expect, vi } from 'vitest';
2
- import { BrowserBridge, __test__ } from './browser/index.js';
2
+ import { BrowserBridge, __test__, generateStealthJs } from './browser/index.js';
3
3
  import * as daemonClient from './browser/daemon-client.js';
4
4
 
5
5
  describe('browser helpers', () => {
@@ -133,3 +133,53 @@ describe('BrowserBridge state', () => {
133
133
  await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
134
134
  });
135
135
  });
136
+
137
+ describe('stealth anti-detection', () => {
138
+ it('generates non-empty JS string', () => {
139
+ const js = generateStealthJs();
140
+ expect(typeof js).toBe('string');
141
+ expect(js.length).toBeGreaterThan(100);
142
+ });
143
+
144
+ it('contains all 7 anti-detection patches', () => {
145
+ const js = generateStealthJs();
146
+ // 1. webdriver
147
+ expect(js).toContain('navigator');
148
+ expect(js).toContain('webdriver');
149
+ // 2. chrome stub
150
+ expect(js).toContain('window.chrome');
151
+ // 3. plugins
152
+ expect(js).toContain('plugins');
153
+ expect(js).toContain('PDF Viewer');
154
+ // 4. languages
155
+ expect(js).toContain('languages');
156
+ // 5. permissions
157
+ expect(js).toContain('Permissions');
158
+ expect(js).toContain('notifications');
159
+ // 6. automation artifacts (dynamic cdc_ scan)
160
+ expect(js).toContain('__playwright');
161
+ expect(js).toContain('__puppeteer');
162
+ expect(js).toContain('getOwnPropertyNames');
163
+ expect(js).toContain('cdc_');
164
+ // 7. CDP stack trace cleanup
165
+ expect(js).toContain('Error.prototype');
166
+ expect(js).toContain('puppeteer_evaluation_script');
167
+ expect(js).toContain('getOwnPropertyDescriptor');
168
+ });
169
+
170
+ it('includes guard flag to prevent double-injection', () => {
171
+ const js = generateStealthJs();
172
+ // Guard uses a non-enumerable property on a built-in prototype
173
+ expect(js).toContain("EventTarget.prototype");
174
+ // Guard should check early and return 'skipped'
175
+ expect(js).toContain("return 'skipped'");
176
+ // Normal path returns 'applied'
177
+ expect(js).toContain("return 'applied'");
178
+ });
179
+
180
+ it('generates syntactically valid JS', () => {
181
+ const js = generateStealthJs();
182
+ // Should not throw when parsed
183
+ expect(() => new Function(js)).not.toThrow();
184
+ });
185
+ });
@@ -129,4 +129,18 @@ describe('manifest helper rules', () => {
129
129
 
130
130
  expect(scanTs(file, 'demo')).toBeNull();
131
131
  });
132
+
133
+ it('keeps literal domain and navigateBefore for TS adapters', () => {
134
+ const file = path.join(process.cwd(), 'src', 'clis', 'xueqiu', 'fund-holdings.ts');
135
+ const entry = scanTs(file, 'xueqiu');
136
+
137
+ expect(entry).toMatchObject({
138
+ site: 'xueqiu',
139
+ name: 'fund-holdings',
140
+ domain: 'danjuanfunds.com',
141
+ navigateBefore: 'https://danjuanfunds.com/my-money',
142
+ type: 'ts',
143
+ modulePath: 'xueqiu/fund-holdings.js',
144
+ });
145
+ });
132
146
  });
@@ -46,34 +46,9 @@ export interface ManifestEntry {
46
46
  navigateBefore?: boolean | string;
47
47
  }
48
48
 
49
- interface YamlArgDefinition {
50
- type?: string;
51
- default?: unknown;
52
- required?: boolean;
53
- positional?: boolean;
54
- description?: string;
55
- help?: string;
56
- choices?: string[];
57
- }
58
-
59
- interface YamlCliDefinition {
60
- site?: string;
61
- name?: string;
62
- description?: string;
63
- domain?: string;
64
- strategy?: string;
65
- browser?: boolean;
66
- args?: Record<string, YamlArgDefinition>;
67
- columns?: string[];
68
- pipeline?: Record<string, unknown>[];
69
- timeout?: number;
70
- navigateBefore?: boolean | string;
71
- }
72
-
73
- function isRecord(value: unknown): value is Record<string, unknown> {
74
- return typeof value === 'object' && value !== null && !Array.isArray(value);
75
- }
49
+ import type { YamlCliDefinition } from './yaml-schema.js';
76
50
 
51
+ import { isRecord } from './utils.js';
77
52
 
78
53
 
79
54
  function extractBalancedBlock(
@@ -180,7 +155,8 @@ export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
180
155
  choices: parseInlineChoices(body),
181
156
  });
182
157
 
183
- cursor = objectStart + body.length + 2;
158
+ cursor = objectStart + body.length;
159
+ if (cursor <= objectStart) break; // safety: prevent infinite loop
184
160
  }
185
161
 
186
162
  return args;
@@ -284,9 +260,14 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
284
260
  entry.args = parseTsArgsBlock(argsBlock);
285
261
  }
286
262
 
287
- // Extract navigateBefore: false
288
- const navMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
289
- if (navMatch) entry.navigateBefore = navMatch[1] === 'true' ? true : false;
263
+ // Extract navigateBefore: false / true / 'https://...'
264
+ const navBoolMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
265
+ if (navBoolMatch) {
266
+ entry.navigateBefore = navBoolMatch[1] === 'true';
267
+ } else {
268
+ const navStringMatch = src.match(/navigateBefore\s*:\s*['"`]([^'"`]+)['"`]/);
269
+ if (navStringMatch) entry.navigateBefore = navStringMatch[1];
270
+ }
290
271
 
291
272
  return entry;
292
273
  } catch (err) {
@@ -301,7 +282,7 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
301
282
  * prefer the TS version (it self-registers and typically has richer logic).
302
283
  */
303
284
  export function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean {
304
- if (current.type === next.type) return true;
285
+ if (current.type === next.type) return false;
305
286
  return current.type === 'yaml' && next.type === 'ts';
306
287
  }
307
288
 
package/src/cascade.ts CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { Strategy } from './registry.js';
14
14
  import type { IPage } from './types.js';
15
+ import { getErrorMessage } from './errors.js';
15
16
 
16
17
  /** Strategy cascade order (simplest → most complex) */
17
18
  const CASCADE_ORDER: Strategy[] = [
@@ -128,9 +129,9 @@ export async function probeEndpoint(
128
129
  result.error = `Strategy ${strategy} requires site-specific implementation`;
129
130
  break;
130
131
  }
131
- } catch (err: any) {
132
+ } catch (err) {
132
133
  result.success = false;
133
- result.error = err.message ?? String(err);
134
+ result.error = getErrorMessage(err);
134
135
  }
135
136
 
136
137
  return result;
@@ -145,9 +146,10 @@ export async function cascadeProbe(
145
146
  url: string,
146
147
  opts: { maxStrategy?: Strategy; timeout?: number } = {},
147
148
  ): Promise<CascadeResult> {
148
- const maxIdx = opts.maxStrategy
149
+ const rawIdx = opts.maxStrategy
149
150
  ? CASCADE_ORDER.indexOf(opts.maxStrategy)
150
151
  : CASCADE_ORDER.indexOf(Strategy.HEADER); // Don't auto-try INTERCEPT/UI
152
+ const maxIdx = rawIdx === -1 ? CASCADE_ORDER.indexOf(Strategy.HEADER) : rawIdx;
151
153
 
152
154
  const probes: ProbeResult[] = [];
153
155