@agentrhq/webcmd 0.1.1 → 0.1.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 (415) hide show
  1. package/README.md +317 -24
  2. package/dist/src/adapter-shadow.d.ts +11 -0
  3. package/dist/src/adapter-shadow.js +72 -0
  4. package/dist/src/adapter-shadow.test.d.ts +1 -0
  5. package/dist/src/adapter-shadow.test.js +49 -0
  6. package/dist/src/adapter-source.d.ts +11 -0
  7. package/dist/src/adapter-source.js +24 -0
  8. package/dist/src/adapter-source.test.d.ts +1 -0
  9. package/dist/src/adapter-source.test.js +29 -0
  10. package/dist/src/brand.d.ts +9 -0
  11. package/dist/src/brand.js +9 -0
  12. package/dist/src/brand.test.d.ts +1 -0
  13. package/dist/src/brand.test.js +15 -0
  14. package/dist/src/browser/analyze.d.ts +118 -0
  15. package/dist/src/browser/analyze.js +405 -0
  16. package/dist/src/browser/analyze.test.d.ts +1 -0
  17. package/dist/src/browser/analyze.test.js +209 -0
  18. package/dist/src/browser/article-extract.d.ts +57 -0
  19. package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
  20. package/dist/src/browser/article-extract.e2e.test.js +105 -0
  21. package/dist/src/browser/article-extract.js +169 -0
  22. package/dist/src/browser/article-extract.test.d.ts +1 -0
  23. package/dist/src/browser/article-extract.test.js +94 -0
  24. package/dist/src/browser/ax-snapshot.d.ts +37 -0
  25. package/dist/src/browser/ax-snapshot.js +217 -0
  26. package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
  27. package/dist/src/browser/ax-snapshot.test.js +91 -0
  28. package/dist/src/browser/base-page.d.ts +151 -0
  29. package/dist/src/browser/base-page.js +1060 -0
  30. package/dist/src/browser/base-page.test.d.ts +1 -0
  31. package/dist/src/browser/base-page.test.js +803 -0
  32. package/dist/src/browser/bridge-readiness.d.ts +14 -0
  33. package/dist/src/browser/bridge-readiness.js +24 -0
  34. package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
  35. package/dist/src/browser/bridge-readiness.test.js +58 -0
  36. package/dist/src/browser/bridge.d.ts +26 -0
  37. package/dist/src/browser/bridge.js +58 -0
  38. package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
  39. package/dist/src/browser/cdp-click-fixture.test.js +87 -0
  40. package/dist/src/browser/cdp.d.ts +46 -0
  41. package/dist/src/browser/cdp.js +536 -0
  42. package/dist/src/browser/cdp.test.d.ts +1 -0
  43. package/dist/src/browser/cdp.test.js +79 -0
  44. package/dist/src/browser/compound.d.ts +59 -0
  45. package/dist/src/browser/compound.js +112 -0
  46. package/dist/src/browser/compound.test.d.ts +1 -0
  47. package/dist/src/browser/compound.test.js +175 -0
  48. package/dist/src/browser/config.d.ts +2 -0
  49. package/dist/src/browser/config.js +14 -0
  50. package/dist/src/browser/daemon-client.d.ts +32 -0
  51. package/dist/src/browser/daemon-client.js +135 -0
  52. package/dist/src/browser/daemon-client.test.d.ts +1 -0
  53. package/dist/src/browser/daemon-client.test.js +326 -0
  54. package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
  55. package/dist/src/browser/daemon-lifecycle.js +147 -0
  56. package/dist/src/browser/daemon-transport.d.ts +54 -0
  57. package/dist/src/browser/daemon-transport.js +68 -0
  58. package/dist/src/browser/daemon-version.d.ts +4 -0
  59. package/dist/src/browser/daemon-version.js +12 -0
  60. package/dist/src/browser/dom-helpers.d.ts +41 -0
  61. package/dist/src/browser/dom-helpers.js +239 -0
  62. package/dist/src/browser/dom-helpers.test.d.ts +1 -0
  63. package/dist/src/browser/dom-helpers.test.js +106 -0
  64. package/dist/src/browser/dom-snapshot.d.ts +93 -0
  65. package/dist/src/browser/dom-snapshot.js +894 -0
  66. package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
  67. package/dist/src/browser/dom-snapshot.test.js +313 -0
  68. package/dist/src/browser/errors.d.ts +38 -0
  69. package/dist/src/browser/errors.js +79 -0
  70. package/dist/src/browser/errors.test.d.ts +1 -0
  71. package/dist/src/browser/errors.test.js +60 -0
  72. package/dist/src/browser/extract.d.ts +69 -0
  73. package/dist/src/browser/extract.js +132 -0
  74. package/dist/src/browser/extract.test.d.ts +1 -0
  75. package/dist/src/browser/extract.test.js +129 -0
  76. package/dist/src/browser/find.d.ts +84 -0
  77. package/dist/src/browser/find.js +398 -0
  78. package/dist/src/browser/find.test.d.ts +1 -0
  79. package/dist/src/browser/find.test.js +180 -0
  80. package/dist/src/browser/html-tree.d.ts +75 -0
  81. package/dist/src/browser/html-tree.js +112 -0
  82. package/dist/src/browser/html-tree.test.d.ts +1 -0
  83. package/dist/src/browser/html-tree.test.js +181 -0
  84. package/dist/src/browser/index.d.ts +14 -0
  85. package/dist/src/browser/index.js +12 -0
  86. package/dist/src/browser/network-cache.d.ts +49 -0
  87. package/dist/src/browser/network-cache.js +78 -0
  88. package/dist/src/browser/network-cache.test.d.ts +1 -0
  89. package/dist/src/browser/network-cache.test.js +75 -0
  90. package/dist/src/browser/network-interceptor.d.ts +11 -0
  91. package/dist/src/browser/network-interceptor.js +11 -0
  92. package/dist/src/browser/network-key.d.ts +22 -0
  93. package/dist/src/browser/network-key.js +66 -0
  94. package/dist/src/browser/network-key.test.d.ts +1 -0
  95. package/dist/src/browser/network-key.test.js +49 -0
  96. package/dist/src/browser/page.d.ts +83 -0
  97. package/dist/src/browser/page.js +453 -0
  98. package/dist/src/browser/page.test.d.ts +1 -0
  99. package/dist/src/browser/page.test.js +406 -0
  100. package/dist/src/browser/profile.d.ts +14 -0
  101. package/dist/src/browser/profile.js +86 -0
  102. package/dist/src/browser/protocol.d.ts +63 -0
  103. package/dist/src/browser/protocol.js +1 -0
  104. package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
  105. package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
  106. package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
  107. package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
  108. package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
  109. package/dist/src/browser/runtime/local-cloak/network.js +120 -0
  110. package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
  111. package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
  112. package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
  113. package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
  114. package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
  115. package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
  116. package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
  117. package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
  118. package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
  119. package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
  120. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
  121. package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
  122. package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
  123. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
  124. package/dist/src/browser/runtime/provider.d.ts +9 -0
  125. package/dist/src/browser/runtime/provider.js +1 -0
  126. package/dist/src/browser/runtime/provider.test.d.ts +2 -0
  127. package/dist/src/browser/runtime/provider.test.js +49 -0
  128. package/dist/src/browser/shape-filter.d.ts +52 -0
  129. package/dist/src/browser/shape-filter.js +101 -0
  130. package/dist/src/browser/shape-filter.test.d.ts +1 -0
  131. package/dist/src/browser/shape-filter.test.js +101 -0
  132. package/dist/src/browser/shape.d.ts +23 -0
  133. package/dist/src/browser/shape.js +95 -0
  134. package/dist/src/browser/shape.test.d.ts +1 -0
  135. package/dist/src/browser/shape.test.js +82 -0
  136. package/dist/src/browser/stealth.d.ts +11 -0
  137. package/dist/src/browser/stealth.js +359 -0
  138. package/dist/src/browser/stealth.test.d.ts +1 -0
  139. package/dist/src/browser/stealth.test.js +134 -0
  140. package/dist/src/browser/tabs.d.ts +13 -0
  141. package/dist/src/browser/tabs.js +70 -0
  142. package/dist/src/browser/target-errors.d.ts +39 -0
  143. package/dist/src/browser/target-errors.js +45 -0
  144. package/dist/src/browser/target-errors.test.d.ts +1 -0
  145. package/dist/src/browser/target-errors.test.js +94 -0
  146. package/dist/src/browser/target-resolver.d.ts +158 -0
  147. package/dist/src/browser/target-resolver.js +570 -0
  148. package/dist/src/browser/target-resolver.test.d.ts +1 -0
  149. package/dist/src/browser/target-resolver.test.js +118 -0
  150. package/dist/src/browser/utils.d.ts +20 -0
  151. package/dist/src/browser/utils.js +64 -0
  152. package/dist/src/browser/utils.test.d.ts +1 -0
  153. package/dist/src/browser/utils.test.js +29 -0
  154. package/dist/src/browser/verify-fixture.d.ts +65 -0
  155. package/dist/src/browser/verify-fixture.js +318 -0
  156. package/dist/src/browser/verify-fixture.test.d.ts +1 -0
  157. package/dist/src/browser/verify-fixture.test.js +219 -0
  158. package/dist/src/browser/visual-refs.d.ts +11 -0
  159. package/dist/src/browser/visual-refs.js +108 -0
  160. package/dist/src/browser.test.d.ts +1 -0
  161. package/dist/src/browser.test.js +297 -0
  162. package/dist/src/build-manifest.d.ts +103 -0
  163. package/dist/src/build-manifest.js +363 -0
  164. package/dist/src/build-manifest.test.d.ts +1 -0
  165. package/dist/src/build-manifest.test.js +341 -0
  166. package/dist/src/capabilityRouting.d.ts +19 -0
  167. package/dist/src/capabilityRouting.js +56 -0
  168. package/dist/src/capabilityRouting.test.d.ts +1 -0
  169. package/dist/src/capabilityRouting.test.js +63 -0
  170. package/dist/src/cli-argv-preprocess.d.ts +63 -0
  171. package/dist/src/cli-argv-preprocess.js +303 -0
  172. package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
  173. package/dist/src/cli-argv-preprocess.test.js +304 -0
  174. package/dist/src/cli.d.ts +79 -0
  175. package/dist/src/cli.js +3350 -0
  176. package/dist/src/cli.test.d.ts +1 -0
  177. package/dist/src/cli.test.js +3041 -0
  178. package/dist/src/commanderAdapter.d.ts +21 -0
  179. package/dist/src/commanderAdapter.js +208 -0
  180. package/dist/src/commanderAdapter.test.d.ts +1 -0
  181. package/dist/src/commanderAdapter.test.js +352 -0
  182. package/dist/src/commands/auth.d.ts +40 -0
  183. package/dist/src/commands/auth.js +434 -0
  184. package/dist/src/commands/auth.test.d.ts +1 -0
  185. package/dist/src/commands/auth.test.js +252 -0
  186. package/dist/src/commands/daemon.d.ts +9 -0
  187. package/dist/src/commands/daemon.js +91 -0
  188. package/dist/src/commands/daemon.test.d.ts +1 -0
  189. package/dist/src/commands/daemon.test.js +293 -0
  190. package/dist/src/completion-fast.d.ts +22 -0
  191. package/dist/src/completion-fast.js +88 -0
  192. package/dist/src/completion-shared.d.ts +13 -0
  193. package/dist/src/completion-shared.js +61 -0
  194. package/dist/src/completion.d.ts +21 -0
  195. package/dist/src/completion.js +68 -0
  196. package/dist/src/completion.test.d.ts +1 -0
  197. package/dist/src/completion.test.js +24 -0
  198. package/dist/src/constants.d.ts +18 -0
  199. package/dist/src/constants.js +41 -0
  200. package/dist/src/convention-audit.d.ts +50 -0
  201. package/dist/src/convention-audit.js +553 -0
  202. package/dist/src/convention-audit.test.d.ts +1 -0
  203. package/dist/src/convention-audit.test.js +255 -0
  204. package/dist/src/daemon/server.d.ts +13 -0
  205. package/dist/src/daemon/server.js +185 -0
  206. package/dist/src/daemon/server.test.d.ts +1 -0
  207. package/dist/src/daemon/server.test.js +132 -0
  208. package/dist/src/daemon-utils.d.ts +18 -0
  209. package/dist/src/daemon-utils.js +37 -0
  210. package/dist/src/daemon.d.ts +1 -0
  211. package/dist/src/daemon.js +27 -0
  212. package/dist/src/daemon.test.d.ts +1 -0
  213. package/dist/src/daemon.test.js +60 -0
  214. package/dist/src/discovery.d.ts +46 -0
  215. package/dist/src/discovery.js +341 -0
  216. package/dist/src/doctor.d.ts +39 -0
  217. package/dist/src/doctor.js +168 -0
  218. package/dist/src/doctor.test.d.ts +1 -0
  219. package/dist/src/doctor.test.js +302 -0
  220. package/dist/src/download/article-download.d.ts +72 -0
  221. package/dist/src/download/article-download.js +305 -0
  222. package/dist/src/download/article-download.test.d.ts +1 -0
  223. package/dist/src/download/article-download.test.js +235 -0
  224. package/dist/src/download/index.d.ts +71 -0
  225. package/dist/src/download/index.js +363 -0
  226. package/dist/src/download/index.test.d.ts +1 -0
  227. package/dist/src/download/index.test.js +174 -0
  228. package/dist/src/download/media-download.d.ts +49 -0
  229. package/dist/src/download/media-download.js +127 -0
  230. package/dist/src/download/media-download.test.d.ts +1 -0
  231. package/dist/src/download/media-download.test.js +112 -0
  232. package/dist/src/download/progress.d.ts +36 -0
  233. package/dist/src/download/progress.js +120 -0
  234. package/dist/src/download/progress.test.d.ts +1 -0
  235. package/dist/src/download/progress.test.js +36 -0
  236. package/dist/src/electron-apps.d.ts +31 -0
  237. package/dist/src/electron-apps.js +91 -0
  238. package/dist/src/electron-apps.test.d.ts +1 -0
  239. package/dist/src/electron-apps.test.js +76 -0
  240. package/dist/src/engine.test.d.ts +1 -0
  241. package/dist/src/engine.test.js +312 -0
  242. package/dist/src/errors.d.ts +114 -0
  243. package/dist/src/errors.js +174 -0
  244. package/dist/src/errors.test.d.ts +1 -0
  245. package/dist/src/errors.test.js +109 -0
  246. package/dist/src/execution.d.ts +24 -0
  247. package/dist/src/execution.js +509 -0
  248. package/dist/src/execution.test.d.ts +1 -0
  249. package/dist/src/execution.test.js +645 -0
  250. package/dist/src/external.d.ts +49 -0
  251. package/dist/src/external.js +211 -0
  252. package/dist/src/external.test.d.ts +1 -0
  253. package/dist/src/external.test.js +110 -0
  254. package/dist/src/help.d.ts +88 -0
  255. package/dist/src/help.js +556 -0
  256. package/dist/src/help.test.d.ts +1 -0
  257. package/dist/src/help.test.js +58 -0
  258. package/dist/src/hooks.d.ts +46 -0
  259. package/dist/src/hooks.js +58 -0
  260. package/dist/src/hooks.test.d.ts +4 -0
  261. package/dist/src/hooks.test.js +92 -0
  262. package/dist/src/interceptor.d.ts +44 -0
  263. package/dist/src/interceptor.js +183 -0
  264. package/dist/src/interceptor.test.d.ts +4 -0
  265. package/dist/src/interceptor.test.js +81 -0
  266. package/dist/src/launcher.d.ts +41 -0
  267. package/dist/src/launcher.js +226 -0
  268. package/dist/src/launcher.test.d.ts +1 -0
  269. package/dist/src/launcher.test.js +153 -0
  270. package/dist/src/logger.d.ts +26 -0
  271. package/dist/src/logger.js +49 -0
  272. package/dist/src/main.d.ts +5 -0
  273. package/dist/src/main.js +177 -0
  274. package/dist/src/manifest-types.d.ts +43 -0
  275. package/dist/src/manifest-types.js +9 -0
  276. package/dist/src/node-network.d.ts +10 -0
  277. package/dist/src/node-network.js +174 -0
  278. package/dist/src/node-network.test.d.ts +1 -0
  279. package/dist/src/node-network.test.js +55 -0
  280. package/dist/src/observation/artifact.d.ts +16 -0
  281. package/dist/src/observation/artifact.js +260 -0
  282. package/dist/src/observation/artifact.test.d.ts +1 -0
  283. package/dist/src/observation/artifact.test.js +121 -0
  284. package/dist/src/observation/events.d.ts +89 -0
  285. package/dist/src/observation/events.js +1 -0
  286. package/dist/src/observation/index.d.ts +7 -0
  287. package/dist/src/observation/index.js +7 -0
  288. package/dist/src/observation/manager.d.ts +9 -0
  289. package/dist/src/observation/manager.js +27 -0
  290. package/dist/src/observation/manager.test.d.ts +1 -0
  291. package/dist/src/observation/manager.test.js +13 -0
  292. package/dist/src/observation/redaction.d.ts +11 -0
  293. package/dist/src/observation/redaction.js +81 -0
  294. package/dist/src/observation/redaction.test.d.ts +1 -0
  295. package/dist/src/observation/redaction.test.js +32 -0
  296. package/dist/src/observation/retention.d.ts +32 -0
  297. package/dist/src/observation/retention.js +160 -0
  298. package/dist/src/observation/retention.test.d.ts +1 -0
  299. package/dist/src/observation/retention.test.js +118 -0
  300. package/dist/src/observation/ring-buffer.d.ts +22 -0
  301. package/dist/src/observation/ring-buffer.js +45 -0
  302. package/dist/src/observation/ring-buffer.test.d.ts +1 -0
  303. package/dist/src/observation/ring-buffer.test.js +22 -0
  304. package/dist/src/observation/session.d.ts +25 -0
  305. package/dist/src/observation/session.js +50 -0
  306. package/dist/src/output.d.ts +14 -0
  307. package/dist/src/output.js +152 -0
  308. package/dist/src/output.test.d.ts +1 -0
  309. package/dist/src/output.test.js +53 -0
  310. package/dist/src/package-exports.test.d.ts +1 -0
  311. package/dist/src/package-exports.test.js +112 -0
  312. package/dist/src/package-paths.d.ts +8 -0
  313. package/dist/src/package-paths.js +41 -0
  314. package/dist/src/pipeline/executor.d.ts +11 -0
  315. package/dist/src/pipeline/executor.js +93 -0
  316. package/dist/src/pipeline/executor.test.d.ts +4 -0
  317. package/dist/src/pipeline/executor.test.js +182 -0
  318. package/dist/src/pipeline/index.d.ts +5 -0
  319. package/dist/src/pipeline/index.js +5 -0
  320. package/dist/src/pipeline/registry.d.ts +28 -0
  321. package/dist/src/pipeline/registry.js +55 -0
  322. package/dist/src/pipeline/steps/browser.d.ts +13 -0
  323. package/dist/src/pipeline/steps/browser.js +86 -0
  324. package/dist/src/pipeline/steps/download.d.ts +18 -0
  325. package/dist/src/pipeline/steps/download.js +252 -0
  326. package/dist/src/pipeline/steps/download.test.d.ts +1 -0
  327. package/dist/src/pipeline/steps/download.test.js +102 -0
  328. package/dist/src/pipeline/steps/fetch.d.ts +5 -0
  329. package/dist/src/pipeline/steps/fetch.js +122 -0
  330. package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
  331. package/dist/src/pipeline/steps/fetch.test.js +117 -0
  332. package/dist/src/pipeline/steps/intercept.d.ts +5 -0
  333. package/dist/src/pipeline/steps/intercept.js +50 -0
  334. package/dist/src/pipeline/steps/tap.d.ts +12 -0
  335. package/dist/src/pipeline/steps/tap.js +93 -0
  336. package/dist/src/pipeline/steps/transform.d.ts +9 -0
  337. package/dist/src/pipeline/steps/transform.js +70 -0
  338. package/dist/src/pipeline/template.d.ts +17 -0
  339. package/dist/src/pipeline/template.js +344 -0
  340. package/dist/src/pipeline/template.test.d.ts +4 -0
  341. package/dist/src/pipeline/template.test.js +197 -0
  342. package/dist/src/pipeline/transform.test.d.ts +4 -0
  343. package/dist/src/pipeline/transform.test.js +133 -0
  344. package/dist/src/plugin-manifest.d.ts +70 -0
  345. package/dist/src/plugin-manifest.js +160 -0
  346. package/dist/src/plugin-manifest.test.d.ts +4 -0
  347. package/dist/src/plugin-manifest.test.js +179 -0
  348. package/dist/src/plugin-scaffold.d.ts +28 -0
  349. package/dist/src/plugin-scaffold.js +143 -0
  350. package/dist/src/plugin-scaffold.test.d.ts +4 -0
  351. package/dist/src/plugin-scaffold.test.js +83 -0
  352. package/dist/src/plugin.d.ts +146 -0
  353. package/dist/src/plugin.js +1303 -0
  354. package/dist/src/plugin.test.d.ts +4 -0
  355. package/dist/src/plugin.test.js +1363 -0
  356. package/dist/src/registry-api.d.ts +13 -0
  357. package/dist/src/registry-api.js +10 -0
  358. package/dist/src/registry.d.ts +123 -0
  359. package/dist/src/registry.js +125 -0
  360. package/dist/src/registry.test.d.ts +4 -0
  361. package/dist/src/registry.test.js +209 -0
  362. package/dist/src/runtime-copy.test.d.ts +1 -0
  363. package/dist/src/runtime-copy.test.js +29 -0
  364. package/dist/src/runtime-detect.d.ts +31 -0
  365. package/dist/src/runtime-detect.js +47 -0
  366. package/dist/src/runtime-detect.test.d.ts +1 -0
  367. package/dist/src/runtime-detect.test.js +39 -0
  368. package/dist/src/runtime-identity.test.d.ts +1 -0
  369. package/dist/src/runtime-identity.test.js +18 -0
  370. package/dist/src/runtime.d.ts +47 -0
  371. package/dist/src/runtime.js +54 -0
  372. package/dist/src/scripts/framework.d.ts +7 -0
  373. package/dist/src/scripts/framework.js +25 -0
  374. package/dist/src/scripts/interact.d.ts +4 -0
  375. package/dist/src/scripts/interact.js +20 -0
  376. package/dist/src/scripts/store.d.ts +13 -0
  377. package/dist/src/scripts/store.js +48 -0
  378. package/dist/src/serialization.d.ts +42 -0
  379. package/dist/src/serialization.js +103 -0
  380. package/dist/src/serialization.test.d.ts +1 -0
  381. package/dist/src/serialization.test.js +78 -0
  382. package/dist/src/skills.d.ts +14 -0
  383. package/dist/src/skills.js +127 -0
  384. package/dist/src/skills.test.d.ts +1 -0
  385. package/dist/src/skills.test.js +71 -0
  386. package/dist/src/snapshotFormatter.d.ts +11 -0
  387. package/dist/src/snapshotFormatter.js +338 -0
  388. package/dist/src/snapshotFormatter.test.d.ts +7 -0
  389. package/dist/src/snapshotFormatter.test.js +521 -0
  390. package/dist/src/tui.d.ts +28 -0
  391. package/dist/src/tui.js +179 -0
  392. package/dist/src/tui.test.d.ts +1 -0
  393. package/dist/src/tui.test.js +19 -0
  394. package/dist/src/types.d.ts +236 -0
  395. package/dist/src/types.js +7 -0
  396. package/dist/src/update-check.d.ts +66 -0
  397. package/dist/src/update-check.js +206 -0
  398. package/dist/src/update-check.test.d.ts +1 -0
  399. package/dist/src/update-check.test.js +117 -0
  400. package/dist/src/utils.d.ts +57 -0
  401. package/dist/src/utils.js +152 -0
  402. package/dist/src/utils.test.d.ts +1 -0
  403. package/dist/src/utils.test.js +155 -0
  404. package/dist/src/validate.d.ts +21 -0
  405. package/dist/src/validate.js +130 -0
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/dist/src/verify.d.ts +26 -0
  409. package/dist/src/verify.js +67 -0
  410. package/dist/src/version.d.ts +4 -0
  411. package/dist/src/version.js +22 -0
  412. package/dist/src/weixin-download.test.d.ts +1 -0
  413. package/dist/src/weixin-download.test.js +45 -0
  414. package/package.json +4 -3
  415. package/scripts/check-package-bin.mjs +95 -0
