@mseep/clawdcursor 1.5.5

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 (354) hide show
  1. package/CHANGELOG.md +2264 -0
  2. package/LICENSE +21 -0
  3. package/README.md +385 -0
  4. package/SECURITY.md +44 -0
  5. package/SKILL.md +503 -0
  6. package/dist/core/agent-loop/agent.d.ts +42 -0
  7. package/dist/core/agent-loop/agent.js +1023 -0
  8. package/dist/core/agent-loop/agent.js.map +1 -0
  9. package/dist/core/agent-loop/batch-tool.d.ts +25 -0
  10. package/dist/core/agent-loop/batch-tool.js +218 -0
  11. package/dist/core/agent-loop/batch-tool.js.map +1 -0
  12. package/dist/core/agent-loop/coord-scale.d.ts +72 -0
  13. package/dist/core/agent-loop/coord-scale.js +89 -0
  14. package/dist/core/agent-loop/coord-scale.js.map +1 -0
  15. package/dist/core/agent-loop/focus-guard.d.ts +24 -0
  16. package/dist/core/agent-loop/focus-guard.js +29 -0
  17. package/dist/core/agent-loop/focus-guard.js.map +1 -0
  18. package/dist/core/agent-loop/project-mcp.d.ts +97 -0
  19. package/dist/core/agent-loop/project-mcp.js +253 -0
  20. package/dist/core/agent-loop/project-mcp.js.map +1 -0
  21. package/dist/core/agent-loop/prompt.d.ts +45 -0
  22. package/dist/core/agent-loop/prompt.js +426 -0
  23. package/dist/core/agent-loop/prompt.js.map +1 -0
  24. package/dist/core/agent-loop/tool-meta.d.ts +93 -0
  25. package/dist/core/agent-loop/tool-meta.js +651 -0
  26. package/dist/core/agent-loop/tool-meta.js.map +1 -0
  27. package/dist/core/agent-loop/tools.d.ts +38 -0
  28. package/dist/core/agent-loop/tools.js +2134 -0
  29. package/dist/core/agent-loop/tools.js.map +1 -0
  30. package/dist/core/agent-loop/types.d.ts +170 -0
  31. package/dist/core/agent-loop/types.js +12 -0
  32. package/dist/core/agent-loop/types.js.map +1 -0
  33. package/dist/core/agent.d.ts +51 -0
  34. package/dist/core/agent.js +245 -0
  35. package/dist/core/agent.js.map +1 -0
  36. package/dist/core/app-categories.d.ts +67 -0
  37. package/dist/core/app-categories.js +108 -0
  38. package/dist/core/app-categories.js.map +1 -0
  39. package/dist/core/banner.d.ts +70 -0
  40. package/dist/core/banner.js +245 -0
  41. package/dist/core/banner.js.map +1 -0
  42. package/dist/core/classify/capability.d.ts +45 -0
  43. package/dist/core/classify/capability.js +78 -0
  44. package/dist/core/classify/capability.js.map +1 -0
  45. package/dist/core/decompose/llm-decomposer.d.ts +35 -0
  46. package/dist/core/decompose/llm-decomposer.js +156 -0
  47. package/dist/core/decompose/llm-decomposer.js.map +1 -0
  48. package/dist/core/decompose/parser.d.ts +27 -0
  49. package/dist/core/decompose/parser.js +101 -0
  50. package/dist/core/decompose/parser.js.map +1 -0
  51. package/dist/core/observability/correlation.d.ts +19 -0
  52. package/dist/core/observability/correlation.js +36 -0
  53. package/dist/core/observability/correlation.js.map +1 -0
  54. package/dist/core/observability/cost-meter.d.ts +51 -0
  55. package/dist/core/observability/cost-meter.js +134 -0
  56. package/dist/core/observability/cost-meter.js.map +1 -0
  57. package/dist/core/observability/logger.d.ts +61 -0
  58. package/dist/core/observability/logger.js +550 -0
  59. package/dist/core/observability/logger.js.map +1 -0
  60. package/dist/core/router/aliases.d.ts +50 -0
  61. package/dist/core/router/aliases.js +104 -0
  62. package/dist/core/router/aliases.js.map +1 -0
  63. package/dist/core/router/normalize.d.ts +41 -0
  64. package/dist/core/router/normalize.js +80 -0
  65. package/dist/core/router/normalize.js.map +1 -0
  66. package/dist/core/safety.d.ts +126 -0
  67. package/dist/core/safety.js +568 -0
  68. package/dist/core/safety.js.map +1 -0
  69. package/dist/core/sense/a11y-resolver.d.ts +73 -0
  70. package/dist/core/sense/a11y-resolver.js +76 -0
  71. package/dist/core/sense/a11y-resolver.js.map +1 -0
  72. package/dist/core/sense/fingerprint.d.ts +41 -0
  73. package/dist/core/sense/fingerprint.js +123 -0
  74. package/dist/core/sense/fingerprint.js.map +1 -0
  75. package/dist/core/sense/rank.d.ts +70 -0
  76. package/dist/core/sense/rank.js +192 -0
  77. package/dist/core/sense/rank.js.map +1 -0
  78. package/dist/core/sense/reactive-check.d.ts +40 -0
  79. package/dist/core/sense/reactive-check.js +48 -0
  80. package/dist/core/sense/reactive-check.js.map +1 -0
  81. package/dist/core/sense/snapshot.d.ts +19 -0
  82. package/dist/core/sense/snapshot.js +100 -0
  83. package/dist/core/sense/snapshot.js.map +1 -0
  84. package/dist/core/sense/types.d.ts +66 -0
  85. package/dist/core/sense/types.js +9 -0
  86. package/dist/core/sense/types.js.map +1 -0
  87. package/dist/core/sense/ui-map-anchors.d.ts +7 -0
  88. package/dist/core/sense/ui-map-anchors.js +24 -0
  89. package/dist/core/sense/ui-map-anchors.js.map +1 -0
  90. package/dist/core/sense/ui-map-elements.d.ts +5 -0
  91. package/dist/core/sense/ui-map-elements.js +33 -0
  92. package/dist/core/sense/ui-map-elements.js.map +1 -0
  93. package/dist/core/sense/ui-map-find.d.ts +56 -0
  94. package/dist/core/sense/ui-map-find.js +153 -0
  95. package/dist/core/sense/ui-map-find.js.map +1 -0
  96. package/dist/core/sense/ui-map-fuse.d.ts +4 -0
  97. package/dist/core/sense/ui-map-fuse.js +44 -0
  98. package/dist/core/sense/ui-map-fuse.js.map +1 -0
  99. package/dist/core/sense/ui-map-geom.d.ts +3 -0
  100. package/dist/core/sense/ui-map-geom.js +16 -0
  101. package/dist/core/sense/ui-map-geom.js.map +1 -0
  102. package/dist/core/sense/ui-map-holder.d.ts +58 -0
  103. package/dist/core/sense/ui-map-holder.js +87 -0
  104. package/dist/core/sense/ui-map-holder.js.map +1 -0
  105. package/dist/core/sense/ui-map-normalize.d.ts +19 -0
  106. package/dist/core/sense/ui-map-normalize.js +65 -0
  107. package/dist/core/sense/ui-map-normalize.js.map +1 -0
  108. package/dist/core/sense/ui-map-render.d.ts +4 -0
  109. package/dist/core/sense/ui-map-render.js +34 -0
  110. package/dist/core/sense/ui-map-render.js.map +1 -0
  111. package/dist/core/sense/ui-map-resolve.d.ts +41 -0
  112. package/dist/core/sense/ui-map-resolve.js +59 -0
  113. package/dist/core/sense/ui-map-resolve.js.map +1 -0
  114. package/dist/core/sense/ui-map-types.d.ts +66 -0
  115. package/dist/core/sense/ui-map-types.js +11 -0
  116. package/dist/core/sense/ui-map-types.js.map +1 -0
  117. package/dist/core/sense/ui-map.d.ts +29 -0
  118. package/dist/core/sense/ui-map.js +113 -0
  119. package/dist/core/sense/ui-map.js.map +1 -0
  120. package/dist/core/verify/assertions.d.ts +132 -0
  121. package/dist/core/verify/assertions.js +284 -0
  122. package/dist/core/verify/assertions.js.map +1 -0
  123. package/dist/index.d.ts +21 -0
  124. package/dist/index.js +24 -0
  125. package/dist/index.js.map +1 -0
  126. package/dist/llm/browser-config.d.ts +36 -0
  127. package/dist/llm/browser-config.js +83 -0
  128. package/dist/llm/browser-config.js.map +1 -0
  129. package/dist/llm/client.d.ts +268 -0
  130. package/dist/llm/client.js +1094 -0
  131. package/dist/llm/client.js.map +1 -0
  132. package/dist/llm/config.d.ts +79 -0
  133. package/dist/llm/config.js +375 -0
  134. package/dist/llm/config.js.map +1 -0
  135. package/dist/llm/credentials.d.ts +35 -0
  136. package/dist/llm/credentials.js +491 -0
  137. package/dist/llm/credentials.js.map +1 -0
  138. package/dist/llm/external-creds.d.ts +42 -0
  139. package/dist/llm/external-creds.js +169 -0
  140. package/dist/llm/external-creds.js.map +1 -0
  141. package/dist/llm/providers.d.ts +123 -0
  142. package/dist/llm/providers.js +717 -0
  143. package/dist/llm/providers.js.map +1 -0
  144. package/dist/paths.d.ts +31 -0
  145. package/dist/paths.js +147 -0
  146. package/dist/paths.js.map +1 -0
  147. package/dist/platform/accessibility.d.ts +139 -0
  148. package/dist/platform/accessibility.js +670 -0
  149. package/dist/platform/accessibility.js.map +1 -0
  150. package/dist/platform/cdp-driver.d.ts +318 -0
  151. package/dist/platform/cdp-driver.js +1179 -0
  152. package/dist/platform/cdp-driver.js.map +1 -0
  153. package/dist/platform/index.d.ts +11 -0
  154. package/dist/platform/index.js +69 -0
  155. package/dist/platform/index.js.map +1 -0
  156. package/dist/platform/keys.d.ts +17 -0
  157. package/dist/platform/keys.js +129 -0
  158. package/dist/platform/keys.js.map +1 -0
  159. package/dist/platform/launch-poll.d.ts +101 -0
  160. package/dist/platform/launch-poll.js +177 -0
  161. package/dist/platform/launch-poll.js.map +1 -0
  162. package/dist/platform/linux.d.ts +173 -0
  163. package/dist/platform/linux.js +1253 -0
  164. package/dist/platform/linux.js.map +1 -0
  165. package/dist/platform/macos.d.ts +136 -0
  166. package/dist/platform/macos.js +976 -0
  167. package/dist/platform/macos.js.map +1 -0
  168. package/dist/platform/native-desktop.d.ts +145 -0
  169. package/dist/platform/native-desktop.js +936 -0
  170. package/dist/platform/native-desktop.js.map +1 -0
  171. package/dist/platform/native-helper.d.ts +130 -0
  172. package/dist/platform/native-helper.js +592 -0
  173. package/dist/platform/native-helper.js.map +1 -0
  174. package/dist/platform/ocr-engine.d.ts +78 -0
  175. package/dist/platform/ocr-engine.js +363 -0
  176. package/dist/platform/ocr-engine.js.map +1 -0
  177. package/dist/platform/ps-runner.d.ts +28 -0
  178. package/dist/platform/ps-runner.js +228 -0
  179. package/dist/platform/ps-runner.js.map +1 -0
  180. package/dist/platform/types.d.ts +397 -0
  181. package/dist/platform/types.js +15 -0
  182. package/dist/platform/types.js.map +1 -0
  183. package/dist/platform/uri-handler.d.ts +75 -0
  184. package/dist/platform/uri-handler.js +273 -0
  185. package/dist/platform/uri-handler.js.map +1 -0
  186. package/dist/platform/wayland-backend.d.ts +53 -0
  187. package/dist/platform/wayland-backend.js +348 -0
  188. package/dist/platform/wayland-backend.js.map +1 -0
  189. package/dist/platform/windows.d.ts +232 -0
  190. package/dist/platform/windows.js +1210 -0
  191. package/dist/platform/windows.js.map +1 -0
  192. package/dist/postbuild.d.ts +10 -0
  193. package/dist/postbuild.js +98 -0
  194. package/dist/postbuild.js.map +1 -0
  195. package/dist/schema/snapshot.d.ts +33 -0
  196. package/dist/schema/snapshot.js +90 -0
  197. package/dist/schema/snapshot.js.map +1 -0
  198. package/dist/shortcuts.d.ts +30 -0
  199. package/dist/shortcuts.js +261 -0
  200. package/dist/shortcuts.js.map +1 -0
  201. package/dist/surface/cli.d.ts +7 -0
  202. package/dist/surface/cli.js +1556 -0
  203. package/dist/surface/cli.js.map +1 -0
  204. package/dist/surface/dashboard.d.ts +8 -0
  205. package/dist/surface/dashboard.js +1193 -0
  206. package/dist/surface/dashboard.js.map +1 -0
  207. package/dist/surface/doctor.d.ts +29 -0
  208. package/dist/surface/doctor.js +1514 -0
  209. package/dist/surface/doctor.js.map +1 -0
  210. package/dist/surface/format.d.ts +10 -0
  211. package/dist/surface/format.js +37 -0
  212. package/dist/surface/format.js.map +1 -0
  213. package/dist/surface/http-utility.d.ts +65 -0
  214. package/dist/surface/http-utility.js +336 -0
  215. package/dist/surface/http-utility.js.map +1 -0
  216. package/dist/surface/mcp-server.d.ts +91 -0
  217. package/dist/surface/mcp-server.js +280 -0
  218. package/dist/surface/mcp-server.js.map +1 -0
  219. package/dist/surface/onboarding.d.ts +15 -0
  220. package/dist/surface/onboarding.js +184 -0
  221. package/dist/surface/onboarding.js.map +1 -0
  222. package/dist/surface/pidfile.d.ts +79 -0
  223. package/dist/surface/pidfile.js +263 -0
  224. package/dist/surface/pidfile.js.map +1 -0
  225. package/dist/surface/readiness.d.ts +45 -0
  226. package/dist/surface/readiness.js +230 -0
  227. package/dist/surface/readiness.js.map +1 -0
  228. package/dist/surface/report.d.ts +68 -0
  229. package/dist/surface/report.js +341 -0
  230. package/dist/surface/report.js.map +1 -0
  231. package/dist/surface/skill-register.d.ts +14 -0
  232. package/dist/surface/skill-register.js +150 -0
  233. package/dist/surface/skill-register.js.map +1 -0
  234. package/dist/surface/version.d.ts +6 -0
  235. package/dist/surface/version.js +27 -0
  236. package/dist/surface/version.js.map +1 -0
  237. package/dist/tools/a11y.d.ts +8 -0
  238. package/dist/tools/a11y.js +545 -0
  239. package/dist/tools/a11y.js.map +1 -0
  240. package/dist/tools/a11y_depth.d.ts +19 -0
  241. package/dist/tools/a11y_depth.js +455 -0
  242. package/dist/tools/a11y_depth.js.map +1 -0
  243. package/dist/tools/agent.d.ts +15 -0
  244. package/dist/tools/agent.js +248 -0
  245. package/dist/tools/agent.js.map +1 -0
  246. package/dist/tools/batch.d.ts +46 -0
  247. package/dist/tools/batch.js +230 -0
  248. package/dist/tools/batch.js.map +1 -0
  249. package/dist/tools/cdp.d.ts +8 -0
  250. package/dist/tools/cdp.js +233 -0
  251. package/dist/tools/cdp.js.map +1 -0
  252. package/dist/tools/compact.d.ts +63 -0
  253. package/dist/tools/compact.js +418 -0
  254. package/dist/tools/compact.js.map +1 -0
  255. package/dist/tools/cost-class.d.ts +38 -0
  256. package/dist/tools/cost-class.js +117 -0
  257. package/dist/tools/cost-class.js.map +1 -0
  258. package/dist/tools/desktop.d.ts +9 -0
  259. package/dist/tools/desktop.js +346 -0
  260. package/dist/tools/desktop.js.map +1 -0
  261. package/dist/tools/electron_bridge.d.ts +41 -0
  262. package/dist/tools/electron_bridge.js +261 -0
  263. package/dist/tools/electron_bridge.js.map +1 -0
  264. package/dist/tools/extras.d.ts +22 -0
  265. package/dist/tools/extras.js +942 -0
  266. package/dist/tools/extras.js.map +1 -0
  267. package/dist/tools/favorites.d.ts +13 -0
  268. package/dist/tools/favorites.js +137 -0
  269. package/dist/tools/favorites.js.map +1 -0
  270. package/dist/tools/introspection.d.ts +13 -0
  271. package/dist/tools/introspection.js +55 -0
  272. package/dist/tools/introspection.js.map +1 -0
  273. package/dist/tools/ocr.d.ts +8 -0
  274. package/dist/tools/ocr.js +66 -0
  275. package/dist/tools/ocr.js.map +1 -0
  276. package/dist/tools/orchestration.d.ts +7 -0
  277. package/dist/tools/orchestration.js +377 -0
  278. package/dist/tools/orchestration.js.map +1 -0
  279. package/dist/tools/playbooks/extract-compose.d.ts +22 -0
  280. package/dist/tools/playbooks/extract-compose.js +85 -0
  281. package/dist/tools/playbooks/extract-compose.js.map +1 -0
  282. package/dist/tools/playbooks/find-replace.d.ts +11 -0
  283. package/dist/tools/playbooks/find-replace.js +56 -0
  284. package/dist/tools/playbooks/find-replace.js.map +1 -0
  285. package/dist/tools/playbooks/index.d.ts +63 -0
  286. package/dist/tools/playbooks/index.js +70 -0
  287. package/dist/tools/playbooks/index.js.map +1 -0
  288. package/dist/tools/playbooks/keys-blocklist.d.ts +24 -0
  289. package/dist/tools/playbooks/keys-blocklist.js +89 -0
  290. package/dist/tools/playbooks/keys-blocklist.js.map +1 -0
  291. package/dist/tools/registry.d.ts +40 -0
  292. package/dist/tools/registry.js +560 -0
  293. package/dist/tools/registry.js.map +1 -0
  294. package/dist/tools/safety-gate.d.ts +16 -0
  295. package/dist/tools/safety-gate.js +70 -0
  296. package/dist/tools/safety-gate.js.map +1 -0
  297. package/dist/tools/scheduler.d.ts +76 -0
  298. package/dist/tools/scheduler.js +413 -0
  299. package/dist/tools/scheduler.js.map +1 -0
  300. package/dist/tools/shortcuts.d.ts +13 -0
  301. package/dist/tools/shortcuts.js +205 -0
  302. package/dist/tools/shortcuts.js.map +1 -0
  303. package/dist/tools/smart.d.ts +15 -0
  304. package/dist/tools/smart.js +785 -0
  305. package/dist/tools/smart.js.map +1 -0
  306. package/dist/tools/types.d.ts +174 -0
  307. package/dist/tools/types.js +67 -0
  308. package/dist/tools/types.js.map +1 -0
  309. package/dist/tools/window-text.d.ts +15 -0
  310. package/dist/tools/window-text.js +39 -0
  311. package/dist/tools/window-text.js.map +1 -0
  312. package/dist/types.d.ts +122 -0
  313. package/dist/types.js +41 -0
  314. package/dist/types.js.map +1 -0
  315. package/native/Package.swift +38 -0
  316. package/native/README.md +113 -0
  317. package/native/Sources/ClawdCursorHelper/main.swift +602 -0
  318. package/native/Sources/ClawdCursorHost/main.swift +182 -0
  319. package/native/Sources/PermissionCheck/main.swift +53 -0
  320. package/native/Sources/ScreenshotHelper/main.swift +219 -0
  321. package/native/build.sh +139 -0
  322. package/native/entitlements.plist +12 -0
  323. package/package.json +115 -0
  324. package/scripts/banner.ps1 +112 -0
  325. package/scripts/coord-accuracy.ps1 +140 -0
  326. package/scripts/coord-uwp.ps1 +80 -0
  327. package/scripts/edge-glow.ps1 +180 -0
  328. package/scripts/find-element.ps1 +198 -0
  329. package/scripts/get-foreground-window.ps1 +71 -0
  330. package/scripts/get-screen-context.ps1 +183 -0
  331. package/scripts/get-windows.ps1 +66 -0
  332. package/scripts/install-panic-hotkey.ps1 +46 -0
  333. package/scripts/interact-element.ps1 +431 -0
  334. package/scripts/invoke-element.ps1 +314 -0
  335. package/scripts/linux/atspi-bridge.py +356 -0
  336. package/scripts/linux/ocr-recognize.py +154 -0
  337. package/scripts/mac/_window-picker.jxa +163 -0
  338. package/scripts/mac/find-element.jxa +0 -0
  339. package/scripts/mac/find-element.sh +161 -0
  340. package/scripts/mac/focus-window.jxa +284 -0
  341. package/scripts/mac/get-focused-element.jxa +102 -0
  342. package/scripts/mac/get-foreground-window.jxa +173 -0
  343. package/scripts/mac/get-screen-context.jxa +197 -0
  344. package/scripts/mac/get-ui-tree.sh +141 -0
  345. package/scripts/mac/get-windows.jxa +117 -0
  346. package/scripts/mac/interact-element.sh +235 -0
  347. package/scripts/mac/invoke-element.jxa +408 -0
  348. package/scripts/mac/ocr-recognize.swift +124 -0
  349. package/scripts/ocr-recognize.ps1 +102 -0
  350. package/scripts/postinstall-native.js +48 -0
  351. package/scripts/ps-bridge.ps1 +830 -0
  352. package/scripts/smoke-mcp.ps1 +119 -0
  353. package/scripts/sync-version.ts +178 -0
  354. package/scripts/verify-install.js +81 -0
