@jackwener/opencli 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -9,6 +9,8 @@
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';
@@ -23,6 +25,7 @@ import {
23
25
  networkRequestsJs,
24
26
  waitForDomStableJs,
25
27
  } from './dom-helpers.js';
28
+ import { isRecord, saveBase64ToFile } from '../utils.js';
26
29
 
27
30
  export interface CDPTarget {
28
31
  type?: string;
@@ -42,7 +45,7 @@ interface RuntimeEvaluateResult {
42
45
  };
43
46
  }
44
47
 
45
- const CDP_SEND_TIMEOUT = 30_000; // 30s per command
48
+ const CDP_SEND_TIMEOUT = 30_000;
46
49
 
47
50
  export class CDPBridge {
48
51
  private _ws: WebSocket | null = null;
@@ -51,15 +54,14 @@ export class CDPBridge {
51
54
  private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
52
55
 
53
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
+
54
59
  const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
55
60
  if (!endpoint) throw new Error('OPENCLI_CDP_ENDPOINT is not set');
56
61
 
57
- // If it's a direct ws:// URL, use it. Otherwise, fetch the /json endpoint to find a page.
58
62
  let wsUrl = endpoint;
59
63
  if (endpoint.startsWith('http')) {
60
- const res = await fetch(`${endpoint.replace(/\/$/, '')}/json`);
61
- if (!res.ok) throw new Error(`Failed to fetch CDP targets: ${res.statusText}`);
62
- const targets = await res.json() as CDPTarget[];
64
+ const targets = await fetchJsonDirect(`${endpoint.replace(/\/$/, '')}/json`) as CDPTarget[];
63
65
  const target = selectCDPTarget(targets);
64
66
  if (!target || !target.webSocketDebuggerUrl) {
65
67
  throw new Error('No inspectable targets found at CDP endpoint');
@@ -69,19 +71,16 @@ export class CDPBridge {
69
71
 
70
72
  return new Promise((resolve, reject) => {
71
73
  const ws = new WebSocket(wsUrl);
72
- const timeoutMs = (opts?.timeout ?? 10) * 1000; // opts.timeout is in seconds
74
+ const timeoutMs = (opts?.timeout ?? 10) * 1000;
73
75
  const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), timeoutMs);
74
76
 
75
77
  ws.on('open', async () => {
76
78
  clearTimeout(timeout);
77
79
  this._ws = ws;
78
- // Register stealth script to run before any page JS on every navigation.
79
80
  try {
80
81
  await this.send('Page.enable');
81
82
  await this.send('Page.addScriptToEvaluateOnNewDocument', { source: generateStealthJs() });
82
- } catch {
83
- // Non-fatal: stealth is best-effort
84
- }
83
+ } catch {}
85
84
  resolve(new CDPPage(this));
86
85
  });
87
86
 
@@ -93,7 +92,6 @@ export class CDPBridge {
93
92
  ws.on('message', (data: RawData) => {
94
93
  try {
95
94
  const msg = JSON.parse(data.toString());
96
- // Handle command responses
97
95
  if (msg.id && this._pending.has(msg.id)) {
98
96
  const entry = this._pending.get(msg.id)!;
99
97
  clearTimeout(entry.timer);
@@ -104,16 +102,13 @@ export class CDPBridge {
104
102
  entry.resolve(msg.result);
105
103
  }
106
104
  }
107
- // Handle CDP events
108
105
  if (msg.method) {
109
106
  const listeners = this._eventListeners.get(msg.method);
110
107
  if (listeners) {
111
108
  for (const fn of listeners) fn(msg.params);
112
109
  }
113
110
  }
114
- } catch {
115
- // ignore parsing errors
116
- }
111
+ } catch {}
117
112
  });
118
113
  });
119
114
  }
@@ -131,7 +126,6 @@ export class CDPBridge {
131
126
  this._eventListeners.clear();
132
127
  }
133
128
 
134
- /** Send a CDP command with timeout guard (P0 fix #4) */
135
129
  async send(method: string, params: Record<string, unknown> = {}, timeoutMs: number = CDP_SEND_TIMEOUT): Promise<unknown> {
136
130
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
137
131
  throw new Error('CDP connection is not open');
@@ -147,19 +141,19 @@ export class CDPBridge {
147
141
  });
148
142
  }
149
143
 
150
- /** Listen for a CDP event */
151
144
  on(event: string, handler: (params: unknown) => void): void {
152
145
  let set = this._eventListeners.get(event);
153
- if (!set) { set = new Set(); this._eventListeners.set(event, set); }
146
+ if (!set) {
147
+ set = new Set();
148
+ this._eventListeners.set(event, set);
149
+ }
154
150
  set.add(handler);
155
151
  }