@@ -0,0 +1,1060 @@
1
+ /**
2
+ * BasePage — shared IPage method implementations for DOM helpers.
3
+ *
4
+ * Both Page (daemon-backed) and CDPPage (direct CDP) execute JS the same way
5
+ * for DOM operations. This base class deduplicates ~200 lines of identical
6
+ * click/type/scroll/wait/snapshot/interceptor methods.
7
+ *
8
+ * Subclasses implement the transport-specific methods: goto, evaluate,
9
+ * getCookies, screenshot, tabs, etc.
10
+ */
11
+ import { generateSnapshotJs, getFormStateJs } from './dom-snapshot.js';
12
+ import { buildAxSnapshotFromTrees, findAxRefReplacement } from './ax-snapshot.js';
13
+ import { pressKeyJs, waitForTextJs, waitForCaptureJs, waitForSelectorJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
14
+ import { resolveTargetJs, boundingRectResolvedJs, clickResolvedJs, typeResolvedJs, prepareNativeTypeResolvedJs, verifyFilledResolvedJs, scrollResolvedJs, } from './target-resolver.js';
15
+ import { TargetError } from './target-errors.js';
16
+ import { CliError } from '../errors.js';
17
+ import { formatSnapshot } from '../snapshotFormatter.js';
18
+ import { installVisualRefOverlayJs, removeVisualRefOverlayJs } from './visual-refs.js';
19
+ /**
20
+ * Execute `resolveTargetJs` once, throw structured `TargetError` on failure.
21
+ * Single helper so click/typeText/scrollTo share one resolution pathway,
22
+ * which is what the selector-first contract promises agents.
23
+ */
24
+ async function runResolve(page, ref, opts = {}) {
25
+ const resolution = (await page.evaluate(resolveTargetJs(ref, opts)));
26
+ if (!resolution.ok) {
27
+ throw new TargetError({
28
+ code: resolution.code,
29
+ message: resolution.message,
30
+ hint: resolution.hint,
31
+ candidates: resolution.candidates,
32
+ matches_n: resolution.matches_n,
33
+ });
34
+ }
35
+ return { matches_n: resolution.matches_n, match_level: resolution.match_level };
36
+ }
37
+ function previewText(text) {
38
+ const preview = (text ?? '').replace(/\s+/g, ' ').trim().slice(0, 300);
39
+ return preview ? `Response preview: ${preview}` : undefined;
40
+ }
41
+ function parseKeyChord(rawKey) {
42
+ const parts = rawKey.split('+').map(part => part.trim()).filter(Boolean);
43
+ if (parts.length <= 1)
44
+ return { key: rawKey, modifiers: [] };
45
+ const modifiers = [];
46
+ for (const token of parts.slice(0, -1)) {
47
+ const normalized = token.toLowerCase();
48
+ if (normalized === 'ctrl' || normalized === 'control')
49
+ modifiers.push('Ctrl');
50
+ else if (normalized === 'cmd' || normalized === 'command' || normalized === 'meta')
51
+ modifiers.push('Meta');
52
+ else if (normalized === 'option' || normalized === 'alt')
53
+ modifiers.push('Alt');
54
+ else if (normalized === 'shift')
55
+ modifiers.push('Shift');
56
+ else
57
+ return { key: rawKey, modifiers: [] };
58
+ }
59
+ const key = parts.at(-1);
60
+ return key ? { key, modifiers } : { key: rawKey, modifiers: [] };
61
+ }
62
+ export class BasePage {
63
+ _lastUrl = null;
64
+ /** Cached previous snapshot hashes for incremental diff marking */
65
+ _prevSnapshotHashes = null;
66
+ _cdpTargetMarkerSeq = 0;
67
+ _axRefs = new Map();
68
+ /**
69
+ * Safely evaluate JS with pre-serialized arguments.
70
+ * Each key in `args` becomes a `const` declaration with JSON-serialized value,
71
+ * wrapped in a lexical block to avoid polluting the global execution context.
72
+ *
73
+ * Why a block: Chrome's Runtime.evaluate shares a single global context per page.
74
+ * Top-level `const` declarations persist across calls, so re-declaring the same
75
+ * variable name (e.g. `markerAttr` in both click resolution and file upload)
76
+ * throws "SyntaxError: Identifier has already been declared". A block keeps
77
+ * the args scoped without forcing callers to pass expression-only code.
78
+ *
79
+ * Usage:
80
+ * page.evaluateWithArgs(`(async () => { return sym; })()`, { sym: userInput })
81
+ */
82
+ async evaluateWithArgs(js, args) {
83
+ const declarations = Object.entries(args)
84
+ .map(([key, value]) => {
85
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
86
+ throw new Error(`evaluateWithArgs: invalid key "${key}"`);
87
+ }
88
+ return `const ${key} = ${JSON.stringify(value)};`;
89
+ })
90
+ .join('\n');
91
+ return this.evaluate(`{\n${declarations}\n${js}\n}`);
92
+ }
93
+ async fetchJson(url, opts = {}) {
94
+ const request = {
95
+ url,
96
+ method: opts.method ?? 'GET',
97
+ headers: opts.headers ?? {},
98
+ body: opts.body,
99
+ hasBody: opts.body !== undefined,
100
+ timeoutMs: opts.timeoutMs ?? 15_000,
101
+ };
102
+ const result = await this.evaluateWithArgs(`
103
+ (async () => {
104
+ const ctrl = new AbortController();
105
+ const timer = setTimeout(() => ctrl.abort(), request.timeoutMs);
106
+ try {
107
+ const headers = { Accept: 'application/json', ...request.headers };
108
+ const init = {
109
+ method: request.method,
110
+ credentials: 'include',
111
+ headers,
112
+ signal: ctrl.signal,
113
+ };
114
+ if (request.hasBody) {
115
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {
116
+ headers['Content-Type'] = 'application/json';
117
+ }
118
+ init.body = JSON.stringify(request.body);
119
+ }
120
+ const resp = await fetch(request.url, init);
121
+ const text = await resp.text();
122
+ return {
123
+ ok: resp.ok,
124
+ status: resp.status,
125
+ statusText: resp.statusText,
126
+ url: resp.url,
127
+ contentType: resp.headers.get('content-type') || '',
128
+ text,
129
+ };
130
+ } catch (error) {
131
+ return {
132
+ ok: false,
133
+ status: 0,
134
+ statusText: '',
135
+ url: request.url,
136
+ contentType: '',
137
+ text: '',
138
+ error: error instanceof Error ? error.message : String(error),
139
+ };
140
+ } finally {
141
+ clearTimeout(timer);
142
+ }
143
+ })()
144
+ `, { request });
145
+ const targetUrl = result.url || url;
146
+ if (result.error) {
147
+ throw new CliError('FETCH_ERROR', `Browser fetch failed for ${targetUrl}: ${result.error}`, 'Check that the page is reachable and the current browser profile has access.');
148
+ }
149
+ if (!result.ok) {
150
+ throw new CliError('FETCH_ERROR', `HTTP ${result.status ?? 0}${result.statusText ? ` ${result.statusText}` : ''} from ${targetUrl}`, previewText(result.text));
151
+ }
152
+ const text = result.text ?? '';
153
+ if (!text.trim())
154
+ return null;
155
+ try {
156
+ return JSON.parse(text);
157
+ }
158
+ catch {
159
+ const contentType = result.contentType ? ` (${result.contentType})` : '';
160
+ throw new CliError('FETCH_ERROR', `Expected JSON from ${targetUrl}${contentType}`, previewText(text));
161
+ }
162
+ }
163
+ async annotatedScreenshot(options = {}) {
164
+ // Refresh DOM refs first so visual labels map to immediate `browser click <ref>` targets.
165
+ await this.snapshot({ source: 'dom', viewportExpand: 0 });
166
+ try {
167
+ await this.evaluate(installVisualRefOverlayJs());
168
+ return await this.screenshot({ ...options, annotate: false });
169
+ }
170
+ finally {
171
+ await this.evaluate(removeVisualRefOverlayJs()).catch(() => { });
172
+ }
173
+ }
174
+ // ── Shared DOM helper implementations ──
175
+ async click(ref, opts = {}) {
176
+ const axClick = await this.tryClickAxRef(ref);
177
+ if (axClick)
178
+ return axClick;
179
+ // Phase 1: Resolve target with fingerprint verification
180
+ const resolved = await runResolve(this, ref, opts);
181
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
182
+ // Phase 2: measure first so native click can run before DOM el.click().
183
+ // Custom dropdowns often listen to pointer/mouse down/up; DOM el.click()
184
+ // only fires click and can silently report success without opening/selecting.
185
+ const rect = await this.evaluate(boundingRectResolvedJs({ skipScroll: nativeScrolled }));
186
+ if (rect?.visible === true) {
187
+ const success = await this.tryNativeClick(rect.x, rect.y);
188
+ if (success)
189
+ return resolved;
190
+ }
191
+ // JS fallback for older backends or zero-rect targets.
192
+ const result = await this.evaluate(clickResolvedJs({ skipScroll: nativeScrolled }));
193
+ if (typeof result === 'string' || result == null)
194
+ return resolved;
195
+ if (result.status === 'clicked')
196
+ return resolved;
197
+ // JS click failed — try CDP native click if coordinates available
198
+ if (result.x != null && result.y != null) {
199
+ const success = await this.tryNativeClick(result.x, result.y);
200
+ if (success)
201
+ return resolved;
202
+ }
203
+ throw new Error(`Click failed: ${result.error ?? 'JS click and CDP fallback both failed'}`);
204
+ }
205
+ /** Uses native CDP click support when the concrete page exposes it. */
206
+ async tryNativeClick(x, y) {
207
+ const nativeClick = this.nativeClick;
208
+ if (typeof nativeClick !== 'function')
209
+ return false;
210
+ try {
211
+ await nativeClick.call(this, x, y);
212
+ return true;
213
+ }
214
+ catch {
215
+ return false;
216
+ }
217
+ }
218
+ async tryNativeMouseMove(x, y) {
219
+ const cdp = this.cdp;
220
+ if (typeof cdp !== 'function')
221
+ return false;
222
+ try {
223
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseMoved', x, y });
224
+ return true;
225
+ }
226
+ catch {
227
+ return false;
228
+ }
229
+ }
230
+ async tryNativeDoubleClick(x, y) {
231
+ const cdp = this.cdp;
232
+ if (typeof cdp !== 'function')
233
+ return false;
234
+ try {
235
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseMoved', x, y });
236
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 });
237
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 });
238
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 2 });
239
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 2 });
240
+ return true;
241
+ }
242
+ catch {
243
+ return false;
244
+ }
245
+ }
246
+ async tryNativeDrag(from, to) {
247
+ const cdp = this.cdp;
248
+ if (typeof cdp !== 'function')
249
+ return false;
250
+ const midX = Math.round((from.x + to.x) / 2);
251
+ const midY = Math.round((from.y + to.y) / 2);
252
+ try {
253
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseMoved', x: from.x, y: from.y });
254
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mousePressed', x: from.x, y: from.y, button: 'left', clickCount: 1 });
255
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseMoved', x: midX, y: midY, button: 'left', buttons: 1 });
256
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseMoved', x: to.x, y: to.y, button: 'left', buttons: 1 });
257
+ await cdp.call(this, 'Input.dispatchMouseEvent', { type: 'mouseReleased', x: to.x, y: to.y, button: 'left', clickCount: 1 });
258
+ return true;
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ }
264
+ async tryClickAxRef(ref) {
265
+ if (!/^\d+$/.test(ref))
266
+ return null;
267
+ const entry = this._axRefs.get(ref);
268
+ if (!entry)
269
+ return null;
270
+ const nativeClick = this.nativeClick;
271
+ if (typeof nativeClick !== 'function')
272
+ return null;
273
+ const resolved = await this.resolveAxRefPoint(entry);
274
+ if (!resolved)
275
+ return null;
276
+ try {
277
+ await nativeClick.call(this, resolved.x, resolved.y);
278
+ return { matches_n: 1, match_level: resolved.matchLevel };
279
+ }
280
+ catch {
281
+ return null;
282
+ }
283
+ }
284
+ async resolveAxRefPoint(entry) {
285
+ const cdp = this.cdp;
286
+ if (typeof cdp !== 'function')
287
+ return null;
288
+ if (entry.backendNodeId != null) {
289
+ const point = await this.axBoxCenter(entry.backendNodeId, entry.frame).catch(() => null);
290
+ if (point)
291
+ return { ...point, matchLevel: 'exact' };
292
+ }
293
+ await cdp.call(this, 'Accessibility.enable', axEnableParams(entry.frame));
294
+ const axTree = await cdp.call(this, 'Accessibility.getFullAXTree', axTreeParams(entry.frame)).catch(() => null);
295
+ const recovered = findAxRefReplacement(axTree, entry);
296
+ if (!recovered?.backendNodeId)
297
+ return null;
298
+ this._axRefs.set(entry.ref, recovered);
299
+ const point = await this.axBoxCenter(recovered.backendNodeId, recovered.frame).catch(() => null);
300
+ return point ? { ...point, matchLevel: 'reidentified' } : null;
301
+ }
302
+ async axBoxCenter(backendNodeId, frame) {
303
+ const cdp = this.cdp;
304
+ if (typeof cdp !== 'function')
305
+ return null;
306
+ const result = await cdp.call(this, 'DOM.getBoxModel', {
307
+ backendNodeId,
308
+ ...(frame?.sessionId
309
+ ? { frameId: frame.frameId, sessionId: frame.sessionId, ...(frame.targetUrl ? { targetUrl: frame.targetUrl } : {}) }
310
+ : {}),
311
+ });
312
+ const quad = Array.isArray(result?.model?.content) && result.model.content.length >= 8
313
+ ? result.model.content
314
+ : Array.isArray(result?.model?.border) && result.model.border.length >= 8
315
+ ? result.model.border
316
+ : null;
317
+ if (!quad)
318
+ return null;
319
+ const nums = quad.slice(0, 8).map((value) => typeof value === 'number' ? value : Number(value));
320
+ if (nums.some((value) => !Number.isFinite(value)))
321
+ return null;
322
+ return {
323
+ x: Math.round((nums[0] + nums[2] + nums[4] + nums[6]) / 4),
324
+ y: Math.round((nums[1] + nums[3] + nums[5] + nums[7]) / 4),
325
+ };
326
+ }
327
+ /** Uses native CDP text insertion when the concrete page exposes it. */
328
+ async tryNativeType(text) {
329
+ const nativeType = this.nativeType;
330
+ if (typeof nativeType === 'function') {
331
+ try {
332
+ await nativeType.call(this, text);
333
+ return true;
334
+ }
335
+ catch {
336
+ // Fall through to the older dedicated insertText primitive if present.
337
+ }
338
+ }
339
+ const insertText = this.insertText;
340
+ if (typeof insertText !== 'function')
341
+ return false;
342
+ try {
343
+ await insertText.call(this, text);
344
+ return true;
345
+ }
346
+ catch {
347
+ return false;
348
+ }
349
+ }
350
+ /** Uses native CDP key events when the concrete page exposes them. */
351
+ async tryNativeKeyPress(key, modifiers) {
352
+ const nativeKeyPress = this.nativeKeyPress;
353
+ if (typeof nativeKeyPress !== 'function')
354
+ return false;
355
+ try {
356
+ await nativeKeyPress.call(this, key, modifiers);
357
+ return true;
358
+ }
359
+ catch {
360
+ return false;
361
+ }
362
+ }
363
+ async isResolvedFocused() {
364
+ try {
365
+ return await this.evaluate(`
366
+ (() => {
367
+ const el = window.__resolved;
368
+ return !!el && (document.activeElement === el || (typeof el.matches === 'function' && el.matches(':focus')));
369
+ })()
370
+ `);
371
+ }
372
+ catch {
373
+ return false;
374
+ }
375
+ }
376
+ /**
377
+ * Run a DOM-domain CDP command against `window.__resolved`.
378
+ *
379
+ * CDP DOM.focus / DOM.scrollIntoViewIfNeeded need a nodeId, while our
380
+ * resolver stores the live Element in page JS. Bridge the two worlds with a
381
+ * short-lived marker attribute, then query it through CDP.
382
+ */
383
+ async tryCdpOnResolvedElement(method) {
384
+ const cdp = this.cdp;
385
+ if (typeof cdp !== 'function')
386
+ return false;
387
+ const markerAttr = 'data-webcmd-cdp-target';
388
+ const markerValue = `${Date.now().toString(36)}-${++this._cdpTargetMarkerSeq}`;
389
+ const selector = `[${markerAttr}="${markerValue}"]`;
390
+ let marked = false;
391
+ try {
392
+ const marker = await this.evaluateWithArgs(`
393
+ (() => {
394
+ const el = window.__resolved;
395
+ if (!el || el.nodeType !== 1 || typeof el.setAttribute !== 'function') {
396
+ return { ok: false };
397
+ }
398
+ el.setAttribute(markerAttr, markerValue);
399
+ return { ok: true };
400
+ })()
401
+ `, { markerAttr, markerValue });
402
+ marked = marker?.ok === true;
403
+ if (!marked)
404
+ return false;
405
+ await cdp.call(this, 'DOM.enable', {}).catch(() => undefined);
406
+ const doc = await cdp.call(this, 'DOM.getDocument', {});
407
+ const rootNodeId = doc?.root?.nodeId;
408
+ if (typeof rootNodeId !== 'number')
409
+ return false;
410
+ const query = await cdp.call(this, 'DOM.querySelector', {
411
+ nodeId: rootNodeId,
412
+ selector,
413
+ });
414
+ const nodeId = query?.nodeId;
415
+ if (typeof nodeId !== 'number' || nodeId <= 0)
416
+ return false;
417
+ await cdp.call(this, method, { nodeId });
418
+ return true;
419
+ }
420
+ catch {
421
+ return false;
422
+ }
423
+ finally {
424
+ if (marked) {
425
+ await this.evaluateWithArgs(`
426
+ (() => {
427
+ for (const el of document.querySelectorAll(selector)) {
428
+ el.removeAttribute(markerAttr);
429
+ }
430
+ })()
431
+ `, { selector, markerAttr }).catch(() => undefined);
432
+ }
433
+ }
434
+ }
435
+ async typeText(ref, text, opts = {}) {
436
+ const resolved = await runResolve(this, ref, opts);
437
+ let typed = false;
438
+ let nativeScrolled = false;
439
+ let nativeFocused = false;
440
+ if (typeof this.nativeType === 'function' || typeof this.insertText === 'function') {
441
+ try {
442
+ nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
443
+ nativeFocused = await this.tryCdpOnResolvedElement('DOM.focus');
444
+ const preparation = await this.evaluate(prepareNativeTypeResolvedJs({
445
+ skipScroll: nativeScrolled,
446
+ skipFocus: nativeFocused,
447
+ }));
448
+ typed = preparation?.ok === true && await this.tryNativeType(text);
449
+ }
450
+ catch {
451
+ // Native input is a reliability upgrade, not the only path. Preserve
452
+ // the existing DOM setter fallback if preparation fails.
453
+ }
454
+ }
455
+ if (!typed) {
456
+ await this.evaluate(typeResolvedJs(text));
457
+ }
458
+ return resolved;
459
+ }
460
+ async hover(ref, opts = {}) {
461
+ const resolved = await runResolve(this, ref, opts);
462
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
463
+ const rect = await this.evaluate(boundingRectResolvedJs({ skipScroll: nativeScrolled }));
464
+ if (rect?.visible === true && await this.tryNativeMouseMove(rect.x, rect.y))
465
+ return resolved;
466
+ await this.evaluate(`
467
+ (() => {
468
+ const el = window.__resolved;
469
+ if (!el) throw new Error('No resolved element');
470
+ if (${nativeScrolled ? 'false' : 'true'}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
471
+ const rect = el.getBoundingClientRect();
472
+ const init = {
473
+ bubbles: true,
474
+ cancelable: true,
475
+ view: window,
476
+ clientX: Math.round(rect.left + rect.width / 2),
477
+ clientY: Math.round(rect.top + rect.height / 2),
478
+ };
479
+ try { el.dispatchEvent(new PointerEvent('pointerover', init)); } catch (_) {}
480
+ try { el.dispatchEvent(new PointerEvent('pointermove', init)); } catch (_) {}
481
+ el.dispatchEvent(new MouseEvent('mouseover', init));
482
+ el.dispatchEvent(new MouseEvent('mousemove', init));
483
+ })()
484
+ `);
485
+ return resolved;
486
+ }
487
+ async focus(ref, opts = {}) {
488
+ const resolved = await runResolve(this, ref, opts);
489
+ let focused = await this.tryCdpOnResolvedElement('DOM.focus') && await this.isResolvedFocused();
490
+ if (!focused) {
491
+ focused = await this.evaluate(`
492
+ (() => {
493
+ const el = window.__resolved;
494
+ if (!el || typeof el.focus !== 'function') return false;
495
+ try { el.focus({ preventScroll: true }); } catch (_) { try { el.focus(); } catch (_) {} }
496
+ return document.activeElement === el || (typeof el.matches === 'function' && el.matches(':focus'));
497
+ })()
498
+ `);
499
+ }
500
+ return { ...resolved, focused: !!focused };
501
+ }
502
+ async dblClick(ref, opts = {}) {
503
+ const resolved = await runResolve(this, ref, opts);
504
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
505
+ const rect = await this.evaluate(boundingRectResolvedJs({ skipScroll: nativeScrolled }));
506
+ if (rect?.visible === true && await this.tryNativeDoubleClick(rect.x, rect.y))
507
+ return resolved;
508
+ await this.evaluate(`
509
+ (() => {
510
+ const el = window.__resolved;
511
+ if (!el) throw new Error('No resolved element');
512
+ if (${nativeScrolled ? 'false' : 'true'}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
513
+ const rect = el.getBoundingClientRect();
514
+ const init = {
515
+ bubbles: true,
516
+ cancelable: true,
517
+ view: window,
518
+ clientX: Math.round(rect.left + rect.width / 2),
519
+ clientY: Math.round(rect.top + rect.height / 2),
520
+ button: 0,
521
+ detail: 2,
522
+ };
523
+ el.dispatchEvent(new MouseEvent('dblclick', init));
524
+ })()
525
+ `);
526
+ return resolved;
527
+ }
528
+ async readCheckableState() {
529
+ return await this.evaluate(`
530
+ (() => {
531
+ const el = window.__resolved;
532
+ if (!el || el.nodeType !== 1) return { ok: false, reason: 'not_checkable' };
533
+ const tag = el.tagName.toLowerCase();
534
+ const role = (el.getAttribute('role') || '').toLowerCase();
535
+ const type = (el.getAttribute('type') || '').toLowerCase();
536
+ if (tag === 'input' && (type === 'checkbox' || type === 'radio')) {
537
+ return {
538
+ ok: true,
539
+ checked: !!el.checked,
540
+ disabled: !!el.disabled,
541
+ kind: type,
542
+ };
543
+ }
544
+ if (role === 'checkbox' || role === 'switch' || role === 'menuitemcheckbox' || role === 'radio' || role === 'menuitemradio') {
545
+ const aria = (el.getAttribute('aria-checked') || '').toLowerCase();
546
+ return {
547
+ ok: true,
548
+ checked: aria === 'true' || aria === 'mixed',
549
+ disabled: el.getAttribute('aria-disabled') === 'true' || el.hasAttribute('disabled'),
550
+ kind: role,
551
+ };
552
+ }
553
+ return { ok: false, reason: 'not_checkable', tag, role };
554
+ })()
555
+ `);
556
+ }
557
+ async setChecked(ref, checked, opts = {}) {
558
+ const resolved = await runResolve(this, ref, opts);
559
+ const before = await this.readCheckableState();
560
+ if (before?.ok !== true) {
561
+ throw new TargetError({
562
+ code: 'not_checkable',
563
+ message: `Target "${ref}" is not a checkbox, radio, switch, or aria-checked control.`,
564
+ hint: 'Use `webcmd browser state` or `browser find` to pick an input[type=checkbox], input[type=radio], or role=checkbox/switch target.',
565
+ });
566
+ }
567
+ if (before.disabled) {
568
+ throw new TargetError({
569
+ code: 'not_checkable',
570
+ message: `Target "${ref}" is disabled and cannot be ${checked ? 'checked' : 'unchecked'}.`,
571
+ hint: 'Pick an enabled control, or inspect the form state before retrying.',
572
+ });
573
+ }
574
+ if ((before.kind === 'radio' || before.kind === 'menuitemradio') && !checked) {
575
+ throw new TargetError({
576
+ code: 'not_checkable',
577
+ message: `Target "${ref}" is a radio button and cannot be unchecked directly.`,
578
+ hint: 'Select another radio option in the same group instead.',
579
+ });
580
+ }
581
+ if (before.checked === checked) {
582
+ return {
583
+ ...resolved,
584
+ checked,
585
+ changed: false,
586
+ ...(before.kind ? { kind: before.kind } : {}),
587
+ };
588
+ }
589
+ const clicked = await this.click(ref, opts);
590
+ const after = await this.readCheckableState();
591
+ if (after?.ok !== true || after.checked !== checked) {
592
+ throw new TargetError({
593
+ code: 'not_checkable',
594
+ message: `Target "${ref}" did not become ${checked ? 'checked' : 'unchecked'} after click.`,
595
+ hint: 'The control may be custom, disabled by app logic, or require a different target such as its visible label.',
596
+ });
597
+ }
598
+ return {
599
+ matches_n: clicked.matches_n,
600
+ match_level: clicked.match_level,
601
+ checked,
602
+ changed: true,
603
+ ...(after.kind ? { kind: after.kind } : {}),
604
+ };
605
+ }
606
+ async setFileInputBySelector(files, selector) {
607
+ const setFileInput = this.setFileInput;
608
+ if (typeof setFileInput === 'function') {
609
+ await setFileInput.call(this, files, selector);
610
+ return;
611
+ }
612
+ const cdp = this.cdp;
613
+ if (typeof cdp !== 'function') {
614
+ throw new Error('File upload requires setFileInput or CDP support from the active browser backend.');
615
+ }
616
+ await cdp.call(this, 'DOM.enable', {}).catch(() => undefined);
617
+ const doc = await cdp.call(this, 'DOM.getDocument', {});
618
+ const rootNodeId = doc?.root?.nodeId;
619
+ if (typeof rootNodeId !== 'number')
620
+ throw new Error('DOM.getDocument returned no root node.');
621
+ const query = await cdp.call(this, 'DOM.querySelector', { nodeId: rootNodeId, selector });
622
+ const nodeId = query?.nodeId;
623
+ if (typeof nodeId !== 'number' || nodeId <= 0)
624
+ throw new Error(`No element found matching selector: ${selector}`);
625
+ await cdp.call(this, 'DOM.setFileInputFiles', { files, nodeId });
626
+ }
627
+ async uploadFiles(ref, files, opts = {}) {
628
+ if (!Array.isArray(files) || files.length === 0) {
629
+ throw new TargetError({
630
+ code: 'not_file_input',
631
+ message: 'No files were provided for upload.',
632
+ hint: 'Pass one or more local file paths after the target.',
633
+ });
634
+ }
635
+ const resolved = await runResolve(this, ref, opts);
636
+ const markerAttr = 'data-webcmd-upload-target';
637
+ const markerValue = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
638
+ const selector = `[${markerAttr}="${markerValue}"]`;
639
+ let marked = false;
640
+ let info = null;
641
+ try {
642
+ info = await this.evaluateWithArgs(`
643
+ (() => {
644
+ const el = window.__resolved;
645
+ if (!el || el.nodeType !== 1) return { ok: false, reason: 'not_file_input' };
646
+ const tag = el.tagName.toLowerCase();
647
+ const type = (el.getAttribute('type') || '').toLowerCase();
648
+ if (tag !== 'input' || type !== 'file') return { ok: false, reason: 'not_file_input', tag, type };
649
+ el.setAttribute(markerAttr, markerValue);
650
+ return {
651
+ ok: true,
652
+ multiple: !!el.multiple,
653
+ accept: el.getAttribute('accept') || '',
654
+ };
655
+ })()
656
+ `, { markerAttr, markerValue });
657
+ marked = info?.ok === true;
658
+ if (!marked) {
659
+ throw new TargetError({
660
+ code: 'not_file_input',
661
+ message: `Target "${ref}" is not an input[type=file].`,
662
+ hint: 'Use `webcmd browser find --css "input[type=file]"` or inspect `compound` output from browser state/find.',
663
+ });
664
+ }
665
+ if (files.length > 1 && !info?.multiple) {
666
+ throw new TargetError({
667
+ code: 'not_file_input',
668
+ message: `Target "${ref}" does not allow multiple files, but ${files.length} files were provided.`,
669
+ hint: 'Pass one file, or choose a file input with the multiple attribute.',
670
+ });
671
+ }
672
+ await this.setFileInputBySelector(files, selector);
673
+ const verification = await this.evaluate(`
674
+ (() => {
675
+ const el = window.__resolved;
676
+ const names = [];
677
+ try {
678
+ if (el && el.files) {
679
+ for (let i = 0; i < el.files.length; i++) names.push(el.files[i].name || '');
680
+ }
681
+ } catch (_) {}
682
+ return names;
683
+ })()
684
+ `);
685
+ const fileNames = Array.isArray(verification)
686
+ ? verification.map((value) => String(value))
687
+ : [];
688
+ return {
689
+ ...resolved,
690
+ uploaded: true,
691
+ files: fileNames.length || files.length,
692
+ file_names: fileNames,
693
+ target: ref,
694
+ multiple: !!info?.multiple,
695
+ ...(info?.accept ? { accept: info.accept } : {}),
696
+ };
697
+ }
698
+ finally {
699
+ if (marked) {
700
+ await this.evaluateWithArgs(`
701
+ (() => {
702
+ for (const el of document.querySelectorAll(selector)) {
703
+ el.removeAttribute(markerAttr);
704
+ }
705
+ })()
706
+ `, { selector, markerAttr }).catch(() => undefined);
707
+ }
708
+ }
709
+ }
710
+ async drag(source, target, opts = {}) {
711
+ const sourceResolved = await runResolve(this, source, opts.from ?? {});
712
+ const sourceScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
713
+ const sourceRect = await this.evaluate(`
714
+ (() => {
715
+ const el = window.__resolved;
716
+ if (!el) throw new Error('No resolved drag source');
717
+ window.__webcmd_drag_source = el;
718
+ if (${sourceScrolled ? 'false' : 'true'}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
719
+ const rect = el.getBoundingClientRect();
720
+ const w = Math.round(rect.width);
721
+ const h = Math.round(rect.height);
722
+ const x = Math.round(rect.left + rect.width / 2);
723
+ const y = Math.round(rect.top + rect.height / 2);
724
+ const visible = w > 0 && h > 0 && x >= 0 && y >= 0 && x <= window.innerWidth && y <= window.innerHeight;
725
+ return { x, y, w, h, visible };
726
+ })()
727
+ `);
728
+ if (sourceRect?.visible !== true) {
729
+ throw new Error(`Drag source "${source}" has no visible bounding box.`);
730
+ }
731
+ try {
732
+ const targetResolved = await runResolve(this, target, opts.to ?? {});
733
+ const targetScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
734
+ const endpoints = await this.evaluate(`
735
+ (() => {
736
+ const sourceEl = window.__webcmd_drag_source;
737
+ const targetEl = window.__resolved;
738
+ if (!sourceEl) throw new Error('No resolved drag source');
739
+ if (!targetEl) throw new Error('No resolved drag target');
740
+ if (${targetScrolled ? 'false' : 'true'}) targetEl.scrollIntoView({ behavior: 'instant', block: 'center' });
741
+ const measure = (el) => {
742
+ const rect = el.getBoundingClientRect();
743
+ const w = Math.round(rect.width);
744
+ const h = Math.round(rect.height);
745
+ const x = Math.round(rect.left + rect.width / 2);
746
+ const y = Math.round(rect.top + rect.height / 2);
747
+ const visible = w > 0 && h > 0 && x >= 0 && y >= 0 && x <= window.innerWidth && y <= window.innerHeight;
748
+ return { x, y, w, h, visible };
749
+ };
750
+ return { source: measure(sourceEl), target: measure(targetEl) };
751
+ })()
752
+ `);
753
+ if (endpoints?.source?.visible !== true) {
754
+ throw new Error(`Drag source "${source}" is not visible at drag time.`);
755
+ }
756
+ if (endpoints?.target?.visible !== true) {
757
+ throw new Error(`Drag target "${target}" has no visible bounding box.`);
758
+ }
759
+ const dragged = await this.tryNativeDrag({ x: endpoints.source.x, y: endpoints.source.y }, { x: endpoints.target.x, y: endpoints.target.y });
760
+ if (!dragged)
761
+ throw new Error('Native drag requires CDP Input.dispatchMouseEvent support.');
762
+ return {
763
+ dragged: true,
764
+ source,
765
+ target,
766
+ source_matches_n: sourceResolved.matches_n,
767
+ target_matches_n: targetResolved.matches_n,
768
+ source_match_level: sourceResolved.match_level,
769
+ target_match_level: targetResolved.match_level,
770
+ };
771
+ }
772
+ finally {
773
+ await this.evaluate('delete window.__webcmd_drag_source').catch(() => { });
774
+ }
775
+ }
776
+ async fillText(ref, text, opts = {}) {
777
+ const resolved = await runResolve(this, ref, opts);
778
+ let nativeScrolled = false;
779
+ let nativeFocused = false;
780
+ try {
781
+ nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
782
+ nativeFocused = await this.tryCdpOnResolvedElement('DOM.focus');
783
+ }
784
+ catch {
785
+ // CDP focus/scroll is best-effort; DOM preparation below remains authoritative.
786
+ }
787
+ const preparation = await this.evaluate(prepareNativeTypeResolvedJs({
788
+ skipScroll: nativeScrolled,
789
+ skipFocus: nativeFocused,
790
+ }));
791
+ if (preparation?.ok !== true) {
792
+ throw new TargetError({
793
+ code: 'not_editable',
794
+ message: `Target "${ref}" is not a fillable input, textarea, or contenteditable element.`,
795
+ hint: 'Use `webcmd browser state` to pick an editable target, or use `browser type` for keyboard-like interactions.',
796
+ });
797
+ }
798
+ const usedNativeInput = await this.tryNativeType(text);
799
+ if (!usedNativeInput) {
800
+ await this.evaluate(typeResolvedJs(text));
801
+ }
802
+ let verification = await this.evaluate(verifyFilledResolvedJs(text));
803
+ if (usedNativeInput && verification?.ok !== true) {
804
+ await this.evaluate(typeResolvedJs(text));
805
+ verification = await this.evaluate(verifyFilledResolvedJs(text));
806
+ }
807
+ const actual = verification && 'actual' in verification ? verification.actual : '';
808
+ const mode = verification && 'mode' in verification ? verification.mode : undefined;
809
+ return {
810
+ ...resolved,
811
+ filled: true,
812
+ verified: verification?.ok === true,
813
+ expected: text,
814
+ actual,
815
+ length: actual.length,
816
+ ...(mode ? { mode } : {}),
817
+ };
818
+ }
819
+ async pressKey(key) {
820
+ const parsed = parseKeyChord(key);
821
+ if (!await this.tryNativeKeyPress(parsed.key, parsed.modifiers)) {
822
+ await this.evaluate(pressKeyJs(parsed.key, parsed.modifiers));
823
+ }
824
+ }
825
+ async scrollTo(ref, opts = {}) {
826
+ const resolved = await runResolve(this, ref, opts);
827
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
828
+ const result = (await this.evaluate(scrollResolvedJs({ skipScroll: nativeScrolled })));
829
+ // Fold match_level into the scroll payload so the user-facing envelope
830
+ // carries it the same way click / type do.
831
+ if (result && typeof result === 'object') {
832
+ return { ...result, matches_n: resolved.matches_n, match_level: resolved.match_level };
833
+ }
834
+ return { matches_n: resolved.matches_n, match_level: resolved.match_level };
835
+ }
836
+ async getFormState() {
837
+ return (await this.evaluate(getFormStateJs()));
838
+ }
839
+ async scroll(direction = 'down', amount = 500) {
840
+ await this.evaluate(scrollJs(direction, amount));
841
+ }
842
+ async autoScroll(options) {
843
+ const times = options?.times ?? 3;
844
+ const delayMs = options?.delayMs ?? 2000;
845
+ await this.evaluate(autoScrollJs(times, delayMs));
846
+ }
847
+ async networkRequests(includeStatic = false) {
848
+ const result = await this.evaluate(networkRequestsJs(includeStatic));
849
+ return Array.isArray(result) ? result : [];
850
+ }
851
+ async consoleMessages(_level = 'info') {
852
+ return [];
853
+ }
854
+ async wait(options) {
855
+ if (typeof options === 'number') {
856
+ if (options >= 1) {
857
+ try {
858
+ const maxMs = options * 1000;
859
+ await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
860
+ return;
861
+ }
862
+ catch {
863
+ // Fallback: fixed sleep
864
+ }
865
+ }
866
+ await new Promise(resolve => setTimeout(resolve, options * 1000));
867
+ return;
868
+ }
869
+ if (typeof options.time === 'number') {
870
+ await new Promise(resolve => setTimeout(resolve, options.time * 1000));
871
+ return;
872
+ }
873
+ if (options.selector) {
874
+ const timeout = (options.timeout ?? 10) * 1000;
875
+ await this.evaluate(waitForSelectorJs(options.selector, timeout));
876
+ return;
877
+ }
878
+ if (options.text) {
879
+ const timeout = (options.timeout ?? 30) * 1000;
880
+ await this.evaluate(waitForTextJs(options.text, timeout));
881
+ }
882
+ }
883
+ async snapshot(opts = {}) {
884
+ if (opts.source === 'ax') {
885
+ const cdp = this.cdp;
886
+ if (typeof cdp !== 'function') {
887
+ throw new CliError('BROWSER_AX_UNAVAILABLE', 'AX snapshot requires CDP support from the active browser backend.', 'Use the default DOM state, or update the active browser runtime.');
888
+ }
889
+ const axTrees = await this.collectAxSnapshotTrees(cdp);
890
+ const built = buildAxSnapshotFromTrees(axTrees, {
891
+ maxDepth: opts.maxDepth,
892
+ interactiveOnly: opts.interactive,
893
+ });
894
+ this._axRefs = built.refs;
895
+ return built.text;
896
+ }
897
+ this._axRefs.clear();
898
+ const snapshotJs = generateSnapshotJs({
899
+ viewportExpand: opts.viewportExpand ?? 2000,
900
+ maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
901
+ interactiveOnly: opts.interactive ?? false,
902
+ maxTextLength: opts.maxTextLength ?? 120,
903
+ includeScrollInfo: true,
904
+ bboxDedup: true,
905
+ previousHashes: this._prevSnapshotHashes,
906
+ });
907
+ try {
908
+ const result = await this.evaluate(snapshotJs);
909
+ // Read back the hashes stored by the snapshot for next diff
910
+ try {
911
+ const hashes = await this.evaluate('window.__webcmd_prev_hashes');
912
+ this._prevSnapshotHashes = typeof hashes === 'string' ? hashes : null;
913
+ }
914
+ catch {
915
+ // Non-fatal: diff is best-effort
916
+ }
917
+ return result;
918
+ }
919
+ catch (err) {
920
+ // Log snapshot failure for debugging, then fallback to basic accessibility tree
921
+ if (process.env.DEBUG_SNAPSHOT) {
922
+ process.stderr.write(`[snapshot] DOM snapshot failed, falling back to accessibility tree: ${err?.message?.slice(0, 200)}\n`);
923
+ }
924
+ return this._basicSnapshot(opts);
925
+ }
926
+ }
927
+ async collectAxSnapshotTrees(cdp) {
928
+ await cdp.call(this, 'Accessibility.enable', {});
929
+ const rootTree = await cdp.call(this, 'Accessibility.getFullAXTree', {});
930
+ const trees = [{ tree: rootTree }];
931
+ const frameTreeResult = await cdp.call(this, 'Page.getFrameTree', {}).catch(() => null);
932
+ const frames = collectAxFrameRefs(frameTreeResult);
933
+ for (const frame of frames) {
934
+ if (frame.sessionId) {
935
+ await cdp.call(this, 'Accessibility.enable', axEnableParams(frame)).catch(() => null);
936
+ }
937
+ const tree = await cdp.call(this, 'Accessibility.getFullAXTree', axTreeParams(frame)).catch(() => null);
938
+ if (tree)
939
+ trees.push({ tree, frame });
940
+ }
941
+ return trees;
942
+ }
943
+ async getCurrentUrl() {
944
+ if (this._lastUrl)
945
+ return this._lastUrl;
946
+ try {
947
+ const current = await this.evaluate('window.location.href');
948
+ if (typeof current === 'string' && current) {
949
+ this._lastUrl = current;
950
+ return current;
951
+ }
952
+ }
953
+ catch {
954
+ // Best-effort
955
+ }
956
+ return null;
957
+ }
958
+ async installInterceptor(pattern) {
959
+ const { generateInterceptorJs } = await import('../interceptor.js');
960
+ await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
961
+ arrayName: '__webcmd_xhr',
962
+ patchGuard: '__webcmd_interceptor_patched',
963
+ }));
964
+ }
965
+ async getInterceptedRequests() {
966
+ const { generateReadInterceptedJs } = await import('../interceptor.js');
967
+ const result = await this.evaluate(generateReadInterceptedJs('__webcmd_xhr'));
968
+ return Array.isArray(result) ? result : [];
969
+ }
970
+ async waitForCapture(timeout = 10) {
971
+ const maxMs = timeout * 1000;
972
+ await this.evaluate(waitForCaptureJs(maxMs));
973
+ }
974
+ /** Fallback basic snapshot */
975
+ async _basicSnapshot(opts = {}) {
976
+ const maxDepth = Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200));
977
+ const code = `
978
+ (async () => {
979
+ function buildTree(node, depth) {
980
+ if (depth > ${maxDepth}) return '';
981
+ const role = node.getAttribute?.('role') || node.tagName?.toLowerCase() || 'generic';
982
+ const name = node.getAttribute?.('aria-label') || node.getAttribute?.('alt') || node.textContent?.trim().slice(0, 80) || '';
983
+ const isInteractive = ['a', 'button', 'input', 'select', 'textarea'].includes(node.tagName?.toLowerCase()) || node.getAttribute?.('tabindex') != null;
984
+
985
+ ${opts.interactive ? 'if (!isInteractive && !node.children?.length) return "";' : ''}
986
+
987
+ let indent = ' '.repeat(depth);
988
+ let line = indent + role;
989
+ if (name) line += ' "' + name.replace(/"/g, '\\\\\\"') + '"';
990
+ if (node.tagName?.toLowerCase() === 'a' && node.href) line += ' [' + node.href + ']';
991
+ if (node.tagName?.toLowerCase() === 'input') line += ' [' + (node.type || 'text') + ']';
992
+
993
+ let result = line + '\\n';
994
+ if (node.children) {
995
+ for (const child of node.children) {
996
+ result += buildTree(child, depth + 1);
997
+ }
998
+ }
999
+ return result;
1000
+ }
1001
+ return buildTree(document.body, 0);
1002
+ })()
1003
+ `;
1004
+ const raw = await this.evaluate(code);
1005
+ if (opts.raw)
1006
+ return raw;
1007
+ if (typeof raw === 'string')
1008
+ return formatSnapshot(raw, opts);
1009
+ return raw;
1010
+ }
1011
+ }
1012
+ function axTreeParams(frame) {
1013
+ return frame?.frameId
1014
+ ? {
1015
+ frameId: frame.frameId,
1016
+ ...(frame.sessionId ? { sessionId: frame.sessionId } : {}),
1017
+ ...(frame.targetUrl ? { targetUrl: frame.targetUrl } : {}),
1018
+ }
1019
+ : {};
1020
+ }
1021
+ function axEnableParams(frame) {
1022
+ return frame?.frameId && frame.sessionId
1023
+ ? { frameId: frame.frameId, sessionId: frame.sessionId, ...(frame.targetUrl ? { targetUrl: frame.targetUrl } : {}) }
1024
+ : {};
1025
+ }
1026
+ function collectAxFrameRefs(frameTreeResult) {
1027
+ const root = frameTreeResult?.frameTree;
1028
+ const rootUrl = root?.frame?.url || root?.frame?.unreachableUrl || '';
1029
+ const rootOrigin = urlOrigin(rootUrl);
1030
+ if (!root || !rootOrigin)
1031
+ return [];
1032
+ const frames = [];
1033
+ function collect(node) {
1034
+ for (const child of node?.childFrames ?? []) {
1035
+ const frame = child.frame;
1036
+ const frameId = frame?.id;
1037
+ const frameUrl = frame?.url || frame?.unreachableUrl || '';
1038
+ const origin = urlOrigin(frameUrl);
1039
+ if (!frameId)
1040
+ continue;
1041
+ if (origin === rootOrigin) {
1042
+ frames.push({ frameId, url: frameUrl });
1043
+ collect(child);
1044
+ }
1045
+ else {
1046
+ frames.push({ frameId, url: frameUrl, targetUrl: frameUrl, sessionId: 'target' });
1047
+ }
1048
+ }
1049
+ }
1050
+ collect(root);
1051
+ return frames;
1052
+ }
1053
+ function urlOrigin(url) {
1054
+ try {
1055
+ return new URL(url).origin;
1056
+ }
1057
+ catch {
1058
+ return null;
1059
+ }
1060
+ }