@@ -0,0 +1,1179 @@
1
+ "use strict";
2
+ /**
3
+ * CDPDriver — Chrome DevTools Protocol driver for browser DOM interaction.
4
+ *
5
+ * This solves the "Gmail compose problem" and similar web-app challenges:
6
+ * Windows UI Automation sees browser content as a monolithic "Document" or
7
+ * "Pane" element — it can't reach individual DOM elements like input fields,
8
+ * buttons, or content-editable divs inside web pages.
9
+ *
10
+ * CDPDriver connects to Chrome/Edge via the DevTools Protocol and provides:
11
+ * - DOM element queries by CSS selector, XPath, ARIA role, text content
12
+ * - Click, type, focus, select on DOM elements
13
+ * - Form filling by label text (finds the input associated with a label)
14
+ * - Page state queries (URL, title, ready state)
15
+ * - Script evaluation for custom interactions
16
+ *
17
+ * Architecture:
18
+ * ┌─────────────────────────────┐
19
+ * │ UIDriver (native UI) │ ← Windows UI Automation
20
+ * ├─────────────────────────────┤
21
+ * │ CDPDriver (browser DOM) │ ← This module (CDP)
22
+ * ├─────────────────────────────┤
23
+ * │ Browser tools (CDP) │ ← Pipeline browser MCP surface
24
+ * └─────────────────────────────┘
25
+ *
26
+ * Connection:
27
+ * Edge/Chrome must be launched with --remote-debugging-port=9223
28
+ * Or connect to an existing Playwright-managed browser's CDP endpoint.
29
+ *
30
+ * Usage:
31
+ * const cdp = new CDPDriver();
32
+ * await cdp.connect(); // connects to Edge/Chrome on port 9223
33
+ *
34
+ * // Click a button by text
35
+ * await cdp.clickByText('Compose');
36
+ *
37
+ * // Type into a Gmail field
38
+ * await cdp.typeInField('[aria-label="To"]', 'user@example.com');
39
+ *
40
+ * // Fill a form by label
41
+ * await cdp.fillFormByLabels({ 'Subject': 'Hello', 'To': 'user@example.com' });
42
+ *
43
+ * // Query elements
44
+ * const results = await cdp.querySelectorAll('button');
45
+ */
46
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
47
+ if (k2 === undefined) k2 = k;
48
+ var desc = Object.getOwnPropertyDescriptor(m, k);
49
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
50
+ desc = { enumerable: true, get: function() { return m[k]; } };
51
+ }
52
+ Object.defineProperty(o, k2, desc);
53
+ }) : (function(o, m, k, k2) {
54
+ if (k2 === undefined) k2 = k;
55
+ o[k2] = m[k];
56
+ }));
57
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
58
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
59
+ }) : function(o, v) {
60
+ o["default"] = v;
61
+ });
62
+ var __importStar = (this && this.__importStar) || (function () {
63
+ var ownKeys = function(o) {
64
+ ownKeys = Object.getOwnPropertyNames || function (o) {
65
+ var ar = [];
66
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
67
+ return ar;
68
+ };
69
+ return ownKeys(o);
70
+ };
71
+ return function (mod) {
72
+ if (mod && mod.__esModule) return mod;
73
+ var result = {};
74
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
75
+ __setModuleDefault(result, mod);
76
+ return result;
77
+ };
78
+ })();
79
+ Object.defineProperty(exports, "__esModule", { value: true });
80
+ exports.CDPDriver = void 0;
81
+ const playwright_1 = require("playwright");
82
+ const child_process_1 = require("child_process");
83
+ const os = __importStar(require("os"));
84
+ const path = __importStar(require("path"));
85
+ const fs = __importStar(require("fs"));
86
+ const browser_config_1 = require("../llm/browser-config");
87
+ // ── Default CDP port — matches browser-config.ts and tool launch flags ──
88
+ const DEFAULT_CDP_PORT = 9223;
89
+ /**
90
+ * CDPDriver — interact with web page DOM elements via Chrome DevTools Protocol.
91
+ *
92
+ * Uses Playwright's CDP connection under the hood, so it works with any
93
+ * Chromium-based browser launched with --remote-debugging-port. You can
94
+ * also share a Page object directly.
95
+ */
96
+ class CDPDriver {
97
+ browser = null;
98
+ activePage = null;
99
+ connected = false;
100
+ cdpPort;
101
+ ownsBrowser = false; // true if we created the browser connection
102
+ cursorInjected = false;
103
+ // Provenance of the current connection, for honest disclosure to the agent:
104
+ // 'dedicated' — we spawned a private --user-data-dir instance the agent owns.
105
+ // 'attached' — we connected to a browser ALREADY on the debug port, which
106
+ // may be the user's own session (navigation affects THEIR tabs).
107
+ // 'unknown' — not yet connected.
108
+ connectionMode = 'unknown';
109
+ // In 'attached' mode (someone else's browser), the agent must never drive the
110
+ // user's existing tabs. The first navigate() opens the agent's OWN tab and all
111
+ // subsequent work happens there. Mechanical — not dependent on the model
112
+ // honoring the disclosure text (root-cause fix 2026-06-11).
113
+ agentTab = null;
114
+ /** How the live connection was established (see connectionMode). */
115
+ getConnectionMode() { return this.connectionMode; }
116
+ /**
117
+ * @param cdpPort CDP debugging port (default 9223)
118
+ */
119
+ constructor(cdpPort = DEFAULT_CDP_PORT) {
120
+ this.cdpPort = cdpPort;
121
+ }
122
+ // ════════════════════════════════════════════════════════════════════
123
+ // CONNECTION
124
+ // ════════════════════════════════════════════════════════════════════
125
+ /**
126
+ * Connect to an existing Chrome/Edge instance via CDP.
127
+ *
128
+ * The browser must be launched with:
129
+ * msedge.exe --remote-debugging-port=9223
130
+ * chrome.exe --remote-debugging-port=9223
131
+ *
132
+ * @param port Override the port for this attempt (defaults to the
133
+ * constructor port). Ownership is encoded in the port: AGENT_CDP_PORT is
134
+ * only ever used by our own dedicated-profile launcher, so connecting
135
+ * there means 'dedicated'; any other port means we attached to a browser
136
+ * someone else put on the wire → 'attached'.
137
+ * @returns true if connected successfully
138
+ */
139
+ async connect(port) {
140
+ const targetPort = port ?? this.cdpPort;
141
+ try {
142
+ this.browser = await playwright_1.chromium.connectOverCDP(`http://127.0.0.1:${targetPort}`, { timeout: 15000 });
143
+ this.ownsBrowser = true;
144
+ this.connectionMode = targetPort === browser_config_1.AGENT_CDP_PORT ? 'dedicated' : 'attached';
145
+ this.agentTab = null; // fresh connection → no agent tab claimed yet
146
+ // Get the most relevant tab — search ALL browser contexts (not just the first)
147
+ // Priority: user-navigated pages > pinned/system widgets > browser internal pages
148
+ const BLOCKED_URL_PATTERNS = [
149
+ 'edge://', 'chrome://', 'about:',
150
+ // Known OEM/vendor widget pages that embed browsers as system components
151
+ 'vantage.csw.lenovo.com', 'lenovo.com/widget',
152
+ 'msn.com/spartan', 'bing.com/widget',
153
+ 'ntp.msn.com',
154
+ ];
155
+ const isUserPage = (url) => url.startsWith('http') &&
156
+ !BLOCKED_URL_PATTERNS.some(blocked => url.includes(blocked));
157
+ const contexts = this.browser.contexts();
158
+ const allPages = contexts.flatMap(ctx => ctx.pages());
159
+ const userPages = allPages.filter(p => isUserPage(p.url()));
160
+ const fallbackPage = allPages.find(p => !p.url().startsWith('edge://') && !p.url().startsWith('chrome://') && !p.url().startsWith('about:')) ?? allPages[0] ?? null;
161
+ // Among user pages, prefer the last one (most recently opened/navigated)
162
+ this.activePage = userPages.length > 0
163
+ ? userPages[userPages.length - 1]
164
+ : fallbackPage;
165
+ if (!this.activePage) {
166
+ console.warn(' ⚠️ CDPDriver: connected but no pages found');
167
+ return false;
168
+ }
169
+ this.connected = true;
170
+ const title = await this.activePage.title().catch(() => '(unknown)');
171
+ console.log(` 🔌 CDPDriver: connected to "${title}" at ${this.activePage.url()}`);
172
+ return true;
173
+ }
174
+ catch (err) {
175
+ console.log(` ❌ CDPDriver: failed to connect to CDP port ${targetPort}: ${err}`);
176
+ return false;
177
+ }
178
+ }
179
+ /**
180
+ * Ensure a CDP connection exists WITHOUT disturbing the user's own browser.
181
+ *
182
+ * Ownership is encoded in the PORT (root-cause fix 2026-06-11 — a single
183
+ * shared port meant the driver could not tell its own dedicated instance
184
+ * from the user's browser, and a lingering agent instance the user had
185
+ * adopted got its tabs hijacked):
186
+ *
187
+ * 1. Already connected → done.
188
+ * 2. Attach on AGENT_CDP_PORT — only our own dedicated-profile launcher
189
+ * ever uses that port, so this is OUR instance → 'dedicated'.
190
+ * 3. Attach on the user port (constructor port, default 9223) — a browser
191
+ * the USER put on the wire (their own flags, relaunch_with_cdp, the
192
+ * relay) → 'attached'; navigation gets new-tab discipline.
193
+ * 4. If `launch` is allowed, spawn the SYSTEM browser on AGENT_CDP_PORT
194
+ * with a dedicated `--user-data-dir`. Chromium keys its single-instance
195
+ * lock on the profile directory, so this starts a SEPARATE process that
196
+ * never closes, reuses, or steals focus from the user's windows. The
197
+ * blank tab is labeled "ClawdCursor — agent browser" so the window is
198
+ * recognizable and doesn't get silently adopted as a personal browser.
199
+ *
200
+ * @param opts.launch Allow launching a dedicated instance (default false → attach-only).
201
+ * @param opts.exePaths Candidate browser executables, in priority order.
202
+ */
203
+ async ensureConnected(opts = {}) {
204
+ if (await this.isConnected())
205
+ return true;
206
+ // 2. Our own dedicated instance from a previous run, if it's still alive.
207
+ if (await this.connect(browser_config_1.AGENT_CDP_PORT))
208
+ return true; // → 'dedicated'
209
+ // 3. A browser the user put on the debug port.
210
+ if (await this.connect(this.cdpPort))
211
+ return true; // → 'attached'
212
+ if (!opts.launch)
213
+ return false;
214
+ const exe = (opts.exePaths ?? []).find(p => {
215
+ try {
216
+ return fs.existsSync(p);
217
+ }
218
+ catch {
219
+ return false;
220
+ }
221
+ });
222
+ if (!exe) {
223
+ console.log(' ❌ CDPDriver: no browser executable found to launch with CDP');
224
+ return false;
225
+ }
226
+ // Dedicated profile dir → a separate Chromium instance that won't touch
227
+ // the user's running browser.
228
+ const profileDir = path.join(os.homedir(), '.clawdcursor', 'cdp-profile');
229
+ try {
230
+ fs.mkdirSync(profileDir, { recursive: true });
231
+ }
232
+ catch { /* best-effort */ }
233
+ try {
234
+ const args = [
235
+ `--remote-debugging-port=${browser_config_1.AGENT_CDP_PORT}`,
236
+ `--user-data-dir=${profileDir}`,
237
+ '--no-first-run',
238
+ '--no-default-browser-check',
239
+ // A fresh --user-data-dir profile has no saved window bounds, so Chromium
240
+ // opens at a small default size — the agent's browser came up as a tiny
241
+ // window (session 2026-06-11). Start maximized so screenshots/coords have
242
+ // the full viewport and the window is usable.
243
+ '--start-maximized',
244
+ '--new-window',
245
+ 'about:blank',
246
+ ];
247
+ const child = (0, child_process_1.spawn)(exe, args, { stdio: 'ignore', detached: true });
248
+ child.unref();
249
+ }
250
+ catch (err) {
251
+ console.log(` ❌ CDPDriver: failed to spawn browser with CDP: ${err}`);
252
+ return false;
253
+ }
254
+ // Browser cold-start can take a couple seconds — poll the CDP endpoint.
255
+ const deadline = Date.now() + 12_000;
256
+ while (Date.now() < deadline) {
257
+ const live = await fetch(`http://127.0.0.1:${browser_config_1.AGENT_CDP_PORT}/json/version`, {
258
+ signal: AbortSignal.timeout(1500),
259
+ }).then(r => r.ok).catch(() => false);
260
+ if (live)
261
+ break;
262
+ await new Promise(r => setTimeout(r, 400));
263
+ }
264
+ // We spawned a dedicated --user-data-dir instance — the agent owns it.
265
+ const ok = await this.connect(browser_config_1.AGENT_CDP_PORT);
266
+ if (ok)
267
+ await this.labelAgentBrowser();
268
+ return ok;
269
+ }
270
+ /**
271
+ * Stamp the dedicated instance's blank tab so the WINDOW is identifiable in
272
+ * the taskbar / Alt-Tab as the agent's, not a second personal browser. The
273
+ * 2026-06-11 incident started exactly this way: an unlabeled agent instance
274
+ * lingered, the user adopted it for music, and a later attach drove their tab.
275
+ */
276
+ async labelAgentBrowser() {
277
+ try {
278
+ const pg = this.activePage;
279
+ if (!pg || !pg.url().startsWith('about:'))
280
+ return;
281
+ await pg.evaluate(() => {
282
+ document.title = 'ClawdCursor — agent browser';
283
+ document.body.innerHTML =
284
+ '<div style="font-family:system-ui;padding:40px;color:#444">' +
285
+ '<h2 style="margin:0 0 8px">🐾 ClawdCursor — agent browser</h2>' +
286
+ '<p>This window belongs to the clawdcursor agent (dedicated profile, port-isolated from your own browser).<br>' +
287
+ 'You can minimize it; closing it just makes the agent launch a fresh one next time.</p></div>';
288
+ });
289
+ }
290
+ catch { /* cosmetic only — never fail the connection over it */ }
291
+ }
292
+ /** Navigate the active page to a URL (waits for DOM content to load). */
293
+ async navigate(url) {
294
+ // ATTACHED mode = someone else's browser (likely the user's session). The
295
+ // agent must not commandeer whatever tab the user last touched — claim ONE
296
+ // tab of our own on first navigation and stay in it. Mechanical, so it
297
+ // holds even when the model ignores the disclosure text.
298
+ if (this.connectionMode === 'attached' && this.browser) {
299
+ try {
300
+ if (!this.agentTab || this.agentTab.isClosed()) {
301
+ const ctx = this.browser.contexts()[0];
302
+ this.agentTab = await ctx.newPage();
303
+ console.log(' 🗂 CDPDriver: attached mode — opened a dedicated agent tab (user tabs untouched)');
304
+ }
305
+ this.activePage = this.agentTab;
306
+ }
307
+ catch { /* tab creation failed — fall back to the current page */ }
308
+ }
309
+ const pg = this.requirePage();
310
+ try {
311
+ await pg.goto(url, { waitUntil: 'domcontentloaded', timeout: 20_000 });
312
+ return { success: true, method: 'goto', value: pg.url() };
313
+ }
314
+ catch (err) {
315
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
316
+ }
317
+ }
318
+ /**
319
+ * Attach to an existing Playwright Page object.
320
+ * Use this when an outer caller already holds a CDP connection — avoid duplicate CDP connections.
321
+ */
322
+ attachToPage(page) {
323
+ this.activePage = page;
324
+ this.connected = true;
325
+ this.ownsBrowser = false;
326
+ // An externally supplied page is by definition not our dedicated instance.
327
+ this.connectionMode = 'attached';
328
+ console.log(` 🔌 CDPDriver: attached to existing page`);
329
+ }
330
+ /**
331
+ * Switch to a tab whose URL contains the given substring.
332
+ * Useful after navigation when the agent needs to find a specific page.
333
+ */
334
+ async switchToTabByUrl(urlSubstring) {
335
+ if (!this.browser)
336
+ return false;
337
+ try {
338
+ const contexts = this.browser.contexts();
339
+ for (const ctx of contexts) {
340
+ for (const page of ctx.pages()) {
341
+ const pageUrl = page.url().toLowerCase();
342
+ if (pageUrl.includes(urlSubstring.toLowerCase())) {
343
+ this.activePage = page;
344
+ this.cursorInjected = false; // Reset — new page doesn't have our overlay
345
+ await page.bringToFront().catch(() => { });
346
+ const title = await page.title().catch(() => '(unknown)');
347
+ console.log(` 🔌 CDPDriver: switched to tab "${title}" at ${page.url()}`);
348
+ return true;
349
+ }
350
+ }
351
+ }
352
+ return false;
353
+ }
354
+ catch (err) {
355
+ console.debug(`[CDPDriver] switchToTabByUrl failed: ${err}`);
356
+ return false;
357
+ }
358
+ }
359
+ /** Check if we're connected and the page is still alive */
360
+ async isConnected() {
361
+ if (!this.connected || !this.activePage)
362
+ return false;
363
+ try {
364
+ await this.activePage.title(); // Health check
365
+ return true;
366
+ }
367
+ catch {
368
+ this.connected = false;
369
+ return false;
370
+ }
371
+ }
372
+ /**
373
+ * Switch to a different tab by URL substring or title substring.
374
+ */
375
+ async switchTab(urlOrTitleSubstring) {
376
+ if (!this.browser)
377
+ return false;
378
+ const lower = urlOrTitleSubstring.toLowerCase();
379
+ for (const context of this.browser.contexts()) {
380
+ for (const page of context.pages()) {
381
+ try {
382
+ const url = page.url().toLowerCase();
383
+ const title = (await page.title()).toLowerCase();
384
+ if (url.includes(lower) || title.includes(lower)) {
385
+ this.activePage = page;
386
+ this.cursorInjected = false; // Reset — new page doesn't have our overlay
387
+ await page.bringToFront();
388
+ console.log(` 🔌 CDPDriver: switched to tab "${await page.title()}"`);
389
+ return true;
390
+ }
391
+ }
392
+ catch (err) {
393
+ console.debug(`[CDPDriver] Tab switch attempt failed: ${err}`);
394
+ continue;
395
+ }
396
+ }
397
+ }
398
+ return false;
399
+ }
400
+ // ════════════════════════════════════════════════════════════════════
401
+ // ELEMENT QUERIES
402
+ // ════════════════════════════════════════════════════════════════════
403
+ /**
404
+ * Query elements by CSS selector.
405
+ * Returns info about all matching elements.
406
+ */
407
+ async querySelectorAll(selector, maxResults = 20) {
408
+ const pg = this.requirePage();
409
+ return pg.evaluate((args) => {
410
+ const elements = document.querySelectorAll(args.sel);
411
+ const infos = [];
412
+ for (let i = 0; i < Math.min(elements.length, args.max); i++) {
413
+ const el = elements[i];
414
+ const rect = el.getBoundingClientRect();
415
+ // Generate a unique CSS selector inside evaluate where we have DOM access
416
+ let selector;
417
+ if (el.id) {
418
+ selector = `#${el.id}`;
419
+ }
420
+ else {
421
+ const testId = el.getAttribute('data-testid');
422
+ if (testId) {
423
+ selector = `[data-testid="${testId}"]`;
424
+ }
425
+ else {
426
+ // Calculate correct nth-of-type among siblings
427
+ const parent = el.parentElement;
428
+ if (parent) {
429
+ let typeIndex = 0;
430
+ for (const sibling of parent.children) {
431
+ if (sibling.tagName === el.tagName) {
432
+ typeIndex++;
433
+ if (sibling === el)
434
+ break;
435
+ }
436
+ }
437
+ selector = `${el.tagName.toLowerCase()}:nth-of-type(${typeIndex})`;
438
+ }
439
+ else {
440
+ selector = el.tagName.toLowerCase();
441
+ }
442
+ }
443
+ }
444
+ infos.push({
445
+ selector,
446
+ tagName: el.tagName.toLowerCase(),
447
+ text: (el.textContent || '').trim().substring(0, 100),
448
+ id: el.id || '',
449
+ className: typeof el.className === 'string' ? el.className : '',
450
+ role: el.getAttribute('role') || '',
451
+ ariaLabel: el.getAttribute('aria-label') || '',
452
+ type: el.type || '',
453
+ placeholder: el.placeholder || '',
454
+ name: el.getAttribute('name') || '',
455
+ bounds: {
456
+ x: Math.round(rect.x),
457
+ y: Math.round(rect.y),
458
+ width: Math.round(rect.width),
459
+ height: Math.round(rect.height),
460
+ },
461
+ isVisible: rect.width > 0 && rect.height > 0 && getComputedStyle(el).visibility !== 'hidden',
462
+ isEnabled: !el.disabled,
463
+ });
464
+ }
465
+ return infos;
466
+ }, { sel: selector, max: maxResults });
467
+ }
468
+ /**
469
+ * Find a single element by CSS selector.
470
+ */
471
+ async querySelector(selector) {
472
+ const results = await this.querySelectorAll(selector, 1);
473
+ return results.length > 0 ? results[0] : null;
474
+ }
475
+ /**
476
+ * Find elements by their visible text content.
477
+ * Useful for clicking buttons like "Compose", "Send", "Save Draft".
478
+ *
479
+ * @param text Text to search for (case-insensitive, partial match)
480
+ * @param tagFilter Optional tag name filter (e.g., 'button', 'a', 'div')
481
+ */
482
+ async findByText(text, tagFilter) {
483
+ const pg = this.requirePage();
484
+ const results = await pg.evaluate((args) => {
485
+ const lower = args.searchText.toLowerCase();
486
+ const candidates = args.tag
487
+ ? document.querySelectorAll(args.tag)
488
+ : document.querySelectorAll('button, a, [role="button"], [role="link"], [role="menuitem"], input[type="submit"], input[type="button"]');
489
+ const matches = [];
490
+ for (const el of candidates) {
491
+ const htmlEl = el;
492
+ const elText = (htmlEl.textContent || '').trim().toLowerCase();
493
+ const ariaLabel = (htmlEl.getAttribute('aria-label') || '').toLowerCase();
494
+ const title = (htmlEl.getAttribute('title') || '').toLowerCase();
495
+ if (elText.includes(lower) || ariaLabel.includes(lower) || title.includes(lower)) {
496
+ const rect = htmlEl.getBoundingClientRect();
497
+ if (rect.width > 0 && rect.height > 0) {
498
+ matches.push({
499
+ tagName: htmlEl.tagName.toLowerCase(),
500
+ text: (htmlEl.textContent || '').trim().substring(0, 100),
501
+ id: htmlEl.id || '',
502
+ className: typeof htmlEl.className === 'string' ? htmlEl.className : '',
503
+ role: htmlEl.getAttribute('role') || '',
504
+ ariaLabel: htmlEl.getAttribute('aria-label') || '',
505
+ type: htmlEl.type || '',
506
+ placeholder: htmlEl.placeholder || '',
507
+ name: htmlEl.getAttribute('name') || '',
508
+ bounds: {
509
+ x: Math.round(rect.x),
510
+ y: Math.round(rect.y),
511
+ width: Math.round(rect.width),
512
+ height: Math.round(rect.height),
513
+ },
514
+ isVisible: true,
515
+ isEnabled: !htmlEl.disabled,
516
+ selector: htmlEl.id ? `#${htmlEl.id}` : '',
517
+ });
518
+ }
519
+ }
520
+ }
521
+ return matches;
522
+ }, { searchText: text, tag: tagFilter || '' });
523
+ return results;
524
+ }
525
+ /**
526
+ * Find form fields by their associated label text.
527
+ * Handles both <label for="..."> and label wrapping the input.
528
+ */
529
+ async findFieldByLabel(labelText) {
530
+ const pg = this.requirePage();
531
+ const result = await pg.evaluate((searchLabel) => {
532
+ const lower = searchLabel.toLowerCase();
533
+ // Strategy 1: <label for="inputId">Label Text</label>
534
+ const labels = document.querySelectorAll('label');
535
+ for (const label of labels) {
536
+ if ((label.textContent || '').trim().toLowerCase().includes(lower)) {
537
+ const forId = label.getAttribute('for');
538
+ if (forId) {
539
+ const input = document.getElementById(forId);
540
+ if (input) {
541
+ const rect = input.getBoundingClientRect();
542
+ return {
543
+ selector: `#${input.id}`,
544
+ tagName: input.tagName.toLowerCase(),
545
+ text: '',
546
+ id: input.id || '',
547
+ className: typeof input.className === 'string' ? input.className : '',
548
+ role: input.getAttribute('role') || '',
549
+ ariaLabel: input.getAttribute('aria-label') || '',
550
+ type: input.type || '',
551
+ placeholder: input.placeholder || '',
552
+ name: input.getAttribute('name') || '',
553
+ bounds: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
554
+ isVisible: rect.width > 0 && rect.height > 0,
555
+ isEnabled: !input.disabled,
556
+ };
557
+ }
558
+ }
559
+ // Strategy 2: label wraps input
560
+ const input = label.querySelector('input, textarea, select');
561
+ if (input) {
562
+ const htmlInput = input;
563
+ const rect = input.getBoundingClientRect();
564
+ return {
565
+ selector: htmlInput.id ? `#${htmlInput.id}` : '',
566
+ tagName: input.tagName.toLowerCase(),
567
+ text: '',
568
+ id: htmlInput.id || '',
569
+ className: typeof htmlInput.className === 'string' ? htmlInput.className : '',
570
+ role: input.getAttribute('role') || '',
571
+ ariaLabel: input.getAttribute('aria-label') || '',
572
+ type: input.type || '',
573
+ placeholder: input.placeholder || '',
574
+ name: input.getAttribute('name') || '',
575
+ bounds: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
576
+ isVisible: rect.width > 0 && rect.height > 0,
577
+ isEnabled: !input.disabled,
578
+ };
579
+ }
580
+ }
581
+ }
582
+ // Strategy 3: aria-label match on input/textarea
583
+ const inputs = document.querySelectorAll('input, textarea, select, [contenteditable="true"]');
584
+ for (const input of inputs) {
585
+ const ariaLabel = (input.getAttribute('aria-label') || '').toLowerCase();
586
+ const placeholder = (input.placeholder || '').toLowerCase();
587
+ const name = (input.getAttribute('name') || '').toLowerCase();
588
+ if (ariaLabel.includes(lower) || placeholder.includes(lower) || name.includes(lower)) {
589
+ const htmlInput = input;
590
+ const rect = input.getBoundingClientRect();
591
+ if (rect.width > 0 && rect.height > 0) {
592
+ return {
593
+ selector: htmlInput.id ? `#${htmlInput.id}` : '',
594
+ tagName: input.tagName.toLowerCase(),
595
+ text: '',
596
+ id: htmlInput.id || '',
597
+ className: typeof htmlInput.className === 'string' ? htmlInput.className : '',
598
+ role: input.getAttribute('role') || '',
599
+ ariaLabel: input.getAttribute('aria-label') || '',
600
+ type: input.type || '',
601
+ placeholder: input.placeholder || '',
602
+ name: input.getAttribute('name') || '',
603
+ bounds: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
604
+ isVisible: true,
605
+ isEnabled: !input.disabled,
606
+ };
607
+ }
608
+ }
609
+ }
610
+ return null;
611
+ }, labelText);
612
+ return result;
613
+ }
614
+ // ════════════════════════════════════════════════════════════════════
615
+ // ELEMENT INTERACTION
616
+ // ════════════════════════════════════════════════════════════════════
617
+ /**
618
+ * Click an element by CSS selector.
619
+ * Uses JS dispatchEvent (reliable on background CDP tabs with 0x0 viewport).
620
+ * Falls back to Playwright click if JS dispatch doesn't work.
621
+ */
622
+ async click(selector) {
623
+ const pg = this.requirePage();
624
+ try {
625
+ // Primary: JS-based click (works on background tabs)
626
+ const clicked = await pg.evaluate((sel) => {
627
+ const el = document.querySelector(sel);
628
+ if (!el)
629
+ return false;
630
+ el.click();
631
+ return true;
632
+ }, selector);
633
+ if (clicked)
634
+ return { success: true, method: 'js.click' };
635
+ // Fallback: Playwright click with force
636
+ await this.moveCursorToSelector(selector);
637
+ await pg.click(selector, { timeout: 5000, force: true });
638
+ return { success: true, method: 'playwright.click' };
639
+ }
640
+ catch (err) {
641
+ return {
642
+ success: false,
643
+ error: `Click failed on "${selector}": ${err instanceof Error ? err.message : String(err)}`,
644
+ };
645
+ }
646
+ }
647
+ /**
648
+ * Click an element by its visible text content.
649
+ *
650
+ * This is the most natural way to interact with web UIs:
651
+ * await cdp.clickByText('Compose');
652
+ * await cdp.clickByText('Send');
653
+ * await cdp.clickByText('Discard');
654
+ */
655
+ async clickByText(text) {
656
+ const pg = this.requirePage();
657
+ try {
658
+ // Primary: JS-based click by text content (works on background tabs)
659
+ const jsClicked = await pg.evaluate((searchText) => {
660
+ const lower = searchText.toLowerCase();
661
+ // Deduplicate: elements matching multiple selectors should only appear once
662
+ const seen = new Set();
663
+ const candidates = [];
664
+ for (const sel of ['button, [role="button"]', 'a[href]', '[role="link"], [role="menuitem"], [role="tab"]']) {
665
+ for (const el of document.querySelectorAll(sel)) {
666
+ if (!seen.has(el)) {
667
+ seen.add(el);
668
+ candidates.push(el);
669
+ }
670
+ }
671
+ }
672
+ for (const htmlEl of candidates) {
673
+ const elText = (htmlEl.textContent || '').trim().toLowerCase();
674
+ const ariaLabel = (htmlEl.getAttribute('aria-label') || '').toLowerCase();
675
+ if (elText.includes(lower) || ariaLabel.includes(lower)) {
676
+ // Visibility check — skip hidden elements
677
+ const style = getComputedStyle(htmlEl);
678
+ if (style.display === 'none' || style.visibility === 'hidden')
679
+ continue;
680
+ htmlEl.click();
681
+ return true;
682
+ }
683
+ }
684
+ return false;
685
+ }, text);
686
+ if (jsClicked)
687
+ return { success: true, method: 'js.clickByText' };
688
+ // Fallback: Playwright locators with force
689
+ const locators = [
690
+ pg.getByRole('button', { name: text }),
691
+ pg.getByRole('link', { name: text }),
692
+ pg.getByRole('menuitem', { name: text }),
693
+ pg.getByText(text, { exact: false }),
694
+ ];
695
+ for (const locator of locators) {
696
+ try {
697
+ const count = await locator.count();
698
+ if (count > 0) {
699
+ await locator.first().click({ timeout: 3000, force: true });
700
+ return { success: true, method: 'playwright.getByText' };
701
+ }
702
+ }
703
+ catch {
704
+ continue;
705
+ }
706
+ }
707
+ return { success: false, error: `No clickable element found with text "${text}"` };
708
+ }
709
+ catch (err) {
710
+ return {
711
+ success: false,
712
+ error: `clickByText("${text}") failed: ${err instanceof Error ? err.message : String(err)}`,
713
+ };
714
+ }
715
+ }
716
+ /**
717
+ * Type text into a field identified by CSS selector.
718
+ *
719
+ * Clears the field first (select all + delete), then types.
720
+ * For content-editable divs (like Gmail compose body), uses fill() which
721
+ * works better than type() for non-input elements.
722
+ */
723
+ async typeInField(selector, text) {
724
+ const pg = this.requirePage();
725
+ try {
726
+ await this.moveCursorToSelector(selector);
727
+ // Try fill() first — works for inputs, textareas, and [contenteditable]
728
+ await pg.fill(selector, text, { timeout: 5000, force: true });
729
+ return { success: true, method: 'playwright.fill' };
730
+ }
731
+ catch {
732
+ // Fall back to click + clear + type for stubborn elements
733
+ try {
734
+ await pg.click(selector, { timeout: 3000, force: true });
735
+ await pg.keyboard.press('Control+a');
736
+ await pg.keyboard.type(text, { delay: 20 });
737
+ return { success: true, method: 'playwright.type' };
738
+ }
739
+ catch (err) {
740
+ return {
741
+ success: false,
742
+ error: `Type failed on "${selector}": ${err instanceof Error ? err.message : String(err)}`,
743
+ };
744
+ }
745
+ }
746
+ }
747
+ /**
748
+ * Type into a field found by its label or aria-label.
749
+ *
750
+ * This is the most user-friendly way to fill forms:
751
+ * await cdp.typeByLabel('To', 'user@example.com');
752
+ * await cdp.typeByLabel('Subject', 'Meeting notes');
753
+ */
754
+ async typeByLabel(label, text) {
755
+ const pg = this.requirePage();
756
+ try {
757
+ // Playwright's getByLabel() handles <label>, aria-label, aria-labelledby
758
+ const locator = pg.getByLabel(label);
759
+ const count = await locator.count();
760
+ if (count === 0) {
761
+ // Fall back to our custom label finder
762
+ const field = await this.findFieldByLabel(label);
763
+ if (field && field.selector) {
764
+ return this.typeInField(field.selector, text);
765
+ }
766
+ return { success: false, error: `No field found with label "${label}"` };
767
+ }
768
+ await locator.first().fill(text, { timeout: 5000, force: true });
769
+ return { success: true, method: 'playwright.getByLabel' };
770
+ }
771
+ catch (err) {
772
+ return {
773
+ success: false,
774
+ error: `typeByLabel("${label}") failed: ${err instanceof Error ? err.message : String(err)}`,
775
+ };
776
+ }
777
+ }
778
+ /**
779
+ * Fill a form by mapping label text → value.
780
+ *
781
+ * Example (Gmail compose):
782
+ * await cdp.fillFormByLabels({
783
+ * 'To': 'friend@gmail.com',
784
+ * 'Subject': 'Hello!',
785
+ * 'Message Body': 'How are you doing?'
786
+ * });
787
+ */
788
+ async fillFormByLabels(fields) {
789
+ const results = {};
790
+ let allSuccess = true;
791
+ for (const [label, value] of Object.entries(fields)) {
792
+ const result = await this.typeByLabel(label, value);
793
+ results[label] = result;
794
+ if (!result.success) {
795
+ allSuccess = false;
796
+ console.log(` ⚠️ CDPDriver: failed to fill "${label}": ${result.error}`);
797
+ }
798
+ }
799
+ return { success: allSuccess, results };
800
+ }
801
+ /**
802
+ * Focus an element by CSS selector.
803
+ */
804
+ async focus(selector) {
805
+ const pg = this.requirePage();
806
+ try {
807
+ await this.moveCursorToSelector(selector);
808
+ await pg.focus(selector, { timeout: 3000 });
809
+ return { success: true, method: 'playwright.focus' };
810
+ }
811
+ catch (err) {
812
+ return {
813
+ success: false,
814
+ error: `Focus failed on "${selector}": ${err instanceof Error ? err.message : String(err)}`,
815
+ };
816
+ }
817
+ }
818
+ /**
819
+ * Select an option in a <select> dropdown.
820
+ *
821
+ * @param selector CSS selector for the <select> element
822
+ * @param valueOrLabel Option value or visible text to select
823
+ */
824
+ async selectOption(selector, valueOrLabel) {
825
+ const pg = this.requirePage();
826
+ try {
827
+ // Try by value first, then by label
828
+ const selected = await pg.selectOption(selector, valueOrLabel, { timeout: 3000 });
829
+ if (selected.length === 0) {
830
+ // Try by label
831
+ await pg.selectOption(selector, { label: valueOrLabel }, { timeout: 3000 });
832
+ }
833
+ return { success: true, method: 'playwright.selectOption', value: valueOrLabel };
834
+ }
835
+ catch (err) {
836
+ return {
837
+ success: false,
838
+ error: `Select failed on "${selector}": ${err instanceof Error ? err.message : String(err)}`,
839
+ };
840
+ }
841
+ }
842
+ /**
843
+ * Press a keyboard key (while the page has focus).
844
+ */
845
+ async pressKey(key) {
846
+ const pg = this.requirePage();
847
+ try {
848
+ await pg.keyboard.press(key);
849
+ return { success: true, method: 'playwright.keyboard.press' };
850
+ }
851
+ catch (err) {
852
+ return {
853
+ success: false,
854
+ error: `Key press "${key}" failed: ${err instanceof Error ? err.message : String(err)}`,
855
+ };
856
+ }
857
+ }
858
+ // ════════════════════════════════════════════════════════════════════
859
+ // PAGE STATE
860
+ // ════════════════════════════════════════════════════════════════════
861
+ /** Get current page URL */
862
+ async getUrl() {
863
+ if (!this.activePage)
864
+ return null;
865
+ try {
866
+ return this.activePage.url();
867
+ }
868
+ catch {
869
+ return null;
870
+ }
871
+ }
872
+ /** Get current page title */
873
+ async getTitle() {
874
+ if (!this.activePage)
875
+ return null;
876
+ try {
877
+ return await this.activePage.title();
878
+ }
879
+ catch {
880
+ return null;
881
+ }
882
+ }
883
+ /**
884
+ * Get a text summary of the visible, interactive elements on the page.
885
+ * Useful for building context for an LLM without screenshots.
886
+ */
887
+ async getInteractiveElements() {
888
+ const pg = this.requirePage();
889
+ return pg.evaluate(() => {
890
+ const interactiveSelectors = [
891
+ 'a[href]',
892
+ 'button',
893
+ 'input',
894
+ 'textarea',
895
+ 'select',
896
+ '[role="button"]',
897
+ '[role="link"]',
898
+ '[role="menuitem"]',
899
+ '[role="tab"]',
900
+ '[role="checkbox"]',
901
+ '[role="radio"]',
902
+ '[role="textbox"]',
903
+ '[role="combobox"]',
904
+ '[contenteditable="true"]',
905
+ '[tabindex]:not([tabindex="-1"])',
906
+ ];
907
+ const sel = interactiveSelectors.join(', ');
908
+ const elements = document.querySelectorAll(sel);
909
+ const results = [];
910
+ // When viewport is 0x0 (background tab via CDP), skip bounds filtering
911
+ const hasViewport = window.innerWidth > 0 && window.innerHeight > 0;
912
+ for (const el of elements) {
913
+ const htmlEl = el;
914
+ const rect = htmlEl.getBoundingClientRect();
915
+ // Always skip zero-size elements (display:none, collapsed, etc.)
916
+ if (rect.width <= 0 || rect.height <= 0)
917
+ continue;
918
+ // Only check viewport bounds when viewport is valid (not background tab)
919
+ if (hasViewport) {
920
+ if (rect.bottom < 0 || rect.top > window.innerHeight)
921
+ continue;
922
+ if (rect.right < 0 || rect.left > window.innerWidth)
923
+ continue;
924
+ }
925
+ const style = getComputedStyle(htmlEl);
926
+ if (style.visibility === 'hidden' || style.display === 'none')
927
+ continue;
928
+ results.push({
929
+ selector: htmlEl.id ? `#${htmlEl.id}` : '',
930
+ tagName: htmlEl.tagName.toLowerCase(),
931
+ text: (htmlEl.textContent || '').trim().substring(0, 60),
932
+ id: htmlEl.id || '',
933
+ className: typeof htmlEl.className === 'string' ? htmlEl.className.substring(0, 80) : '',
934
+ role: htmlEl.getAttribute('role') || '',
935
+ ariaLabel: htmlEl.getAttribute('aria-label') || '',
936
+ type: htmlEl.type || '',
937
+ placeholder: htmlEl.placeholder || '',
938
+ name: htmlEl.getAttribute('name') || '',
939
+ bounds: {
940
+ x: Math.round(rect.x),
941
+ y: Math.round(rect.y),
942
+ width: Math.round(rect.width),
943
+ height: Math.round(rect.height),
944
+ },
945
+ isVisible: true,
946
+ isEnabled: !htmlEl.disabled,
947
+ });
948
+ if (results.length >= 50)
949
+ break; // Cap results
950
+ }
951
+ return results;
952
+ });
953
+ }
954
+ /**
955
+ * Get a text representation of the page's interactive elements.
956
+ * Formatted for LLM consumption — compact, informative.
957
+ */
958
+ async getPageContext() {
959
+ try {
960
+ const url = await this.getUrl();
961
+ const title = await this.getTitle();
962
+ const elements = await this.getInteractiveElements();
963
+ let context = `PAGE: "${title}" at ${url}\n\n`;
964
+ // Prioritize inputs/buttons/selects over links to keep context manageable
965
+ const inputs = elements.filter(e => ['input', 'textarea', 'select'].includes(e.tagName));
966
+ const buttons = elements.filter(e => e.tagName === 'button' || e.role === 'button');
967
+ const links = elements.filter(e => e.tagName === 'a' && e.role !== 'button');
968
+ const other = elements.filter(e => !inputs.includes(e) && !buttons.includes(e) && !links.includes(e));
969
+ // Always show all inputs, buttons, and other elements; limit links to 40
970
+ const shown = [...inputs, ...buttons, ...other, ...links.slice(0, 40)];
971
+ const hiddenLinks = links.length > 40 ? links.length - 40 : 0;
972
+ context += `INTERACTIVE ELEMENTS (${shown.length}${hiddenLinks > 0 ? `, +${hiddenLinks} more links` : ''}):\n`;
973
+ for (const el of shown) {
974
+ const label = el.ariaLabel || el.text || el.placeholder || el.name || el.id || '(unnamed)';
975
+ const typeInfo = el.type ? ` type="${el.type}"` : '';
976
+ const roleInfo = el.role ? ` role="${el.role}"` : '';
977
+ context += ` [${el.tagName}${typeInfo}${roleInfo}] "${label}"`;
978
+ if (el.id)
979
+ context += ` #${el.id}`;
980
+ context += ` @${el.bounds.x},${el.bounds.y}\n`;
981
+ }
982
+ return context;
983
+ }
984
+ catch (err) {
985
+ return `(CDPDriver page context unavailable: ${err})`;
986
+ }
987
+ }
988
+ // ════════════════════════════════════════════════════════════════════
989
+ // SCRIPT EVALUATION
990
+ // ════════════════════════════════════════════════════════════════════
991
+ /**
992
+ * Read text content from a DOM element (safe, parameterized — no CSS injection risk).
993
+ * @param selector CSS selector for the target element (default: 'body')
994
+ * @param maxLength Maximum characters to return (default: 3000)
995
+ */
996
+ async readText(selector = 'body', maxLength = 3000) {
997
+ const pg = this.requirePage();
998
+ return pg.evaluate((args) => {
999
+ const el = document.querySelector(args.sel);
1000
+ if (!el)
1001
+ return `[element not found: ${args.sel}]`;
1002
+ const raw = el.innerText || el.textContent || '';
1003
+ return raw.replace(/\n{3,}/g, '\n\n').trim().substring(0, args.max);
1004
+ }, { sel: selector, max: maxLength });
1005
+ }
1006
+ /**
1007
+ * Read the current value of a form field (safe, parameterized — no CSS injection risk).
1008
+ * @param selector CSS selector for the input/textarea element
1009
+ */
1010
+ async readFieldValue(selector) {
1011
+ const pg = this.requirePage();
1012
+ return pg.evaluate((sel) => {
1013
+ const el = document.querySelector(sel);
1014
+ if (!el)
1015
+ return '';
1016
+ return (el.value ?? el.textContent ?? '').substring(0, 60);
1017
+ }, selector);
1018
+ }
1019
+ /**
1020
+ * Evaluate arbitrary JavaScript in the page context.
1021
+ * Use this for custom interactions not covered by the standard methods.
1022
+ *
1023
+ * @param script JavaScript code to evaluate
1024
+ * @returns The return value of the script, serialized
1025
+ */
1026
+ async evaluate(script) {
1027
+ const pg = this.requirePage();
1028
+ return pg.evaluate(script);
1029
+ }
1030
+ /**
1031
+ * Wait for a selector to appear on the page.
1032
+ */
1033
+ async waitForSelector(selector, timeoutMs = 10_000) {
1034
+ const pg = this.requirePage();
1035
+ try {
1036
+ await pg.waitForSelector(selector, {
1037
+ timeout: timeoutMs,
1038
+ state: 'visible',
1039
+ });
1040
+ return { success: true };
1041
+ }
1042
+ catch (err) {
1043
+ return {
1044
+ success: false,
1045
+ error: `Timeout waiting for "${selector}": ${err instanceof Error ? err.message : String(err)}`,
1046
+ };
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Wait for navigation to complete (e.g., after clicking a link).
1051
+ */
1052
+ async waitForNavigation(timeoutMs = 15_000) {
1053
+ const pg = this.requirePage();
1054
+ try {
1055
+ await pg.waitForLoadState('domcontentloaded', { timeout: timeoutMs });
1056
+ return { success: true, value: pg.url() };
1057
+ }
1058
+ catch (err) {
1059
+ return {
1060
+ success: false,
1061
+ error: `Navigation timeout: ${err instanceof Error ? err.message : String(err)}`,
1062
+ };
1063
+ }
1064
+ }
1065
+ // ════════════════════════════════════════════════════════════════════
1066
+ // CLEANUP
1067
+ // ════════════════════════════════════════════════════════════════════
1068
+ /**
1069
+ * Disconnect from the browser.
1070
+ * Only closes the CDP connection if we own it — doesn't close the user's browser.
1071
+ */
1072
+ async disconnect() {
1073
+ if (this.ownsBrowser && this.browser) {
1074
+ try {
1075
+ await this.browser.close();
1076
+ }
1077
+ catch { /* Browser may already be closed */ }
1078
+ }
1079
+ this.browser = null;
1080
+ this.activePage = null;
1081
+ this.connected = false;
1082
+ this.cursorInjected = false;
1083
+ }
1084
+ /** Get the underlying Playwright Page (for advanced usage) */
1085
+ getPage() {
1086
+ return this.activePage;
1087
+ }
1088
+ // ════════════════════════════════════════════════════════════════════
1089
+ // PRIVATE
1090
+ // ════════════════════════════════════════════════════════════════════
1091
+ /** Ensure a virtual cursor overlay exists in the page */
1092
+ async ensureCursorOverlay() {
1093
+ const pg = this.requirePage();
1094
+ if (this.cursorInjected)
1095
+ return;
1096
+ try {
1097
+ await pg.evaluate(() => {
1098
+ if (document.getElementById('__clawd_cursor'))
1099
+ return;
1100
+ const cursor = document.createElement('div');
1101
+ cursor.id = '__clawd_cursor';
1102
+ cursor.style.position = 'fixed';
1103
+ cursor.style.width = '12px';
1104
+ cursor.style.height = '12px';
1105
+ cursor.style.borderRadius = '50%';
1106
+ cursor.style.background = '#ff6b6b';
1107
+ cursor.style.boxShadow = '0 0 6px rgba(0,0,0,0.4)';
1108
+ cursor.style.zIndex = '2147483647';
1109
+ cursor.style.pointerEvents = 'none';
1110
+ cursor.style.transform = 'translate(-50%, -50%)';
1111
+ cursor.style.left = '10px';
1112
+ cursor.style.top = '10px';
1113
+ const label = document.createElement('div');
1114
+ label.id = '__clawd_cursor_label';
1115
+ label.textContent = 'Clawd';
1116
+ label.style.position = 'fixed';
1117
+ label.style.fontSize = '10px';
1118
+ label.style.fontFamily = 'sans-serif';
1119
+ label.style.color = '#ff6b6b';
1120
+ label.style.zIndex = '2147483647';
1121
+ label.style.pointerEvents = 'none';
1122
+ label.style.transform = 'translate(6px, -18px)';
1123
+ label.style.left = '10px';
1124
+ label.style.top = '10px';
1125
+ document.body.appendChild(cursor);
1126
+ document.body.appendChild(label);
1127
+ });
1128
+ this.cursorInjected = true;
1129
+ }
1130
+ catch (err) {
1131
+ console.debug(`[CDPDriver] Cursor overlay injection failed: ${err}`);
1132
+ }
1133
+ }
1134
+ async moveVirtualCursor(x, y) {
1135
+ const pg = this.requirePage();
1136
+ try {
1137
+ await this.ensureCursorOverlay();
1138
+ await pg.evaluate(({ x, y }) => {
1139
+ const cursor = document.getElementById('__clawd_cursor');
1140
+ const label = document.getElementById('__clawd_cursor_label');
1141
+ if (cursor) {
1142
+ cursor.style.left = `${x}px`;
1143
+ cursor.style.top = `${y}px`;
1144
+ }
1145
+ if (label) {
1146
+ label.style.left = `${x}px`;
1147
+ label.style.top = `${y}px`;
1148
+ }
1149
+ }, { x, y });
1150
+ }
1151
+ catch (err) {
1152
+ console.debug(`[CDPDriver] moveVirtualCursor failed: ${err}`);
1153
+ }
1154
+ }
1155
+ async moveCursorToSelector(selector) {
1156
+ const pg = this.requirePage();
1157
+ try {
1158
+ const box = await pg.locator(selector).first().boundingBox();
1159
+ if (box) {
1160
+ await this.moveVirtualCursor(Math.round(box.x + box.width / 2), Math.round(box.y + box.height / 2));
1161
+ }
1162
+ }
1163
+ catch (err) {
1164
+ console.debug(`[CDPDriver] moveCursorToSelector failed: ${err}`);
1165
+ }
1166
+ }
1167
+ /**
1168
+ * Guard: ensure we have an active page and return it.
1169
+ * Throws if not connected.
1170
+ */
1171
+ requirePage() {
1172
+ if (!this.activePage || !this.connected) {
1173
+ throw new Error('CDPDriver: not connected. Call connect() first or attach a Page with attachToPage().');
1174
+ }
1175
+ return this.activePage;
1176
+ }
1177
+ }
1178
+ exports.CDPDriver = CDPDriver;
1179
+ //# sourceMappingURL=cdp-driver.js.map