156
152
 
157
- /** Remove a CDP event listener */
158
153
  off(event: string, handler: (params: unknown) => void): void {
159
154
  this._eventListeners.get(event)?.delete(handler);
160
155
  }
161
156
 
162
- /** Wait for a CDP event to fire (one-shot) */
163
157
  waitForEvent(event: string, timeoutMs: number = 15_000): Promise<unknown> {
164
158
  return new Promise((resolve, reject) => {
165
159
  const timer = setTimeout(() => {
@@ -177,17 +171,17 @@ export class CDPBridge {
177
171
  }
178
172
 
179
173
  class CDPPage implements IPage {
174
+ private _pageEnabled = false;
180
175
  constructor(private bridge: CDPBridge) {}
181
176
 
182
- /** Navigate with proper load event waiting (P1 fix #3) */
183
177
  async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
184
- await this.bridge.send('Page.enable');
185
- const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000)
186
- .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(() => {});
187
183
  await this.bridge.send('Page.navigate', { url });
188
184
  await loadPromise;
189
- // Smart settle: use DOM stability detection instead of fixed sleep.
190
- // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
191
185
  if (options?.waitUntil !== 'none') {
192
186
  const maxMs = options?.settleMs ?? 1000;
193
187
  await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
@@ -199,7 +193,7 @@ class CDPPage implements IPage {
199
193
  const result = await this.bridge.send('Runtime.evaluate', {
200
194
  expression,
201
195
  returnByValue: true,
202
- awaitPromise: true
196
+ awaitPromise: true,
203
197
  }) as RuntimeEvaluateResult;
204
198
  if (result.exceptionDetails) {
205
199
  throw new Error('Evaluate error: ' + (result.exceptionDetails.exception?.description || 'Unknown exception'));
@@ -212,7 +206,7 @@ class CDPPage implements IPage {
212
206
  const cookies = isRecord(result) && Array.isArray(result.cookies) ? result.cookies : [];
213
207
  const domain = opts.domain;
214
208
  return domain
215
- ? 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))
216
210
  : cookies;
217
211
  }
218
212
 
@@ -228,8 +222,6 @@ class CDPPage implements IPage {
228
222
  return this.evaluate(snapshotJs);
229
223
  }
230
224
 
231
- // ── Shared DOM operations (P1 fix #5 — using dom-helpers.ts) ──
232
-
233
225
  async click(ref: string): Promise<void> {
234
226
  await this.evaluate(clickJs(ref));
235
227
  }
@@ -252,12 +244,12 @@ class CDPPage implements IPage {
252
244
 
253
245
  async wait(options: number | WaitOptions): Promise<void> {
254
246
  if (typeof options === 'number') {
255
- await new Promise(resolve => setTimeout(resolve, options * 1000));
247
+ await new Promise((resolve) => setTimeout(resolve, options * 1000));
256
248
  return;
257
249
  }
258
250
  if (typeof options.time === 'number') {
259
251
  const waitTime = options.time;
260
- await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
252
+ await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
261
253
  return;
262
254
  }
263
255
  if (options.text) {
@@ -266,8 +258,6 @@ class CDPPage implements IPage {
266
258
  }
267
259
  }
268
260
 
269
- // ── Implemented methods (P1 fix #2) ──
270
-
271
261
  async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
272
262
  await this.evaluate(scrollJs(direction, amount));
273
263
  }
@@ -286,11 +276,7 @@ class CDPPage implements IPage {
286
276
  });
287
277
  const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
288
278
  if (options.path) {
289
- const fs = await import('node:fs');
290
- const path = await import('node:path');
291
- const dir = path.dirname(options.path);
292
- await fs.promises.mkdir(dir, { recursive: true });
293
- await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
279
+ await saveBase64ToFile(base64, options.path);
294
280
  }
295
281
  return base64;
296
282
  }
@@ -335,10 +321,6 @@ class CDPPage implements IPage {
335
321
  }
336
322
  }
337
323
 
338
- function isRecord(value: unknown): value is Record<string, unknown> {
339
- return typeof value === 'object' && value !== null && !Array.isArray(value);
340
- }
341
-
342
324
  function isCookie(value: unknown): value is BrowserCookie {
343
325
  return isRecord(value)
344
326
  && typeof value.name === 'string'
@@ -346,7 +328,12 @@ function isCookie(value: unknown): value is BrowserCookie {
346
328
  && typeof value.domain === 'string';
347
329
  }
348
330
 
349
- // ── 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
+ }
350
337
 
351
338
  function selectCDPTarget(targets: CDPTarget[]): CDPTarget | undefined {
352
339
  const preferredPattern = compilePreferredPattern(process.env.OPENCLI_CDP_TARGET);
@@ -420,3 +407,31 @@ export const __test__ = {
420
407
  selectCDPTarget,
421
408
  scoreCDPTarget,
422
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
+ }
@@ -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,12 +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
13
  export { generateStealthJs } from './stealth.js';
14
- export type { SnapshotOptions } from './dom-snapshot.js';
14
+ export type { DomSnapshotOptions } from './dom-snapshot.js';
15
15
 
16
16
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
17
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;
@@ -14,6 +14,7 @@ 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';
18
19
  import { generateStealthJs } from './stealth.js';
19
20
  import {
@@ -36,20 +37,23 @@ export class Page implements IPage {
36
37
  /** Active tab ID, set after navigate and used in all subsequent commands */
37
38
  private _tabId: number | undefined;
38
39
 
39
- /** Helper: spread tabId into command params if we have one */
40
- private _tabOpt(): { tabId: number } | Record<string, never> {
41
- return this._tabId !== undefined ? { tabId: this._tabId } : {};
40
+ /** Helper: spread workspace into command params */
41
+ private _wsOpt(): { workspace: string } {
42
+ return { workspace: this.workspace };
42
43
  }
43
44
 
44
- private _workspaceOpt(): { workspace: string } {
45
- 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
+ };
46
51
  }
47
52
 
48
53
  async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
49
54
  const result = await sendCommand('navigate', {
50
55
  url,
51
- ...this._workspaceOpt(),
52
- ...this._tabOpt(),
56
+ ...this._cmdOpts(),
53
57
  }) as { tabId?: number };
54
58
  // Remember the tabId for subsequent exec calls
55
59
  if (result?.tabId) {
@@ -59,8 +63,7 @@ export class Page implements IPage {
59
63
  try {
60
64
  await sendCommand('exec', {
61
65
  code: generateStealthJs(),
62
- ...this._workspaceOpt(),
63
- ...this._tabOpt(),
66
+ ...this._cmdOpts(),
64
67
  });
65
68
  } catch {
66
69
  // Non-fatal: stealth is best-effort
@@ -71,8 +74,7 @@ export class Page implements IPage {
71
74
  const maxMs = options?.settleMs ?? 1000;
72
75
  await sendCommand('exec', {
73
76
  code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
74
- ...this._workspaceOpt(),
75
- ...this._tabOpt(),
77
+ ...this._cmdOpts(),
76
78
  });
77
79
  }
78
80
  }
@@ -80,7 +82,7 @@ export class Page implements IPage {
80
82
  /** Close the automation window in the extension */
81
83
  async closeWindow(): Promise<void> {
82
84
  try {
83
- await sendCommand('close-window', { ...this._workspaceOpt() });
85
+ await sendCommand('close-window', { ...this._wsOpt() });
84
86
  } catch {
85
87
  // Window may already be closed or daemon may be down
86
88
  }
@@ -88,11 +90,11 @@ export class Page implements IPage {
88
90
 
89
91
  async evaluate(js: string): Promise<unknown> {
90
92
  const code = wrapForEval(js);
91
- return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
93
+ return sendCommand('exec', { code, ...this._cmdOpts() });
92
94
  }
93
95
 
94
96
  async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
95
- const result = await sendCommand('cookies', { ...this._workspaceOpt(), ...opts });
97
+ const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
96
98
  return Array.isArray(result) ? result : [];
97
99
  }
98
100
 
@@ -108,7 +110,7 @@ export class Page implements IPage {
108
110
  });
109
111
 
110
112
  try {
111
- const result = await sendCommand('exec', { code: snapshotJs, ...this._workspaceOpt(), ...this._tabOpt() });
113
+ const result = await sendCommand('exec', { code: snapshotJs, ...this._cmdOpts() });
112
114
  // The advanced engine already produces a clean, pruned, LLM-friendly output.
113
115
  // Do NOT pass through formatSnapshot — its format is incompatible.
114
116
  return result;
@@ -148,7 +150,7 @@ export class Page implements IPage {
148
150
  return buildTree(document.body, 0);
149
151
  })()
150
152
  `;
151
- const raw = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
153
+ const raw = await sendCommand('exec', { code, ...this._cmdOpts() });
152
154
  if (opts.raw) return raw;
153
155
  if (typeof raw === 'string') return formatSnapshot(raw, opts);
154
156
  return raw;
@@ -156,27 +158,27 @@ export class Page implements IPage {
156
158
 
157
159
  async click(ref: string): Promise<void> {
158
160
  const code = clickJs(ref);
159
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
161
+ await sendCommand('exec', { code, ...this._cmdOpts() });
160
162
  }
161
163
 
162
164
  async typeText(ref: string, text: string): Promise<void> {
163
165
  const code = typeTextJs(ref, text);
164
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
166
+ await sendCommand('exec', { code, ...this._cmdOpts() });
165
167
  }
166
168
 
167
169
  async pressKey(key: string): Promise<void> {
168
170
  const code = pressKeyJs(key);
169
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
171
+ await sendCommand('exec', { code, ...this._cmdOpts() });
170
172
  }
171
173
 
172
174
  async scrollTo(ref: string): Promise<unknown> {
173
175
  const code = scrollToRefJs(ref);
174
- return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
176
+ return sendCommand('exec', { code, ...this._cmdOpts() });
175
177
  }
176
178
 
177
179
  async getFormState(): Promise<Record<string, unknown>> {
178
180
  const code = getFormStateJs();
179
- 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>;
180
182
  }
181
183
 
182
184
  async wait(options: number | WaitOptions): Promise<void> {
@@ -184,42 +186,42 @@ export class Page implements IPage {
184
186
  await new Promise(resolve => setTimeout(resolve, options * 1000));
185
187
  return;
186
188
  }
187
- if (options.time) {
189
+ if (typeof options.time === 'number') {
188
190
  await new Promise(resolve => setTimeout(resolve, options.time! * 1000));
189
191
  return;
190
192
  }
191
193
  if (options.text) {
192
194
  const timeout = (options.timeout ?? 30) * 1000;
193
195
  const code = waitForTextJs(options.text, timeout);
194
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
196
+ await sendCommand('exec', { code, ...this._cmdOpts() });
195
197
  }
196
198
  }
197
199
 
198
200
  async tabs(): Promise<unknown[]> {
199
- const result = await sendCommand('tabs', { op: 'list', ...this._workspaceOpt() });
201
+ const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
200
202
  return Array.isArray(result) ? result : [];
201
203
  }
202
204
 
203
205
  async closeTab(index?: number): Promise<void> {
204
- await sendCommand('tabs', { op: 'close', ...this._workspaceOpt(), ...(index !== undefined ? { index } : {}) });
206
+ await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
205
207
  // Invalidate cached tabId — the closed tab might have been our active one.
206
208
  // We can't know for sure (close-by-index doesn't return tabId), so reset.
207
209
  this._tabId = undefined;
208
210
  }
209
211
 
210
212
  async newTab(): Promise<void> {
211
- 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 };
212
214
  if (result?.tabId) this._tabId = result.tabId;
213
215
  }
214
216
 
215
217
  async selectTab(index: number): Promise<void> {
216
- 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 };
217
219
  if (result?.selected) this._tabId = result.selected;
218
220
  }
219
221
 
220
222
  async networkRequests(includeStatic: boolean = false): Promise<unknown[]> {
221
223
  const code = networkRequestsJs(includeStatic);
222
- const result = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
224
+ const result = await sendCommand('exec', { code, ...this._cmdOpts() });
223
225
  return Array.isArray(result) ? result : [];
224
226
  }
225
227
 
@@ -241,19 +243,14 @@ export class Page implements IPage {
241
243
  */
242
244
  async screenshot(options: ScreenshotOptions = {}): Promise<string> {
243
245
  const base64 = await sendCommand('screenshot', {
244
- ...this._workspaceOpt(),
246
+ ...this._cmdOpts(),
245
247
  format: options.format,
246
248
  quality: options.quality,
247
249
  fullPage: options.fullPage,
248
- ...this._tabOpt(),
249
250
  }) as string;
250
251
 
251
252
  if (options.path) {
252
- const fs = await import('node:fs');
253
- const path = await import('node:path');
254
- const dir = path.dirname(options.path);
255
- await fs.promises.mkdir(dir, { recursive: true });
256
- await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
253
+ await saveBase64ToFile(base64, options.path);
257
254
  }
258
255
 
259
256
  return base64;
@@ -261,14 +258,14 @@ export class Page implements IPage {
261
258
 
262
259
  async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
263
260
  const code = scrollJs(direction, amount);
264
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
261
+ await sendCommand('exec', { code, ...this._cmdOpts() });
265
262
  }
266
263
 
267
264
  async autoScroll(options: { times?: number; delayMs?: number } = {}): Promise<void> {
268
265
  const times = options.times ?? 3;
269
266
  const delayMs = options.delayMs ?? 2000;
270
267
  const code = autoScrollJs(times, delayMs);
271
- await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
268
+ await sendCommand('exec', { code, ...this._cmdOpts() });
272
269
  }
273
270
 
274
271
  async installInterceptor(pattern: string): Promise<void> {