@jackwener/opencli 1.6.0 → 1.6.2

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 (390) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -45
  4. package/README.zh-CN.md +32 -34
  5. package/autoresearch/browse-tasks.json +18 -20
  6. package/autoresearch/commands/debug.ts +163 -0
  7. package/autoresearch/commands/fix.ts +145 -0
  8. package/autoresearch/commands/plan.ts +88 -0
  9. package/autoresearch/commands/run.ts +138 -0
  10. package/autoresearch/config.ts +82 -0
  11. package/autoresearch/engine.ts +359 -0
  12. package/autoresearch/eval-all.ts +127 -0
  13. package/autoresearch/eval-browse.ts +1 -1
  14. package/autoresearch/eval-publish.ts +238 -0
  15. package/autoresearch/eval-save.ts +249 -0
  16. package/autoresearch/eval-skill.ts +14 -8
  17. package/autoresearch/eval-v2ex.ts +220 -0
  18. package/autoresearch/eval-zhihu.ts +230 -0
  19. package/autoresearch/logger.ts +69 -0
  20. package/autoresearch/presets/combined-reliability.ts +27 -0
  21. package/autoresearch/presets/index.ts +23 -0
  22. package/autoresearch/presets/operate-reliability.ts +24 -0
  23. package/autoresearch/presets/save-reliability.ts +26 -0
  24. package/autoresearch/presets/skill-quality.ts +20 -0
  25. package/autoresearch/presets/v2ex-reliability.ts +24 -0
  26. package/autoresearch/presets/zhihu-reliability.ts +25 -0
  27. package/autoresearch/publish-tasks.json +345 -0
  28. package/autoresearch/run-save.sh +11 -0
  29. package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
  30. package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
  31. package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
  32. package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
  33. package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
  34. package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
  35. package/autoresearch/save-tasks.json +281 -0
  36. package/autoresearch/v2ex-tasks.json +899 -0
  37. package/autoresearch/zhihu-tasks.json +848 -0
  38. package/bun.lock +615 -0
  39. package/dist/browser/base-page.d.ts +4 -2
  40. package/dist/browser/base-page.js +37 -4
  41. package/dist/browser/bridge.js +10 -8
  42. package/dist/browser/cdp.js +2 -6
  43. package/dist/browser/daemon-client.d.ts +11 -1
  44. package/dist/browser/daemon-client.js +3 -0
  45. package/dist/browser/dom-helpers.d.ts +4 -2
  46. package/dist/browser/dom-helpers.js +42 -31
  47. package/dist/browser/dom-snapshot.js +23 -1
  48. package/dist/browser/page.d.ts +7 -2
  49. package/dist/browser/page.js +112 -30
  50. package/dist/browser.test.js +1 -1
  51. package/dist/build-manifest.d.ts +1 -0
  52. package/dist/build-manifest.js +1 -0
  53. package/dist/cli-manifest.json +1133 -182
  54. package/dist/cli.d.ts +2 -0
  55. package/dist/cli.js +48 -7
  56. package/dist/cli.test.d.ts +1 -0
  57. package/dist/cli.test.js +88 -0
  58. package/dist/clis/1688/item.d.ts +70 -0
  59. package/dist/clis/1688/item.js +187 -0
  60. package/dist/clis/1688/item.test.d.ts +1 -0
  61. package/dist/clis/1688/item.test.js +67 -0
  62. package/dist/clis/1688/search.d.ts +56 -0
  63. package/dist/clis/1688/search.js +309 -0
  64. package/dist/clis/1688/search.test.d.ts +1 -0
  65. package/dist/clis/1688/search.test.js +75 -0
  66. package/dist/clis/1688/shared.d.ts +112 -0
  67. package/dist/clis/1688/shared.js +514 -0
  68. package/dist/clis/1688/shared.test.d.ts +1 -0
  69. package/dist/clis/1688/shared.test.js +57 -0
  70. package/dist/clis/1688/store.d.ts +45 -0
  71. package/dist/clis/1688/store.js +226 -0
  72. package/dist/clis/1688/store.test.d.ts +1 -0
  73. package/dist/clis/1688/store.test.js +62 -0
  74. package/dist/clis/amazon/bestsellers.d.ts +0 -20
  75. package/dist/clis/amazon/bestsellers.js +6 -129
  76. package/dist/clis/amazon/bestsellers.test.js +12 -3
  77. package/dist/clis/amazon/movers-shakers.d.ts +1 -0
  78. package/dist/clis/amazon/movers-shakers.js +7 -0
  79. package/dist/clis/amazon/new-releases.d.ts +1 -0
  80. package/dist/clis/amazon/new-releases.js +7 -0
  81. package/dist/clis/amazon/rankings.d.ts +59 -0
  82. package/dist/clis/amazon/rankings.js +226 -0
  83. package/dist/clis/amazon/rankings.test.d.ts +1 -0
  84. package/dist/clis/amazon/rankings.test.js +41 -0
  85. package/dist/clis/amazon/shared.d.ts +11 -0
  86. package/dist/clis/amazon/shared.js +121 -11
  87. package/dist/clis/amazon/shared.test.js +11 -0
  88. package/dist/clis/bilibili/comments.js +2 -2
  89. package/dist/clis/bilibili/comments.test.js +3 -2
  90. package/dist/clis/bilibili/download.js +2 -1
  91. package/dist/clis/bilibili/subtitle.js +4 -3
  92. package/dist/clis/bilibili/subtitle.test.js +2 -1
  93. package/dist/clis/bilibili/utils.d.ts +5 -0
  94. package/dist/clis/bilibili/utils.js +30 -0
  95. package/dist/clis/bilibili/utils.test.d.ts +1 -0
  96. package/dist/clis/bilibili/utils.test.js +17 -0
  97. package/dist/clis/douban/marks.js +1 -1
  98. package/dist/clis/douban/subject.yaml +50 -19
  99. package/dist/clis/doubao/utils.js +32 -12
  100. package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
  101. package/dist/clis/douyin/_shared/transcode.test.js +0 -2
  102. package/dist/clis/douyin/draft.test.js +0 -2
  103. package/dist/clis/facebook/search.test.js +0 -2
  104. package/dist/clis/gemini/ask.js +9 -3
  105. package/dist/clis/gemini/ask.test.d.ts +1 -0
  106. package/dist/clis/gemini/ask.test.js +100 -0
  107. package/dist/clis/gemini/reply-state.test.d.ts +1 -0
  108. package/dist/clis/gemini/reply-state.test.js +641 -0
  109. package/dist/clis/gemini/utils.d.ts +44 -1
  110. package/dist/clis/gemini/utils.js +528 -61
  111. package/dist/clis/gemini/utils.test.js +149 -2
  112. package/dist/clis/hupu/detail.d.ts +1 -0
  113. package/dist/clis/hupu/detail.js +72 -0
  114. package/dist/clis/hupu/hot.yaml +43 -0
  115. package/dist/clis/hupu/like.d.ts +1 -0
  116. package/dist/clis/hupu/like.js +75 -0
  117. package/dist/clis/hupu/reply.d.ts +1 -0
  118. package/dist/clis/hupu/reply.js +71 -0
  119. package/dist/clis/hupu/search.d.ts +1 -0
  120. package/dist/clis/hupu/search.js +59 -0
  121. package/dist/clis/hupu/unlike.d.ts +1 -0
  122. package/dist/clis/hupu/unlike.js +75 -0
  123. package/dist/clis/hupu/utils.d.ts +20 -0
  124. package/dist/clis/hupu/utils.js +319 -0
  125. package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
  126. package/dist/clis/instagram/_shared/private-publish.js +1030 -0
  127. package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
  128. package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
  129. package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
  130. package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
  131. package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
  132. package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
  133. package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
  134. package/dist/clis/instagram/_shared/runtime-info.js +81 -0
  135. package/dist/clis/instagram/note.d.ts +1 -0
  136. package/dist/clis/instagram/note.js +222 -0
  137. package/dist/clis/instagram/note.test.d.ts +1 -0
  138. package/dist/clis/instagram/note.test.js +81 -0
  139. package/dist/clis/instagram/post.d.ts +4 -0
  140. package/dist/clis/instagram/post.js +1496 -0
  141. package/dist/clis/instagram/post.test.d.ts +1 -0
  142. package/dist/clis/instagram/post.test.js +1647 -0
  143. package/dist/clis/instagram/reel.d.ts +1 -0
  144. package/dist/clis/instagram/reel.js +826 -0
  145. package/dist/clis/instagram/reel.test.d.ts +1 -0
  146. package/dist/clis/instagram/reel.test.js +167 -0
  147. package/dist/clis/instagram/story.d.ts +1 -0
  148. package/dist/clis/instagram/story.js +115 -0
  149. package/dist/clis/instagram/story.test.d.ts +1 -0
  150. package/dist/clis/instagram/story.test.js +167 -0
  151. package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
  152. package/dist/clis/sinafinance/stock-rank.js +65 -0
  153. package/dist/clis/substack/utils.test.js +0 -2
  154. package/dist/clis/twitter/post.js +72 -45
  155. package/dist/clis/twitter/post.test.d.ts +1 -0
  156. package/dist/clis/twitter/post.test.js +116 -0
  157. package/dist/clis/twitter/reply.d.ts +12 -0
  158. package/dist/clis/twitter/reply.js +257 -35
  159. package/dist/clis/twitter/reply.test.d.ts +1 -0
  160. package/dist/clis/twitter/reply.test.js +151 -0
  161. package/dist/clis/twitter/search.js +67 -5
  162. package/dist/clis/twitter/search.test.js +83 -5
  163. package/dist/clis/xianyu/chat.d.ts +7 -0
  164. package/dist/clis/xianyu/chat.js +146 -0
  165. package/dist/clis/xianyu/chat.test.d.ts +1 -0
  166. package/dist/clis/xianyu/chat.test.js +15 -0
  167. package/dist/clis/xianyu/item.d.ts +7 -0
  168. package/dist/clis/xianyu/item.js +152 -0
  169. package/dist/clis/xianyu/item.test.d.ts +1 -0
  170. package/dist/clis/xianyu/item.test.js +56 -0
  171. package/dist/clis/xianyu/search.d.ts +10 -0
  172. package/dist/clis/xianyu/search.js +134 -0
  173. package/dist/clis/xianyu/search.test.d.ts +1 -0
  174. package/dist/clis/xianyu/search.test.js +17 -0
  175. package/dist/clis/xianyu/utils.d.ts +1 -0
  176. package/dist/clis/xianyu/utils.js +8 -0
  177. package/dist/clis/xiaoe/catalog.yaml +129 -0
  178. package/dist/clis/xiaoe/content.yaml +43 -0
  179. package/dist/clis/xiaoe/courses.yaml +73 -0
  180. package/dist/clis/xiaoe/detail.yaml +39 -0
  181. package/dist/clis/xiaoe/play-url.yaml +124 -0
  182. package/dist/clis/xiaohongshu/comments.test.js +0 -2
  183. package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
  184. package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
  185. package/dist/clis/xiaohongshu/download.test.js +0 -2
  186. package/dist/clis/xiaohongshu/note.test.js +0 -2
  187. package/dist/clis/xiaohongshu/publish.test.js +0 -2
  188. package/dist/clis/xiaohongshu/search.js +29 -20
  189. package/dist/clis/xiaohongshu/search.test.js +56 -48
  190. package/dist/clis/yuanbao/ask.d.ts +21 -0
  191. package/dist/clis/yuanbao/ask.js +427 -0
  192. package/dist/clis/yuanbao/ask.test.d.ts +1 -0
  193. package/dist/clis/yuanbao/ask.test.js +124 -0
  194. package/dist/clis/yuanbao/new.d.ts +1 -0
  195. package/dist/clis/yuanbao/new.js +70 -0
  196. package/dist/clis/yuanbao/new.test.d.ts +1 -0
  197. package/dist/clis/yuanbao/new.test.js +30 -0
  198. package/dist/clis/yuanbao/shared.d.ts +13 -0
  199. package/dist/clis/yuanbao/shared.js +49 -0
  200. package/dist/clis/zhihu/question.js +30 -19
  201. package/dist/clis/zhihu/question.test.js +34 -16
  202. package/dist/commanderAdapter.js +8 -4
  203. package/dist/commanderAdapter.test.js +42 -0
  204. package/dist/completion.js +3 -1
  205. package/dist/completion.test.d.ts +1 -0
  206. package/dist/completion.test.js +23 -0
  207. package/dist/doctor.js +1 -1
  208. package/dist/electron-apps.d.ts +2 -0
  209. package/dist/electron-apps.js +7 -1
  210. package/dist/errors.js +1 -1
  211. package/dist/execution.js +25 -35
  212. package/dist/explore.js +1 -1
  213. package/dist/launcher.d.ts +4 -0
  214. package/dist/launcher.js +64 -8
  215. package/dist/launcher.test.js +88 -7
  216. package/dist/output.d.ts +2 -0
  217. package/dist/output.js +10 -1
  218. package/dist/output.test.d.ts +0 -3
  219. package/dist/output.test.js +59 -92
  220. package/dist/pipeline/executor.test.js +0 -2
  221. package/dist/pipeline/steps/download.test.js +0 -2
  222. package/dist/registry.d.ts +2 -0
  223. package/dist/serialization.d.ts +1 -0
  224. package/dist/serialization.js +1 -0
  225. package/dist/types.d.ts +9 -2
  226. package/docs/.vitepress/config.mts +4 -0
  227. package/docs/adapters/browser/1688.md +52 -0
  228. package/docs/adapters/browser/36kr.md +2 -1
  229. package/docs/adapters/browser/doubao.md +5 -1
  230. package/docs/adapters/browser/hupu.md +53 -0
  231. package/docs/adapters/browser/sinafinance.md +32 -2
  232. package/docs/adapters/browser/weibo.md +6 -1
  233. package/docs/adapters/browser/wikipedia.md +2 -0
  234. package/docs/adapters/browser/xianyu.md +42 -0
  235. package/docs/adapters/browser/xiaoe.md +44 -0
  236. package/docs/adapters/browser/yuanbao.md +64 -0
  237. package/docs/adapters/index.md +14 -5
  238. package/docs/comparison.md +1 -1
  239. package/docs/developer/ai-workflow.md +2 -2
  240. package/docs/developer/contributing.md +1 -1
  241. package/docs/developer/testing.md +2 -0
  242. package/docs/guide/plugins.md +1 -0
  243. package/docs/guide/troubleshooting.md +11 -0
  244. package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
  245. package/docs/zh/guide/plugins.md +1 -0
  246. package/extension/dist/background.js +1127 -0
  247. package/extension/src/background.test.ts +39 -0
  248. package/extension/src/background.ts +223 -34
  249. package/extension/src/cdp.ts +194 -4
  250. package/extension/src/protocol.ts +22 -1
  251. package/package.json +3 -2
  252. package/scripts/postinstall.js +1 -1
  253. package/skills/opencli-explorer/SKILL.md +1 -1
  254. package/skills/opencli-oneshot/SKILL.md +2 -2
  255. package/skills/opencli-operate/SKILL.md +120 -27
  256. package/skills/opencli-usage/SKILL.md +31 -20
  257. package/skills/opencli-usage/browser.md +114 -16
  258. package/skills/opencli-usage/public-api.md +32 -3
  259. package/skills/smart-search/SKILL.md +156 -0
  260. package/skills/smart-search/references/sources-ai.md +74 -0
  261. package/skills/smart-search/references/sources-info.md +43 -0
  262. package/skills/smart-search/references/sources-media.md +50 -0
  263. package/skills/smart-search/references/sources-other.md +42 -0
  264. package/skills/smart-search/references/sources-shopping.md +31 -0
  265. package/skills/smart-search/references/sources-social.md +51 -0
  266. package/skills/smart-search/references/sources-tech.md +42 -0
  267. package/skills/smart-search/references/sources-travel.md +20 -0
  268. package/src/browser/base-page.ts +41 -6
  269. package/src/browser/bridge.ts +11 -8
  270. package/src/browser/cdp.ts +1 -8
  271. package/src/browser/daemon-client.ts +11 -1
  272. package/src/browser/dom-helpers.ts +43 -31
  273. package/src/browser/dom-snapshot.ts +23 -1
  274. package/src/browser/page.ts +115 -31
  275. package/src/browser.test.ts +1 -1
  276. package/src/build-manifest.ts +2 -0
  277. package/src/cli.test.ts +133 -0
  278. package/src/cli.ts +73 -11
  279. package/src/clis/1688/item.test.ts +69 -0
  280. package/src/clis/1688/item.ts +282 -0
  281. package/src/clis/1688/search.test.ts +81 -0
  282. package/src/clis/1688/search.ts +402 -0
  283. package/src/clis/1688/shared.test.ts +75 -0
  284. package/src/clis/1688/shared.ts +623 -0
  285. package/src/clis/1688/store.test.ts +69 -0
  286. package/src/clis/1688/store.ts +300 -0
  287. package/src/clis/amazon/bestsellers.test.ts +12 -3
  288. package/src/clis/amazon/bestsellers.ts +6 -178
  289. package/src/clis/amazon/movers-shakers.ts +8 -0
  290. package/src/clis/amazon/new-releases.ts +8 -0
  291. package/src/clis/amazon/rankings.test.ts +47 -0
  292. package/src/clis/amazon/rankings.ts +312 -0
  293. package/src/clis/amazon/shared.test.ts +16 -0
  294. package/src/clis/amazon/shared.ts +134 -12
  295. package/src/clis/bilibili/comments.test.ts +4 -3
  296. package/src/clis/bilibili/comments.ts +2 -2
  297. package/src/clis/bilibili/download.ts +2 -1
  298. package/src/clis/bilibili/subtitle.test.ts +2 -1
  299. package/src/clis/bilibili/subtitle.ts +4 -3
  300. package/src/clis/bilibili/utils.test.ts +21 -0
  301. package/src/clis/bilibili/utils.ts +27 -0
  302. package/src/clis/douban/marks.ts +1 -1
  303. package/src/clis/douban/subject.yaml +50 -19
  304. package/src/clis/doubao/utils.ts +32 -12
  305. package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
  306. package/src/clis/douyin/_shared/transcode.test.ts +0 -2
  307. package/src/clis/douyin/draft.test.ts +0 -2
  308. package/src/clis/facebook/search.test.ts +0 -2
  309. package/src/clis/gemini/ask.test.ts +116 -0
  310. package/src/clis/gemini/ask.ts +10 -3
  311. package/src/clis/gemini/reply-state.test.ts +708 -0
  312. package/src/clis/gemini/utils.test.ts +184 -2
  313. package/src/clis/gemini/utils.ts +588 -60
  314. package/src/clis/hupu/detail.ts +126 -0
  315. package/src/clis/hupu/hot.yaml +43 -0
  316. package/src/clis/hupu/like.ts +76 -0
  317. package/src/clis/hupu/reply.ts +76 -0
  318. package/src/clis/hupu/search.ts +95 -0
  319. package/src/clis/hupu/unlike.ts +76 -0
  320. package/src/clis/hupu/utils.ts +381 -0
  321. package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
  322. package/src/clis/instagram/_shared/private-publish.ts +1303 -0
  323. package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
  324. package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
  325. package/src/clis/instagram/_shared/runtime-info.ts +91 -0
  326. package/src/clis/instagram/note.test.ts +96 -0
  327. package/src/clis/instagram/note.ts +254 -0
  328. package/src/clis/instagram/post.test.ts +1716 -0
  329. package/src/clis/instagram/post.ts +1620 -0
  330. package/src/clis/instagram/reel.test.ts +191 -0
  331. package/src/clis/instagram/reel.ts +886 -0
  332. package/src/clis/instagram/story.test.ts +191 -0
  333. package/src/clis/instagram/story.ts +151 -0
  334. package/src/clis/sinafinance/stock-rank.ts +68 -0
  335. package/src/clis/substack/utils.test.ts +0 -2
  336. package/src/clis/twitter/post.test.ts +157 -0
  337. package/src/clis/twitter/post.ts +82 -48
  338. package/src/clis/twitter/reply.test.ts +177 -0
  339. package/src/clis/twitter/reply.ts +285 -39
  340. package/src/clis/twitter/search.test.ts +88 -5
  341. package/src/clis/twitter/search.ts +68 -5
  342. package/src/clis/xianyu/chat.test.ts +20 -0
  343. package/src/clis/xianyu/chat.ts +175 -0
  344. package/src/clis/xianyu/item.test.ts +67 -0
  345. package/src/clis/xianyu/item.ts +172 -0
  346. package/src/clis/xianyu/search.test.ts +22 -0
  347. package/src/clis/xianyu/search.ts +151 -0
  348. package/src/clis/xianyu/utils.ts +9 -0
  349. package/src/clis/xiaoe/catalog.yaml +129 -0
  350. package/src/clis/xiaoe/content.yaml +43 -0
  351. package/src/clis/xiaoe/courses.yaml +73 -0
  352. package/src/clis/xiaoe/detail.yaml +39 -0
  353. package/src/clis/xiaoe/play-url.yaml +124 -0
  354. package/src/clis/xiaohongshu/comments.test.ts +0 -2
  355. package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
  356. package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
  357. package/src/clis/xiaohongshu/download.test.ts +0 -2
  358. package/src/clis/xiaohongshu/note.test.ts +0 -2
  359. package/src/clis/xiaohongshu/publish.test.ts +0 -2
  360. package/src/clis/xiaohongshu/search.test.ts +59 -48
  361. package/src/clis/xiaohongshu/search.ts +31 -21
  362. package/src/clis/yuanbao/ask.test.ts +156 -0
  363. package/src/clis/yuanbao/ask.ts +522 -0
  364. package/src/clis/yuanbao/new.test.ts +36 -0
  365. package/src/clis/yuanbao/new.ts +81 -0
  366. package/src/clis/yuanbao/shared.ts +57 -0
  367. package/src/clis/zhihu/question.test.ts +42 -17
  368. package/src/clis/zhihu/question.ts +31 -26
  369. package/src/commanderAdapter.test.ts +51 -0
  370. package/src/commanderAdapter.ts +8 -4
  371. package/src/completion.test.ts +30 -0
  372. package/src/completion.ts +3 -1
  373. package/src/doctor.ts +1 -1
  374. package/src/electron-apps.ts +9 -1
  375. package/src/errors.ts +1 -1
  376. package/src/execution.ts +26 -30
  377. package/src/explore.ts +1 -1
  378. package/src/launcher.test.ts +121 -7
  379. package/src/launcher.ts +87 -9
  380. package/src/output.test.ts +50 -90
  381. package/src/output.ts +10 -1
  382. package/src/pipeline/executor.test.ts +0 -2
  383. package/src/pipeline/steps/download.test.ts +0 -2
  384. package/src/registry.ts +2 -0
  385. package/src/serialization.ts +2 -0
  386. package/src/types.ts +9 -2
  387. package/tests/e2e/browser-auth.test.ts +9 -0
  388. package/CLI-EXPLORER.md +0 -724
  389. package/CLI-ONESHOT.md +0 -216
  390. package/SKILL.md +0 -59
