@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
@@ -9,9 +9,12 @@
9
9
  */
10
10
 
11
11
  import { WebSocket, type RawData } from 'ws';
12
+ import { request as httpRequest } from 'node:http';
13
+ import { request as httpsRequest } from 'node:https';
12
14
  import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
13
15
  import { wrapForEval } from './utils.js';
14
16
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
17
+ import { generateStealthJs } from './stealth.js';
15
18
  import {
16
19
  clickJs,
17
20
  typeTextJs,
@@ -22,6 +25,7 @@ import {
22
25
  networkRequestsJs,
23
26
  waitForDomStableJs,
24
27
  } from './dom-helpers.js';
28
+ import { isRecord, saveBase64ToFile } from '../utils.js';
25
29
 
26
30
  export interface CDPTarget {
27
31
  type?: string;
@@ -41,7 +45,7 @@ interface RuntimeEvaluateResult {
41
45
  };
42
46
  }
43
47
 
44
- const CDP_SEND_TIMEOUT = 30_000; // 30s per command
48
+ const CDP_SEND_TIMEOUT = 30_000;
45
49
 
46
50
  export class CDPBridge {
47
51
  private _ws: WebSocket | null = null;
@@ -50,15 +54,14 @@ export class CDPBridge {
50
54
  private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
51
55
 
52
56
  async connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage> {
57
+ if (this._ws) throw new Error('CDPBridge is already connected. Call close() before reconnecting.');
58
+
53
59
  const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
54
60
  if (!endpoint) throw new Error('OPENCLI_CDP_ENDPOINT is not set');
55
61
 
56
- // If it's a direct ws:// URL, use it. Otherwise, fetch the /json endpoint to find a page.
57
62
  let wsUrl = endpoint;
58
63
  if (endpoint.startsWith('http')) {
59
- const res = await fetch(`${endpoint.replace(/\/$/, '')}/json`);
60
- if (!res.ok) throw new Error(`Failed to fetch CDP targets: ${res.statusText}`);
61
- const targets = await res.json() as CDPTarget[];
64
+ const targets = await fetchJsonDirect(`${endpoint.replace(/\/$/, '')}/json`) as CDPTarget[];
62
65
  const target = selectCDPTarget(targets);
63
66
  if (!target || !target.webSocketDebuggerUrl) {
64
67
  throw new Error('No inspectable targets found at CDP endpoint');
@@ -68,12 +71,16 @@ export class CDPBridge {
68
71
 
69
72
  return new Promise((resolve, reject) => {
70
73
  const ws = new WebSocket(wsUrl);
71
- const timeoutMs = (opts?.timeout ?? 10) * 1000; // opts.timeout is in seconds
74
+ const timeoutMs = (opts?.timeout ?? 10) * 1000;
72
75
  const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), timeoutMs);
73
76
 
74
- ws.on('open', () => {
77
+ ws.on('open', async () => {
75
78
  clearTimeout(timeout);
76
79
  this._ws = ws;
80
+ try {
81
+ await this.send('Page.enable');
82
+ await this.send('Page.addScriptToEvaluateOnNewDocument', { source: generateStealthJs() });
83
+ } catch {}
77
84
  resolve(new CDPPage(this));
78
85
  });
79
86
 
@@ -85,7 +92,6 @@ export class CDPBridge {
85
92
  ws.on('message', (data: RawData) => {
86
93
  try {
87
94
  const msg = JSON.parse(data.toString());
88
- // Handle command responses
89
95
  if (msg.id && this._pending.has(msg.id)) {
90
96
  const entry = this._pending.get(msg.id)!;
91
97
  clearTimeout(entry.timer);
@@ -96,16 +102,13 @@ export class CDPBridge {
96
102
  entry.resolve(msg.result);
97
103
  }
98
104
  }
99
- // Handle CDP events
100
105
  if (msg.method) {
101
106
  const listeners = this._eventListeners.get(msg.method);
102
107
  if (listeners) {
103
108
  for (const fn of listeners) fn(msg.params);
104
109
  }
105
110
  }
106
- } catch {
107
- // ignore parsing errors
108
- }
111
+ } catch {}
109
112
  });
110
113
  });
111
114
  }
@@ -123,7 +126,6 @@ export class CDPBridge {
123
126
  this._eventListeners.clear();
124
127
  }
125
128
 
126
- /** Send a CDP command with timeout guard (P0 fix #4) */
127
129
  async send(method: string, params: Record<string, unknown> = {}, timeoutMs: number = CDP_SEND_TIMEOUT): Promise<unknown> {
128
130
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
129
131
  throw new Error('CDP connection is not open');
@@ -139,19 +141,19 @@ export class CDPBridge {
139
141
  });
140
142
  }
141
143
 
142
- /** Listen for a CDP event */
143
144
  on(event: string, handler: (params: unknown) => void): void {
144
145
  let set = this._eventListeners.get(event);
145
- if (!set) { set = new Set(); this._eventListeners.set(event, set); }
146
+ if (!set) {
147
+ set = new Set();
148
+ this._eventListeners.set(event, set);
149
+ }
146
150
  set.add(handler);
147
151
  }
148
152
 
149
- /** Remove a CDP event listener */
150
153
  off(event: string, handler: (params: unknown) => void): void {
151
154
  this._eventListeners.get(event)?.delete(handler);
152
155
  }
153
156
 
154
- /** Wait for a CDP event to fire (one-shot) */
155
157
  waitForEvent(event: string, timeoutMs: number = 15_000): Promise<unknown> {
156
158
  return new Promise((resolve, reject) => {
157
159
  const timer = setTimeout(() => {
@@ -169,17 +171,17 @@ export class CDPBridge {
169
171
  }
170
172
 
171
173
  class CDPPage implements IPage {
174
+ private _pageEnabled = false;
172
175
  constructor(private bridge: CDPBridge) {}
173
176
 
174
- /** Navigate with proper load event waiting (P1 fix #3) */
175
177
  async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
176
- await this.bridge.send('Page.enable');
177
- const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000)
178
- .catch(() => {}); // Don't fail if event times out
178
+ if (!this._pageEnabled) {
179
+ await this.bridge.send('Page.enable');
180
+ this._pageEnabled = true;
181
+ }
182
+ const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000).catch(() => {});
179
183
  await this.bridge.send('Page.navigate', { url });
180
184
  await loadPromise;
181
- // Smart settle: use DOM stability detection instead of fixed sleep.
182
- // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
183
185
  if (options?.waitUntil !== 'none') {
184
186
  const maxMs = options?.settleMs ?? 1000;
185
187
  await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
@@ -191,7 +193,7 @@ class CDPPage implements IPage {
191
193
  const result = await this.bridge.send('Runtime.evaluate', {
192
194
  expression,
193
195
  returnByValue: true,
194
- awaitPromise: true
196
+ awaitPromise: true,
195
197
  }) as RuntimeEvaluateResult;
196
198
  if (result.exceptionDetails) {
197
199
  throw new Error('Evaluate error: ' + (result.exceptionDetails.exception?.description || 'Unknown exception'));
@@ -204,7 +206,7 @@ class CDPPage implements IPage {
204
206
  const cookies = isRecord(result) && Array.isArray(result.cookies) ? result.cookies : [];
205
207
  const domain = opts.domain;
206
208
  return domain
207
- ? cookies.filter((cookie): cookie is BrowserCookie => isCookie(cookie) && cookie.domain.includes(domain))
209
+ ? cookies.filter((cookie): cookie is BrowserCookie => isCookie(cookie) && matchesCookieDomain(cookie.domain, domain))
208
210
  : cookies;
209
211
  }
210
212
 
@@ -220,8 +222,6 @@ class CDPPage implements IPage {
220
222
  return this.evaluate(snapshotJs);
221
223
  }
222
224
 
223
- // ── Shared DOM operations (P1 fix #5 — using dom-helpers.ts) ──
224
-
225
225
  async click(ref: string): Promise<void> {
226
226
  await this.evaluate(clickJs(ref));
227
227
  }
@@ -244,12 +244,12 @@ class CDPPage implements IPage {
244
244
 
245
245
  async wait(options: number | WaitOptions): Promise<void> {
246
246
  if (typeof options === 'number') {
247
- await new Promise(resolve => setTimeout(resolve, options * 1000));
247
+ await new Promise((resolve) => setTimeout(resolve, options * 1000));
248
248
  return;
249
249
  }
250
250
  if (typeof options.time === 'number') {
251
251
  const waitTime = options.time;
252
- await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
252
+ await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
253
253
  return;
254
254
  }
255
255
  if (options.text) {
@@ -258,8 +258,6 @@ class CDPPage implements IPage {
258
258
  }
259
259
  }
260
260
 
261
- // ── Implemented methods (P1 fix #2) ──
262
-
263
261
  async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
264
262
  await this.evaluate(scrollJs(direction, amount));
265
263
  }
@@ -278,11 +276,7 @@ class CDPPage implements IPage {
278
276
  });
279
277
  const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
280
278
  if (options.path) {
281
- const fs = await import('node:fs');
282
- const path = await import('node:path');
283
- const dir = path.dirname(options.path);
284
- await fs.promises.mkdir(dir, { recursive: true });
285
- await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
279
+ await saveBase64ToFile(base64, options.path);
286
280
  }
287
281
  return base64;
288
282
  }
@@ -327,10 +321,6 @@ class CDPPage implements IPage {
327
321
  }
328
322
  }
329
323
 
330
- function isRecord(value: unknown): value is Record<string, unknown> {
331
- return typeof value === 'object' && value !== null && !Array.isArray(value);
332
- }
333
-
334
324
  function isCookie(value: unknown): value is BrowserCookie {
335
325
  return isRecord(value)
336
326
  && typeof value.name === 'string'
@@ -338,7 +328,12 @@ function isCookie(value: unknown): value is BrowserCookie {
338
328
  && typeof value.domain === 'string';
339
329
  }
340
330
 
341
- // ── CDP target selection (unchanged) ──
331
+ function matchesCookieDomain(cookieDomain: string, targetDomain: string): boolean {
332
+ const normalizedCookieDomain = cookieDomain.replace(/^\./, '').toLowerCase();
333
+ const normalizedTargetDomain = targetDomain.replace(/^\./, '').toLowerCase();
334
+ return normalizedTargetDomain === normalizedCookieDomain
335
+ || normalizedTargetDomain.endsWith(`.${normalizedCookieDomain}`);
336
+ }
342
337
 
343
338
  function selectCDPTarget(targets: CDPTarget[]): CDPTarget | undefined {
344
339
  const preferredPattern = compilePreferredPattern(process.env.OPENCLI_CDP_TARGET);
@@ -412,3 +407,31 @@ export const __test__ = {
412
407
  selectCDPTarget,
413
408
  scoreCDPTarget,
414
409
  };
410
+
411
+ function fetchJsonDirect(url: string): Promise<unknown> {
412
+ return new Promise((resolve, reject) => {
413
+ const parsed = new URL(url);
414
+ const request = (parsed.protocol === 'https:' ? httpsRequest : httpRequest)(parsed, (res) => {
415
+ const statusCode = res.statusCode ?? 0;
416
+ if (statusCode < 200 || statusCode >= 300) {
417
+ res.resume();
418
+ reject(new Error(`Failed to fetch CDP targets: HTTP ${statusCode}`));
419
+ return;
420
+ }
421
+
422
+ const chunks: Buffer[] = [];
423
+ res.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
424
+ res.on('end', () => {
425
+ try {
426
+ resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
427
+ } catch (error) {
428
+ reject(error instanceof Error ? error : new Error(String(error)));
429
+ }
430
+ });
431
+ });
432
+
433
+ request.on('error', reject);
434
+ request.setTimeout(10_000, () => request.destroy(new Error('Timed out fetching CDP targets')));
435
+ request.end();
436
+ });
437
+ }
@@ -4,11 +4,12 @@
4
4
  * Provides a typed send() function that posts a Command and returns a Result.
5
5
  */
6
6
 
7
- const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? '19825', 10);
8
- const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
9
-
7
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
10
8
  import type { BrowserSessionInfo } from '../types.js';
11
9
 
10
+ const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
11
+ const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
12
+
12
13
  let _idCounter = 0;
13
14
 
14
15
  function generateId(): string {
@@ -5,6 +5,7 @@
5
5
  * scanning for @playwright/mcp locations.
6
6
  */
7
7
 
8
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
8
9
  import { isDaemonRunning } from './daemon-client.js';
9
10
 
10
11
  export { isDaemonRunning };
@@ -17,7 +18,7 @@ export async function checkDaemonStatus(): Promise<{
17
18
  extensionConnected: boolean;
18
19
  }> {
19
20
  try {
20
- const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? '19825', 10);
21
+ const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
21
22
  const res = await fetch(`http://127.0.0.1:${port}/status`, {
22
23
  headers: { 'X-OpenCLI': '1' },
23
24
  });
@@ -247,3 +247,45 @@ describe('getFormStateJs', () => {
247
247
  expect(js).toContain('data-opencli-ref');
248
248
  });
249
249
  });
250
+
251
+ describe('Search Element Detection', () => {
252
+ it('includes SEARCH_INDICATORS set', () => {
253
+ const js = generateSnapshotJs();
254
+ expect(js).toContain('SEARCH_INDICATORS');
255
+ expect(js).toContain('search');
256
+ expect(js).toContain('magnify');
257
+ expect(js).toContain('glass');
258
+ });
259
+
260
+ it('includes hasFormControlDescendant function', () => {
261
+ const js = generateSnapshotJs();
262
+ expect(js).toContain('hasFormControlDescendant');
263
+ expect(js).toContain('input');
264
+ expect(js).toContain('select');
265
+ expect(js).toContain('textarea');
266
+ });
267
+
268
+ it('includes isSearchElement function', () => {
269
+ const js = generateSnapshotJs();
270
+ expect(js).toContain('isSearchElement');
271
+ expect(js).toContain('className');
272
+ expect(js).toContain('data-');
273
+ });
274
+
275
+ it('checks label wrapper detection in isInteractive', () => {
276
+ const js = generateSnapshotJs();
277
+ // Label elements without "for" attribute should check for form control descendants
278
+ expect(js).toContain('hasFormControlDescendant(el, 2)');
279
+ });
280
+
281
+ it('checks span wrapper detection in isInteractive', () => {
282
+ const js = generateSnapshotJs();
283
+ // Span elements should check for form control descendants
284
+ expect(js).toContain("tag === 'span'");
285
+ });
286
+
287
+ it('integrates search element detection into isInteractive', () => {
288
+ const js = generateSnapshotJs();
289
+ expect(js).toContain('isSearchElement(el)');
290
+ });
291
+ });
@@ -26,7 +26,7 @@
26
26
 
27
27
  // ─── Types ───────────────────────────────────────────────────────────
28
28
 
29
- export interface SnapshotOptions {
29
+ export interface DomSnapshotOptions {
30
30
  /** Extra pixels beyond viewport to include (default 800) */
31
31
  viewportExpand?: number;
32
32
  /** Maximum DOM depth to traverse (default 50) */
@@ -175,7 +175,7 @@ export function getFormStateJs(): string {
175
175
  * - `|iframe|` — iframe content
176
176
  * - `|table|` — markdown table rendering
177
177
  */
178
- export function generateSnapshotJs(opts: SnapshotOptions = {}): string {
178
+ export function generateSnapshotJs(opts: DomSnapshotOptions = {}): string {
179
179
  const viewportExpand = opts.viewportExpand ?? 800;
180
180
  const maxDepth = Math.max(1, Math.min(opts.maxDepth ?? 50, 200));
181
181
  const interactiveOnly = opts.interactiveOnly ?? false;
@@ -271,6 +271,13 @@ export function generateSnapshotJs(opts: SnapshotOptions = {}): string {
271
271
 
272
272
  const AD_SELECTOR_RE = /\\b(ad[_-]?(?:banner|container|wrapper|slot|unit|block|frame|leaderboard|sidebar)|google[_-]?ad|sponsored|adsbygoogle|banner[_-]?ad)\\b/i;
273
273
 
274
+ // Search element indicators for heuristic detection
275
+ const SEARCH_INDICATORS = new Set([
276
+ 'search', 'magnify', 'glass', 'lookup', 'find', 'query',
277
+ 'search-icon', 'search-btn', 'search-button', 'searchbox',
278
+ 'fa-search', 'icon-search', 'btn-search',
279
+ ]);
280
+
274
281
  // ── Viewport & Layout Helpers ──────────────────────────────────────
275
282
 
276
283
  const vw = window.innerWidth;
@@ -339,19 +346,65 @@ export function generateSnapshotJs(opts: SnapshotOptions = {}): string {
339
346
 
340
347
  // ── Interactivity Detection ────────────────────────────────────────
341
348
 
349
+ // Check if element contains a form control within limited depth (handles label/span wrappers)
350
+ function hasFormControlDescendant(el, maxDepth = 2) {
351
+ if (maxDepth <= 0) return false;
352
+ for (const child of el.children || []) {
353
+ const tag = child.tagName?.toLowerCase();
354
+ if (tag === 'input' || tag === 'select' || tag === 'textarea') return true;
355
+ if (hasFormControlDescendant(child, maxDepth - 1)) return true;
356
+ }
357
+ return false;
358
+ }
359
+
342
360
  function isInteractive(el) {
343
361
  const tag = el.tagName.toLowerCase();
344
362
  if (INTERACTIVE_TAGS.has(tag)) {
345
- if (tag === 'label' && el.hasAttribute('for')) return false;
363
+ // Skip labels that proxy via "for" to avoid double-activating external inputs
364
+ if (tag === 'label') {
365
+ if (el.hasAttribute('for')) return false;
366
+ // Detect labels that wrap form controls up to two levels deep (label > span > input)
367
+ if (hasFormControlDescendant(el, 2)) return true;
368
+ }
346
369
  if (el.disabled && (tag === 'button' || tag === 'input')) return false;
347
370
  return true;
348
371
  }
372
+ // Span wrappers for UI components - check if they contain form controls
373
+ if (tag === 'span') {
374
+ if (hasFormControlDescendant(el, 2)) return true;
375
+ }
349
376
  const role = el.getAttribute('role');
350
377
  if (role && INTERACTIVE_ROLES.has(role)) return true;
351
378
  if (el.hasAttribute('onclick') || el.hasAttribute('onmousedown') || el.hasAttribute('ontouchstart')) return true;
352
379
  if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') return true;
353
380
  try { if (window.getComputedStyle(el).cursor === 'pointer') return true; } catch {}
354
381
  if (el.isContentEditable && el.getAttribute('contenteditable') !== 'false') return true;
382
+ // Search element heuristic detection
383
+ if (isSearchElement(el)) return true;
384
+ return false;
385
+ }
386
+
387
+ function isSearchElement(el) {
388
+ // Check class names for search indicators
389
+ const className = el.className?.toLowerCase() || '';
390
+ const classes = className.split(/\\s+/).filter(Boolean);
391
+ for (const cls of classes) {
392
+ const cleaned = cls.replace(/[^a-z0-9-]/g, '');
393
+ if (SEARCH_INDICATORS.has(cleaned)) return true;
394
+ }
395
+ // Check id for search indicators
396
+ const id = el.id?.toLowerCase() || '';
397
+ const cleanedId = id.replace(/[^a-z0-9-]/g, '');
398
+ if (SEARCH_INDICATORS.has(cleanedId)) return true;
399
+ // Check data-* attributes for search functionality
400
+ for (const attr of el.attributes || []) {
401
+ if (attr.name.startsWith('data-')) {
402
+ const value = attr.value.toLowerCase();
403
+ for (const kw of SEARCH_INDICATORS) {
404
+ if (value.includes(kw)) return true;
405
+ }
406
+ }
407
+ }
355
408
  return false;
356
409
  }
357
410
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { BrowserConnectError } from '../errors.js';
9
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
9
10
 
10
11
  export type ConnectFailureKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
11
12
 
@@ -17,7 +18,7 @@ export function formatBrowserConnectError(kind: ConnectFailureKind, detail?: str
17
18
  (detail ? `\n\n${detail}` : ''),
18
19
  'The daemon should start automatically. If it doesn\'t, try:\n' +
19
20
  ' node dist/daemon.js\n' +
20
- 'Make sure port 19825 is available.',
21
+ `Make sure port ${DEFAULT_DAEMON_PORT} is available.`,
21
22
  );
22
23
  case 'extension-not-connected':
23
24
  return new BrowserConnectError(
@@ -6,11 +6,12 @@
6
6
  */
7
7
 
8
8
  export { Page } from './page.js';
9
- export { BrowserBridge, BrowserBridge as PlaywrightMCP } from './mcp.js';
9
+ export { BrowserBridge } from './mcp.js';
10
10
  export { CDPBridge } from './cdp.js';
11
11
  export { isDaemonRunning } from './daemon-client.js';
12
12
  export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
13
- export type { SnapshotOptions } from './dom-snapshot.js';
13
+ export { generateStealthJs } from './stealth.js';
14
+ export type { DomSnapshotOptions } from './dom-snapshot.js';
14
15
 
15
16
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
16
17
  import { __test__ as cdpTest } from './cdp.js';
@@ -9,6 +9,7 @@ import * as fs from 'node:fs';
9
9
  import type { IPage } from '../types.js';
10
10
  import { Page } from './page.js';
11
11
  import { isDaemonRunning, isExtensionConnected } from './daemon-client.js';
12
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
12
13
 
13
14
  const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
14
15
 
@@ -112,10 +113,7 @@ export class BrowserBridge {
112
113
  throw new Error(
113
114
  'Failed to start opencli daemon. Try running manually:\n' +
114
115
  ` node ${daemonPath}\n` +
115
- 'Make sure port 19825 is available.',
116
+ `Make sure port ${DEFAULT_DAEMON_PORT} is available.`,
116
117
  );
117
118
  }
118
119
  }
119
-
120
- /** @deprecated Use BrowserBridge instead */
121
- export const PlaywrightMCP = BrowserBridge;