@@ -11,6 +11,8 @@
11
11
  import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
12
12
  export declare abstract class BasePage implements IPage {
13
13
  protected _lastUrl: string | null;
14
+ /** Cached previous snapshot hashes for incremental diff marking */
15
+ private _prevSnapshotHashes;
14
16
  abstract goto(url: string, options?: {
15
17
  waitUntil?: 'load' | 'none';
16
18
  settleMs?: number;
@@ -22,10 +24,10 @@ export declare abstract class BasePage implements IPage {
22
24
  }): Promise<BrowserCookie[]>;
23
25
  abstract screenshot(options?: ScreenshotOptions): Promise<string>;
24
26
  abstract tabs(): Promise<unknown[]>;
25
- abstract closeTab(index?: number): Promise<void>;
26
- abstract newTab(): Promise<void>;
27
27
  abstract selectTab(index: number): Promise<void>;
28
28
  click(ref: string): Promise<void>;
29
+ /** Override in subclasses with CDP native click support */
30
+ protected tryNativeClick(_x: number, _y: number): Promise<boolean>;
29
31
  typeText(ref: string, text: string): Promise<void>;
30
32
  pressKey(key: string): Promise<void>;
31
33
  scrollTo(ref: string): Promise<unknown>;
@@ -13,9 +13,28 @@ import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, waitForCaptureJs, waitF
13
13
  import { formatSnapshot } from '../snapshotFormatter.js';
14
14
  export class BasePage {
15
15
  _lastUrl = null;
16
+ /** Cached previous snapshot hashes for incremental diff marking */
17
+ _prevSnapshotHashes = null;
16
18
  // ── Shared DOM helper implementations ──
17
19
  async click(ref) {
18
- await this.evaluate(clickJs(ref));
20
+ const result = await this.evaluate(clickJs(ref));
21
+ // Backwards compat: old format returned 'clicked' string
22
+ if (typeof result === 'string' || result == null)
23
+ return;
24
+ // JS click succeeded
25
+ if (result.status === 'clicked')
26
+ return;
27
+ // JS click failed — try CDP native click if coordinates available
28
+ if (result.x != null && result.y != null) {
29
+ const success = await this.tryNativeClick(result.x, result.y);
30
+ if (success)
31
+ return;
32
+ }
33
+ throw new Error(`Click failed: ${result.error ?? 'JS click and CDP fallback both failed'}`);
34
+ }
35
+ /** Override in subclasses with CDP native click support */
36
+ async tryNativeClick(_x, _y) {
37
+ return false;
19
38
  }
20
39
  async typeText(ref, text) {
21
40
  await this.evaluate(typeTextJs(ref, text));
@@ -75,17 +94,31 @@ export class BasePage {
75
94
  }
76
95
  async snapshot(opts = {}) {
77
96
  const snapshotJs = generateSnapshotJs({
78
- viewportExpand: opts.viewportExpand ?? 800,
97
+ viewportExpand: opts.viewportExpand ?? 2000,
79
98
  maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
80
99
  interactiveOnly: opts.interactive ?? false,
81
100
  maxTextLength: opts.maxTextLength ?? 120,
82
101
  includeScrollInfo: true,
83
102
  bboxDedup: true,
103
+ previousHashes: this._prevSnapshotHashes,
84
104
  });
85
105
  try {
86
- return await this.evaluate(snapshotJs);
106
+ const result = await this.evaluate(snapshotJs);
107
+ // Read back the hashes stored by the snapshot for next diff
108
+ try {
109
+ const hashes = await this.evaluate('window.__opencli_prev_hashes');
110
+ this._prevSnapshotHashes = typeof hashes === 'string' ? hashes : null;
111
+ }
112
+ catch {
113
+ // Non-fatal: diff is best-effort
114
+ }
115
+ return result;
87
116
  }
88
- catch {
117
+ catch (err) {
118
+ // Log snapshot failure for debugging, then fallback to basic accessibility tree
119
+ if (process.env.DEBUG_SNAPSHOT) {
120
+ console.error('[snapshot] DOM snapshot failed, falling back to accessibility tree:', err?.message?.slice(0, 200));
121
+ }
89
122
  return this._basicSnapshot(opts);
90
123
  }
91
124
  }
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
  import * as path from 'node:path';
7
7
  import * as fs from 'node:fs';
8
8
  import { Page } from './page.js';
9
- import { isDaemonRunning, isExtensionConnected } from './daemon-client.js';
9
+ import { fetchDaemonStatus, isExtensionConnected } from './daemon-client.js';
10
10
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
11
11
  const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
12
12
  /**
@@ -52,14 +52,16 @@ export class BrowserBridge {
52
52
  async _ensureDaemon(timeoutSeconds) {
53
53
  const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
54
54
  const timeoutMs = effectiveSeconds * 1000;
55
+ // Single status check instead of two separate fetchDaemonStatus() calls
56
+ const status = await fetchDaemonStatus();
55
57
  // Fast path: extension already connected
56
- if (await isExtensionConnected())
58
+ if (status?.extensionConnected)
57
59
  return;
58
60
  // Daemon running but no extension — wait for extension with progress
59
- if (await isDaemonRunning()) {
61
+ if (status !== null) {
60
62
  if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
61
- process.stderr.write('⏳ Waiting for Chrome extension to connect...\n');
62
- process.stderr.write(' Make sure Chrome is open and the OpenCLI extension is enabled.\n');
63
+ process.stderr.write('⏳ Waiting for Chrome/Chromium extension to connect...\n');
64
+ process.stderr.write(' Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.\n');
63
65
  }
64
66
  const deadline = Date.now() + timeoutMs;
65
67
  while (Date.now() < deadline) {
@@ -68,7 +70,7 @@ export class BrowserBridge {
68
70
  return;
69
71
  }
70
72
  throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
71
- 'Please install and enable the opencli Browser Bridge extension in Chrome.');
73
+ 'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
72
74
  }
73
75
  // No daemon — spawn one
74
76
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -96,9 +98,9 @@ export class BrowserBridge {
96
98
  if (await isExtensionConnected())
97
99
  return;
98
100
  }
99
- if (await isDaemonRunning()) {
101
+ if ((await fetchDaemonStatus()) !== null) {
100
102
  throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
101
- 'Please install and enable the opencli Browser Bridge extension in Chrome.');
103
+ 'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
102
104
  }
103
105
  throw new Error('Failed to start opencli daemon. Try running manually:\n' +
104
106
  ` node ${daemonPath}\n` +
@@ -189,12 +189,6 @@ class CDPPage extends BasePage {
189
189
  async tabs() {
190
190
  return [];
191
191
  }
192
- async closeTab(_index) {
193
- // Not supported in direct CDP mode
194
- }
195
- async newTab() {
196
- await this.bridge.send('Target.createTarget', { url: 'about:blank' });
197
- }
198
192
  async selectTab(_index) {
199
193
  // Not supported in direct CDP mode
200
194
  }
@@ -234,6 +228,8 @@ function scoreCDPTarget(target, preferredPattern) {
234
228
  return Number.NEGATIVE_INFINITY;
235
229
  if (haystack.includes('devtools'))
236
230
  return Number.NEGATIVE_INFINITY;
231
+ if (type === 'background_page' || type === 'service_worker')
232
+ return Number.NEGATIVE_INFINITY;
237
233
  let score = 0;
238
234
  if (preferredPattern && preferredPattern.test(haystack))
239
235
  score += 1000;
@@ -6,7 +6,7 @@
6
6
  import type { BrowserSessionInfo } from '../types.js';
7
7
  export interface DaemonCommand {
8
8
  id: string;
9
- action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
9
+ action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'insert-text' | 'bind-current' | 'network-capture-start' | 'network-capture-read' | 'cdp';
10
10
  tabId?: number;
11
11
  code?: string;
12
12
  workspace?: string;
@@ -14,6 +14,8 @@ export interface DaemonCommand {
14
14
  op?: string;
15
15
  index?: number;
16
16
  domain?: string;
17
+ matchDomain?: string;
18
+ matchPathPrefix?: string;
17
19
  format?: 'png' | 'jpeg';
18
20
  quality?: number;
19
21
  fullPage?: boolean;
@@ -21,6 +23,10 @@ export interface DaemonCommand {
21
23
  files?: string[];
22
24
  /** CSS selector for file input element (set-file-input action) */
23
25
  selector?: string;
26
+ /** Raw text payload for insert-text action */
27
+ text?: string;
28
+ /** URL substring filter pattern for network capture */
29
+ pattern?: string;
24
30
  cdpMethod?: string;
25
31
  cdpParams?: Record<string, unknown>;
26
32
  }
@@ -62,3 +68,7 @@ export declare function isExtensionConnected(): Promise<boolean>;
62
68
  */
63
69
  export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
64
70
  export declare function listSessions(): Promise<BrowserSessionInfo[]>;
71
+ export declare function bindCurrentTab(workspace: string, opts?: {
72
+ matchDomain?: string;
73
+ matchPathPrefix?: string;
74
+ }): Promise<unknown>;
@@ -108,3 +108,6 @@ export async function listSessions() {
108
108
  const result = await sendCommand('sessions');
109
109
  return Array.isArray(result) ? result : [];
110
110
  }
111
+ export async function bindCurrentTab(workspace, opts = {}) {
112
+ return sendCommand('bind-current', { workspace, ...opts });
113
+ }
@@ -4,9 +4,11 @@
4
4
  * Used by both Page (daemon mode) and CDPPage (direct CDP mode)
5
5
  * to eliminate code duplication for click, type, press, wait, scroll, etc.
6
6
  */
7
- /** Generate JS to click an element by ref */
7
+ /** Generate JS to click an element by ref.
8
+ * Returns { status, x, y, w, h } for CDP fallback when JS click fails. */
8
9
  export declare function clickJs(ref: string): string;
9
- /** Generate JS to type text into an element by ref */
10
+ /** Generate JS to type text into an element by ref.
11
+ * Uses native setter for React compat + execCommand for contenteditable. */
10
12
  export declare function typeTextJs(ref: string, text: string): string;
11
13
  /** Generate JS to press a keyboard key */
12
14
  export declare function pressKeyJs(key: string): string;
@@ -4,63 +4,74 @@
4
4
  * Used by both Page (daemon mode) and CDPPage (direct CDP mode)
5
5
  * to eliminate code duplication for click, type, press, wait, scroll, etc.
6
6
  */
7
- /** Generate JS to click an element by ref */
8
- export function clickJs(ref) {
9
- const safeRef = JSON.stringify(ref);
7
+ /** Shared element lookup JS fragment (4-strategy resolution) */
8
+ function resolveElementJs(safeRef, selectorSet) {
10
9
  return `
11
- (() => {
12
10
  const ref = ${safeRef};
13
- // 1. data-opencli-ref (set by snapshot engine)
14
11
  let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
15
- // 2. data-ref (legacy)
16
12
  if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
17
- // 3. CSS selector
18
13
  if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
19
14
  try { el = document.querySelector(ref); } catch {}
20
15
  }
21
- // 4. Numeric index into interactive elements
22
16
  if (!el) {
23
17
  const idx = parseInt(ref, 10);
24
18
  if (!isNaN(idx)) {
25
- el = document.querySelectorAll('a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')[idx];
19
+ el = document.querySelectorAll('${selectorSet}')[idx];
26
20
  }
27
- }
21
+ }`;
22
+ }
23
+ /** Generate JS to click an element by ref.
24
+ * Returns { status, x, y, w, h } for CDP fallback when JS click fails. */
25
+ export function clickJs(ref) {
26
+ const safeRef = JSON.stringify(ref);
27
+ return `
28
+ (() => {
29
+ ${resolveElementJs(safeRef, 'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')}
28
30
  if (!el) throw new Error('Element not found: ' + ref);
29
31
  el.scrollIntoView({ behavior: 'instant', block: 'center' });
30
- el.click();
31
- return 'clicked';
32
+ const rect = el.getBoundingClientRect();
33
+ const x = Math.round(rect.left + rect.width / 2);
34
+ const y = Math.round(rect.top + rect.height / 2);
35
+ try {
36
+ el.click();
37
+ return { status: 'clicked', x, y, w: Math.round(rect.width), h: Math.round(rect.height) };
38
+ } catch (e) {
39
+ return { status: 'js_failed', x, y, w: Math.round(rect.width), h: Math.round(rect.height), error: e.message };
40
+ }
32
41
  })()
33
42
  `;
34
43
  }
35
- /** Generate JS to type text into an element by ref */
44
+ /** Generate JS to type text into an element by ref.
45
+ * Uses native setter for React compat + execCommand for contenteditable. */
36
46
  export function typeTextJs(ref, text) {
37
47
  const safeRef = JSON.stringify(ref);
38
48
  const safeText = JSON.stringify(text);
39
49
  return `
40
50
  (() => {
41
- const ref = ${safeRef};
42
- // 1. data-opencli-ref (set by snapshot engine)
43
- let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
44
- // 2. data-ref (legacy)
45
- if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
46
- // 3. CSS selector
47
- if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
48
- try { el = document.querySelector(ref); } catch {}
49
- }
50
- // 4. Numeric index into typeable elements
51
- if (!el) {
52
- const idx = parseInt(ref, 10);
53
- if (!isNaN(idx)) {
54
- el = document.querySelectorAll('input, textarea, [contenteditable="true"]')[idx];
55
- }
56
- }
51
+ ${resolveElementJs(safeRef, 'input, textarea, [contenteditable="true"]')}
57
52
  if (!el) throw new Error('Element not found: ' + ref);
58
53
  el.focus();
59
54
  if (el.isContentEditable) {
60
- el.textContent = ${safeText};
55
+ // Select all content + delete, then insert (supports undo, works with rich text editors)
56
+ const sel = window.getSelection();
57
+ const range = document.createRange();
58
+ range.selectNodeContents(el);
59
+ sel.removeAllRanges();
60
+ sel.addRange(range);
61
+ document.execCommand('delete', false);
62
+ document.execCommand('insertText', false, ${safeText});
61
63
  el.dispatchEvent(new Event('input', { bubbles: true }));
62
64
  } else {
63
- el.value = ${safeText};
65
+ // Use native setter for React/framework compatibility (match element type)
66
+ const proto = el instanceof HTMLTextAreaElement
67
+ ? HTMLTextAreaElement.prototype
68
+ : HTMLInputElement.prototype;
69
+ const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
70
+ if (nativeSetter) {
71
+ nativeSetter.call(el, ${safeText});
72
+ } else {
73
+ el.value = ${safeText};
74
+ }
64
75
  el.dispatchEvent(new Event('input', { bubbles: true }));
65
76
  el.dispatchEvent(new Event('change', { bubbles: true }));
66
77
  }
@@ -336,6 +336,8 @@ export function generateSnapshotJs(opts = {}) {
336
336
  if (role && INTERACTIVE_ROLES.has(role)) return true;
337
337
  if (el.hasAttribute('onclick') || el.hasAttribute('onmousedown') || el.hasAttribute('ontouchstart')) return true;
338
338
  if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') return true;
339
+ // Framework event listener detection (React/Vue/Angular onClick)
340
+ if (hasFrameworkListener(el)) return true;
339
341
  try { if (window.getComputedStyle(el).cursor === 'pointer') return true; } catch {}
340
342
  if (el.isContentEditable && el.getAttribute('contenteditable') !== 'false') return true;
341
343
  // Search element heuristic detection
@@ -343,9 +345,29 @@ export function generateSnapshotJs(opts = {}) {
343
345
  return false;
344
346
  }
345
347
 
348
+ function hasFrameworkListener(el) {
349
+ try {
350
+ // React: __reactProps$xxx / __reactEvents$xxx with onClick/onMouseDown
351
+ for (const key of Object.keys(el)) {
352
+ if (key.startsWith('__reactProps$') || key.startsWith('__reactEvents$')) {
353
+ const props = el[key];
354
+ if (props && (props.onClick || props.onMouseDown || props.onPointerDown)) return true;
355
+ }
356
+ }
357
+ // Vue 3: _vei (Vue Event Invoker) with onClick
358
+ if (el._vei && (el._vei.onClick || el._vei.click || el._vei.onMousedown)) return true;
359
+ // Vue 2: __vue__ instance with $listeners
360
+ if (el.__vue__?.$listeners?.click) return true;
361
+ // Angular: ng-reflect-click binding
362
+ if (el.hasAttribute('ng-reflect-click')) return true;
363
+ } catch { /* ignore errors from cross-origin or frozen objects */ }
364
+ return false;
365
+ }
366
+
346
367
  function isSearchElement(el) {
347
368
  // Check class names for search indicators
348
- const className = el.className?.toLowerCase() || '';
369
+ // Note: SVG elements have className as SVGAnimatedString (not a string), use baseVal
370
+ const className = (typeof el.className === 'string' ? el.className : el.className?.baseVal || '').toLowerCase();
349
371
  const classes = className.split(/\\s+/).filter(Boolean);
350
372
  for (const cls of classes) {
351
373
  const cleaned = cls.replace(/[^a-z0-9-]/g, '');
@@ -37,20 +37,25 @@ export declare class Page extends BasePage {
37
37
  /** Close the automation window in the extension */
38
38
  closeWindow(): Promise<void>;
39
39
  tabs(): Promise<unknown[]>;
40
- closeTab(index?: number): Promise<void>;
41
- newTab(): Promise<void>;
42
40
  selectTab(index: number): Promise<void>;
43
41
  /**
44
42
  * Capture a screenshot via CDP Page.captureScreenshot.
45
43
  */
46
44
  screenshot(options?: ScreenshotOptions): Promise<string>;
45
+ startNetworkCapture(pattern?: string): Promise<void>;
46
+ readNetworkCapture(): Promise<unknown[]>;
47
47
  /**
48
48
  * Set local file paths on a file input element via CDP DOM.setFileInputFiles.
49
49
  * Chrome reads the files directly from the local filesystem, avoiding the
50
50
  * payload size limits of base64-in-evaluate.
51
51
  */
52
52
  setFileInput(files: string[], selector?: string): Promise<void>;
53
+ insertText(text: string): Promise<void>;
53
54
  cdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
55
+ /** CDP native click fallback — called when JS el.click() fails */
56
+ protected tryNativeClick(x: number, y: number): Promise<boolean>;
57
+ /** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
58
+ clickWithQuads(ref: string): Promise<void>;
54
59
  nativeClick(x: number, y: number): Promise<void>;
55
60
  nativeType(text: string): Promise<void>;
56
61
  nativeKeyPress(key: string, modifiers?: string[]): Promise<void>;
@@ -52,26 +52,17 @@ export class Page extends BasePage {
52
52
  this._tabId = result.tabId;
53
53
  }
54
54
  this._lastUrl = url;
55
- // Inject stealth anti-detection patches (guard flag prevents double-injection).
56
- try {
57
- await sendCommand('exec', {
58
- code: generateStealthJs(),
59
- ...this._cmdOpts(),
60
- });
61
- }
62
- catch {
63
- // Non-fatal: stealth is best-effort
64
- }
65
- // Smart settle: use DOM stability detection instead of fixed sleep.
66
- // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
55
+ // Inject stealth + settle in a single round-trip instead of two sequential exec calls.
56
+ // The stealth guard flag prevents double-injection; settle uses DOM stability detection.
67
57
  if (options?.waitUntil !== 'none') {
68
58
  const maxMs = options?.settleMs ?? 1000;
69
- const settleOpts = {
70
- code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
59
+ const combinedCode = `${generateStealthJs()};\n${waitForDomStableJs(maxMs, Math.min(500, maxMs))}`;
60
+ const combinedOpts = {
61
+ code: combinedCode,
71
62
  ...this._cmdOpts(),
72
63
  };
73
64
  try {
74
- await sendCommand('exec', settleOpts);
65
+ await sendCommand('exec', combinedOpts);
75
66
  }
76
67
  catch (err) {
77
68
  if (!isRetryableSettleError(err))
@@ -81,17 +72,26 @@ export class Page extends BasePage {
81
72
  // to load, then retry the settle probe once.
82
73
  try {
83
74
  await new Promise((r) => setTimeout(r, 200));
84
- await sendCommand('exec', settleOpts);
75
+ await sendCommand('exec', combinedOpts);
85
76
  }
86
77
  catch (retryErr) {
87
78
  if (!isRetryableSettleError(retryErr))
88
79
  throw retryErr;
89
- // Retry also failed — give up silently. Settle is best-effort
90
- // after successful navigation; the next real command will surface
91
- // any persistent target error immediately.
92
80
  }
93
81
  }
94
82
  }
83
+ else {
84
+ // Even with waitUntil='none', still inject stealth (best-effort)
85
+ try {
86
+ await sendCommand('exec', {
87
+ code: generateStealthJs(),
88
+ ...this._cmdOpts(),
89
+ });
90
+ }
91
+ catch {
92
+ // Non-fatal: stealth is best-effort
93
+ }
94
+ }
95
95
  }
96
96
  getActiveTabId() {
97
97
  return this._tabId;
@@ -120,22 +120,15 @@ export class Page extends BasePage {
120
120
  catch {
121
121
  // Window may already be closed or daemon may be down
122
122
  }
123
+ finally {
124
+ this._tabId = undefined;
125
+ this._lastUrl = null;
126
+ }
123
127
  }
124
128
  async tabs() {
125
129
  const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
126
130
  return Array.isArray(result) ? result : [];
127
131
  }
128
- async closeTab(index) {
129
- await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
130
- // Invalidate cached tabId — the closed tab might have been our active one.
131
- // We can't know for sure (close-by-index doesn't return tabId), so reset.
132
- this._tabId = undefined;
133
- }
134
- async newTab() {
135
- const result = await sendCommand('tabs', { op: 'new', ...this._wsOpt() });
136
- if (result?.tabId)
137
- this._tabId = result.tabId;
138
- }
139
132
  async selectTab(index) {
140
133
  const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() });
141
134
  if (result?.selected)
@@ -156,6 +149,18 @@ export class Page extends BasePage {
156
149
  }
157
150
  return base64;
158
151
  }
152
+ async startNetworkCapture(pattern = '') {
153
+ await sendCommand('network-capture-start', {
154
+ pattern,
155
+ ...this._cmdOpts(),
156
+ });
157
+ }
158
+ async readNetworkCapture() {
159
+ const result = await sendCommand('network-capture-read', {
160
+ ...this._cmdOpts(),
161
+ });
162
+ return Array.isArray(result) ? result : [];
163
+ }
159
164
  /**
160
165
  * Set local file paths on a file input element via CDP DOM.setFileInputFiles.
161
166
  * Chrome reads the files directly from the local filesystem, avoiding the
@@ -171,6 +176,15 @@ export class Page extends BasePage {
171
176
  throw new Error('setFileInput returned no count — command may not be supported by the extension');
172
177
  }
173
178
  }
179
+ async insertText(text) {
180
+ const result = await sendCommand('insert-text', {
181
+ text,
182
+ ...this._cmdOpts(),
183
+ });
184
+ if (!result?.inserted) {
185
+ throw new Error('insertText returned no inserted flag — command may not be supported by the extension');
186
+ }
187
+ }
174
188
  async cdp(method, params = {}) {
175
189
  return sendCommand('cdp', {
176
190
  cdpMethod: method,
@@ -178,6 +192,74 @@ export class Page extends BasePage {
178
192
  ...this._cmdOpts(),
179
193
  });
180
194
  }
195
+ /** CDP native click fallback — called when JS el.click() fails */
196
+ async tryNativeClick(x, y) {
197
+ try {
198
+ await this.nativeClick(x, y);
199
+ return true;
200
+ }
201
+ catch {
202
+ return false;
203
+ }
204
+ }
205
+ /** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
206
+ async clickWithQuads(ref) {
207
+ const safeRef = JSON.stringify(ref);
208
+ const cssSelector = `[data-opencli-ref="${ref.replace(/"/g, '\\"')}"]`;
209
+ // Scroll element into view first
210
+ await this.evaluate(`
211
+ (() => {
212
+ const el = document.querySelector('[data-opencli-ref="' + ${safeRef} + '"]');
213
+ if (el) el.scrollIntoView({ behavior: 'instant', block: 'center' });
214
+ return !!el;
215
+ })()
216
+ `);
217
+ try {
218
+ // Find DOM node via CDP
219
+ const doc = await this.cdp('DOM.getDocument', {});
220
+ const result = await this.cdp('DOM.querySelectorAll', {
221
+ nodeId: doc.root.nodeId,
222
+ selector: cssSelector,
223
+ });
224
+ if (!result.nodeIds?.length)
225
+ throw new Error('DOM node not found');
226
+ const nodeId = result.nodeIds[0];
227
+ // Try getContentQuads first (precise for inline elements)
228
+ try {
229
+ const quads = await this.cdp('DOM.getContentQuads', { nodeId });
230
+ if (quads.quads?.length) {
231
+ const q = quads.quads[0];
232
+ const cx = (q[0] + q[2] + q[4] + q[6]) / 4;
233
+ const cy = (q[1] + q[3] + q[5] + q[7]) / 4;
234
+ await this.nativeClick(Math.round(cx), Math.round(cy));
235
+ return;
236
+ }
237
+ }
238
+ catch { /* fallthrough */ }
239
+ // Try getBoxModel
240
+ try {
241
+ const box = await this.cdp('DOM.getBoxModel', { nodeId });
242
+ if (box.model?.content) {
243
+ const c = box.model.content;
244
+ const cx = (c[0] + c[2] + c[4] + c[6]) / 4;
245
+ const cy = (c[1] + c[3] + c[5] + c[7]) / 4;
246
+ await this.nativeClick(Math.round(cx), Math.round(cy));
247
+ return;
248
+ }
249
+ }
250
+ catch { /* fallthrough */ }
251
+ }
252
+ catch { /* fallthrough */ }
253
+ // Final fallback: regular click
254
+ await this.evaluate(`
255
+ (() => {
256
+ const el = document.querySelector('[data-opencli-ref="' + ${safeRef} + '"]');
257
+ if (!el) throw new Error('Element not found: ' + ${safeRef});
258
+ el.click();
259
+ return 'clicked';
260
+ })()
261
+ `);
262
+ }
181
263
  async nativeClick(x, y) {
182
264
  await this.cdp('Input.dispatchMouseEvent', {
183
265
  type: 'mousePressed',
@@ -106,7 +106,7 @@ describe('BrowserBridge state', () => {
106
106
  });
107
107
  it('fails fast when daemon is running but extension is disconnected', async () => {
108
108
  vi.spyOn(daemonClient, 'isExtensionConnected').mockResolvedValue(false);
109
- vi.spyOn(daemonClient, 'isDaemonRunning').mockResolvedValue(true);
109
+ vi.spyOn(daemonClient, 'fetchDaemonStatus').mockResolvedValue({ extensionConnected: false });
110
110
  const bridge = new BrowserBridge();
111
111
  await expect(bridge.connect({ timeout: 0.1 })).rejects.toThrow('Browser Extension is not connected');
112
112
  });
@@ -21,6 +21,7 @@ export interface ManifestEntry {
21
21
  type?: string;
22
22
  default?: unknown;
23
23
  required?: boolean;
24
+ valueRequired?: boolean;
24
25
  positional?: boolean;
25
26
  help?: string;
26
27
  choices?: string[];
@@ -26,6 +26,7 @@ function toManifestArgs(args) {
26
26
  type: arg.type ?? 'str',
27
27
  default: arg.default,
28
28
  required: !!arg.required,
29
+ valueRequired: !!arg.valueRequired || undefined,
29
30
  positional: arg.positional || undefined,
30
31
  help: arg.help ?? '',
31
32
  choices: arg.choices,