surf-cli 2.7.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +208 -13
  2. package/dist/content/index.js +116 -0
  3. package/dist/content/index.js.map +1 -0
  4. package/dist/manifest.json +2 -11
  5. package/dist/options/options.js +3 -3
  6. package/dist/options/options.js.map +1 -1
  7. package/dist/service-worker/index.js +261 -61
  8. package/dist/service-worker/index.js.map +1 -1
  9. package/native/abort.cjs +65 -0
  10. package/native/ai-queue.cjs +64 -0
  11. package/native/aistudio-build.cjs +21 -13
  12. package/native/aistudio-client.cjs +40 -20
  13. package/native/browser-lock.cjs +169 -0
  14. package/native/chatgpt-client.cjs +63 -30
  15. package/native/cli.cjs +947 -460
  16. package/native/client-transport.cjs +168 -0
  17. package/native/config.cjs +2 -2
  18. package/native/do-executor.cjs +25 -51
  19. package/native/do-parser.cjs +12 -0
  20. package/native/doctor.cjs +633 -0
  21. package/native/endpoint.cjs +174 -0
  22. package/native/file-transfer.cjs +734 -0
  23. package/native/gemini-client.cjs +244 -88
  24. package/native/grok-client.cjs +321 -212
  25. package/native/host-helpers.cjs +88 -16
  26. package/native/host-sessions.cjs +283 -0
  27. package/native/host.cjs +811 -616
  28. package/native/listener.cjs +20 -0
  29. package/native/mcp-server.cjs +60 -62
  30. package/native/network-export.cjs +113 -0
  31. package/native/perplexity-client.cjs +46 -17
  32. package/native/remote-auth.cjs +279 -0
  33. package/native/remote-transport.cjs +337 -0
  34. package/native/request-pending.cjs +148 -0
  35. package/native/socket-path.cjs +46 -0
  36. package/package.json +11 -9
  37. package/scripts/install-native-host.cjs +184 -51
  38. package/scripts/uninstall-native-host.cjs +93 -15
  39. package/skills/README.md +11 -5
  40. package/skills/deep-x-research/SKILL.md +106 -0
  41. package/skills/surf/SKILL.md +77 -22
  42. package/dist/content/accessibility-tree.js +0 -11
  43. package/dist/content/accessibility-tree.js.map +0 -1
  44. package/dist/content/visual-indicator.js +0 -111
  45. package/dist/content/visual-indicator.js.map +0 -1
package/README.md CHANGED
@@ -43,7 +43,7 @@ Surf takes a different approach:
43
43
  |---------|------|-------|------------------|--------------|-------------|
44
44
  | Agent-agnostic | Yes | No (Manus only) | No (Claude only) | Partial | No (Claude skill) |
45
45
  | Zero config | Yes | No (subscription) | No (subscription) | No (MCP setup) | No (relay server) |
46
- | Local-only | Yes | No (cloud) | Partial | Yes | Partial |
46
+ | Self-hosted (local or Tailnet) | Yes | No (cloud) | Partial | Yes | Partial |
47
47
  | CLI interface | Yes | No | No | No | No |
48
48
  | Free | Yes | No | No | Yes | Yes |
49
49
  | AI via browser cookies | Yes | No | No | No | No |
@@ -76,10 +76,21 @@ surf install <extension-id> # Chrome (default)
76
76
  surf install <extension-id> --browser brave # Brave
77
77
  surf install <extension-id> --browser helium # Helium
78
78
  surf install <extension-id> --browser all # All supported browsers
79
+ surf install <extension-id> --target linux # WSLg/Linux browser from WSL2
79
80
  ```
80
81
 
81
82
  Supported: `chrome`, `chromium`, `brave`, `edge`, `arc`, `helium`
82
83
 
84
+ **WSL2 with Windows Chrome**
85
+ When you run `surf install <extension-id>` inside WSL2, Surf detects WSL2 and installs a Windows-side native messaging manifest for Windows Chrome/Brave/Edge by default. The generated Windows wrapper launches the WSL2 host with `wsl.exe`, so `surf` commands run inside WSL2 still connect to the WSL socket.
86
+
87
+ If you use a Linux browser inside WSLg instead, install with:
88
+ ```bash
89
+ surf install <extension-id> --target linux
90
+ ```
91
+
92
+ Restart Windows Chrome after installing. If the extension reports `Access to the specified native messaging host is forbidden`, rerun `surf install <extension-id>` from the same WSL distro and confirm the extension ID was copied from `chrome://extensions`.
93
+
83
94
  **Package Manager Installs (Nix, Homebrew, etc.)**
84
95
  If surf is installed via a package manager that stores binaries in non-standard locations, set these environment variables before running `surf install`:
85
96
  ```bash
@@ -94,8 +105,91 @@ See [Environment Variables](#environment-variables) for details.
94
105
  ```bash
95
106
  surf uninstall # Chrome only
96
107
  surf uninstall --all # All browsers + wrapper files
108
+ surf uninstall --target linux # Remove WSLg/Linux-browser config from WSL2
109
+ ```
110
+
111
+ ### Remote Surf over Tailscale
112
+
113
+ Remote Surf runs the browser and native host on one Tailnet machine while the CLI runs on another. The listener is available only while the browser extension's native-messaging connection is alive. Tailnet reachability is not authorization: every remote client also needs its own Surf credential.
114
+
115
+ On the browser host, authorize a client before installing the listener:
116
+
117
+ ```bash
118
+ surf remote authorize agent-macbook --output ~/agent-macbook.surf-credential.json
119
+ surf remote list
120
+ surf install <extension-id> --listen 100.101.102.103:4321
121
+ ```
122
+
123
+ `authorize` creates a mode-0600 credential containing the client's Ed25519 private identity and the pinned host identity. Move it to that client through an existing secure channel, then remove the generated copy from the host if it is no longer needed there. The host keeps only the client's public identity in `~/.surf/remote/remote-clients.json`.
124
+
125
+ From the authorized client:
126
+
127
+ ```bash
128
+ surf --remote 100.101.102.103:4321 \
129
+ --remote-credential ~/.config/surf/agent-macbook.json \
130
+ tab.list
131
+
132
+ # Environment equivalent
133
+ SURF_REMOTE=100.101.102.103:4321 \
134
+ SURF_REMOTE_CREDENTIAL=~/.config/surf/agent-macbook.json \
135
+ surf tab.list
136
+ ```
137
+
138
+ Surf performs mutual Ed25519 challenge-response with fresh nonces and checks authorization throughout the connection. A credential grants the same browser and host-file authority as a trusted local Surf user. Give each client its own credential, do not share it, and revoke it immediately if the client or file is lost:
139
+
140
+ ```bash
141
+ surf remote revoke agent-macbook
142
+ surf remote list
143
+ ```
144
+
145
+ `--remote <host>:<port>` takes precedence over `SURF_REMOTE`; `--remote-credential` takes precedence over `SURF_REMOTE_CREDENTIAL`. A selected remote endpoint overrides `SURF_SOCKET` and the default local socket. Local and remote requests share one bounded FIFO browser lease, so they cannot race each other. Disconnects and timeouts abort queued or in-flight work and hold the lease until request-owned cleanup drains or the hard deadline is reached. Browser side effects that already completed are not rolled back.
146
+
147
+ `surf install --listen` persists the explicit Tailnet address in the native-host wrapper. Re-run `surf install` without `--listen` to remove it. The address must be a Tailscale IPv4 or IPv6 address with a port; Surf does not bind every interface. Remote listeners currently require a POSIX browser host and are not supported by Windows native-host wrappers.
148
+
149
+ Keep Tailscale policy restrictions as defense in depth. For example:
150
+
151
+ ```json
152
+ {
153
+ "acls": [
154
+ {
155
+ "action": "accept",
156
+ "src": ["tag:surf-agent"],
157
+ "dst": ["tag:surf-browser:4321"]
158
+ }
159
+ ]
160
+ }
161
+ ```
162
+
163
+ Adapt tags and ports to your Tailnet. Surf authentication does not replace Tailnet policy, and Surf does not add a separate TLS or SSH tunnel.
164
+
165
+ **Operations and troubleshooting**
166
+
167
+ ```bash
168
+ tailscale status
169
+ tailscale ping 100.101.102.103
170
+ surf doctor --remote 100.101.102.103:4321 \
171
+ --remote-credential ~/.config/surf/agent-macbook.json
172
+ ```
173
+
174
+ Use `tailscale status` and `tailscale ping` to confirm reachability, then use `doctor` to verify endpoint selection and authentication.
175
+
176
+ **Remote filesystem and transfer semantics**
177
+
178
+ Unprefixed paths and `local:` paths refer to the client. Only `remote:/absolute/path` refers directly to the browser host. For example:
179
+
180
+ ```bash
181
+ surf --remote "$SURF_REMOTE" --remote-credential "$SURF_REMOTE_CREDENTIAL" \
182
+ upload --ref e5 --files ./client-file.pdf
183
+ surf --remote "$SURF_REMOTE" --remote-credential "$SURF_REMOTE_CREDENTIAL" \
184
+ screenshot --output local:./shot.png
185
+ surf --remote "$SURF_REMOTE" --remote-credential "$SURF_REMOTE_CREDENTIAL" \
186
+ network.export --output remote:/var/tmp/network.har --har
97
187
  ```
98
188
 
189
+ Client-local inputs are staged privately on the host and removed after the request. Client-local outputs are downloaded with size/hash verification and atomic destination replacement. `surf js --file` and `perf-audit --output` are handled by the client itself. `network.export` defaults to a generated client-local `.json`, `.jsonl`, or `.har` path. Gemini edits default to client-local `edited.png`. Successful remote actions transfer their automatic screenshot to a generated client-local path; `--auto-capture` on failure remains a separate screenshot and console diagnostic.
190
+
191
+ The remote single-file boundary supports one `upload` file, one ChatGPT attachment, or one Gemini attachment/edit input, plus one screenshot, network export, or Gemini image output. Transfers are limited to 256 MiB per file, 512 MiB and 32 files per connection, with 256 KiB decoded chunks. Remote `record`, `aistudio.build`, smoke screenshot directories, directory transfer, and multi-file inputs are intentionally rejected. A `remote:` path bypasses transfer and gives the trusted client direct authority over that absolute host path.
192
+
99
193
  ### Development Setup
100
194
 
101
195
  ```bash
@@ -111,6 +205,7 @@ npm run build
111
205
  ```bash
112
206
  surf <command> [args] [options]
113
207
  surf --help # Basic help
208
+ surf --llm-context # Compact reference for AI agents
114
209
  surf --help-full # All 50+ commands
115
210
  surf <command> --help # Command details
116
211
  surf --find <query> # Search commands
@@ -133,6 +228,7 @@ surf read --no-text # Accessibility tree only (no text)
133
228
  surf read --depth 3 # Limit tree depth (smaller output)
134
229
  surf read --compact # Remove empty structural elements
135
230
  surf read --depth 3 --compact # Both (60% smaller output)
231
+ surf read --max-bytes 2000 # Cap visible text on a UTF-8 byte boundary
136
232
  surf page.text # Raw text content only
137
233
  surf page.state # Modals, loading state, scroll position
138
234
  ```
@@ -171,6 +267,7 @@ surf frame.switch --selector "#checkout-frame" # Switch by CSS selector
171
267
  # Now all commands target the iframe
172
268
  surf read # Read iframe content
173
269
  surf click e5 # Click in iframe
270
+ surf type "4242" --into "#card-number"
174
271
  surf locate.role button --action click
175
272
 
176
273
  surf frame.main # Return to main page
@@ -182,10 +279,13 @@ surf frame.main # Return to main page
182
279
  surf click e5 # Click by element ref
183
280
  surf click --selector ".btn" # Click by CSS selector
184
281
  surf click 100 200 # Click by coordinates
185
- surf type "hello" --submit # Type and press Enter
186
- surf type "email@example.com" --ref e12 # Type into specific element
282
+ surf type "hello" --submit # Type at the current focus with CDP events
283
+ surf type "email@example.com" --ref e12 # Fill an element from page.read
284
+ surf type "hello" --into "#message" # Fill a selector in the active frame
187
285
  surf key Escape # Press key
188
- surf scroll.bottom # Scroll to bottom
286
+ surf scroll down 800 # Scroll down 800px
287
+ surf scroll bottom # Scroll to bottom
288
+ surf scroll.bottom # Dot command form also works
189
289
  ```
190
290
 
191
291
  ### Forms
@@ -221,6 +321,7 @@ surf screenshot --output /tmp/shot.png # Save to specific path
221
321
  surf screenshot --full --output /tmp/hd.png # Full resolution (skip resize)
222
322
  surf screenshot --annotate # With element labels
223
323
  surf screenshot --fullpage # Entire page
324
+ surf screenshot --full-page /tmp/full.png # Entire page, save to path
224
325
  surf screenshot --no-save # Return base64 + ID only (no file)
225
326
  surf snap # Alias for screenshot
226
327
  ```
@@ -236,6 +337,7 @@ surf tab.list
236
337
  surf tab.new "https://example.com"
237
338
  surf tab.switch 123
238
339
  surf tab.close 123
340
+ surf tab.move 123 --to-window 456 # Move one tab; use --ids 123,124 for several
239
341
  surf tab.name "dashboard" # Name current tab
240
342
  surf tab.switch "dashboard" # Switch by name
241
343
  surf tab.group --name "Work" --color blue
@@ -246,15 +348,19 @@ surf tab.group --name "Work" --color blue
246
348
  Keep using your browser while the agent works in a separate window:
247
349
 
248
350
  ```bash
249
- # Create isolated window for agent
351
+ # Create a separate window for agent work
250
352
  surf window.new "https://example.com"
251
353
  # Returns: Window 123456 (tab 789)
252
354
 
253
- # All subsequent commands target that window
355
+ # Target that window or its tab from later commands
254
356
  surf click e5 --window-id 123456
255
- surf read --window-id 123456
357
+ surf read --tab-id 789
256
358
  surf tab.new "https://other.com" --window-id 123456
257
359
 
360
+ # Name tabs when humans or agents need stable aliases
361
+ surf tab.name dashboard --tab-id 789
362
+ surf tab.switch dashboard
363
+
258
364
  # Or manage windows directly
259
365
  surf window.list # List all windows
260
366
  surf window.list --tabs # Include tab details
@@ -262,6 +368,19 @@ surf window.focus 123456 # Bring window to front
262
368
  surf window.close 123456 # Close window
263
369
  ```
264
370
 
371
+ `window.new`, `--window-id`, `--tab-id`, and named tabs are Surf's supported coordination tools for parallel workflows. They help agents avoid accidentally driving the same visible tab.
372
+
373
+ Surf also serializes non-streaming browser CLI requests per socket with a file-based lock, so two agents sharing the same native host wait instead of interleaving browser commands. Use `--no-lock` only when you intentionally want to bypass the guard for a command.
374
+
375
+ For hard isolation, run separate browser instances/profiles with separate Surf native hosts and socket paths, then point each shell at the matching socket. Each socket has its own independent lock:
376
+
377
+ ```bash
378
+ SURF_SOCKET=/tmp/surf-agent-a.sock surf tab.list
379
+ SURF_SOCKET=/tmp/surf-agent-b.sock surf tab.list
380
+ ```
381
+
382
+ Surf does not yet provide `session.new`, session IDs, or independent per-agent CDP sessions.
383
+
265
384
  ### Device Emulation
266
385
 
267
386
  Test responsive designs and mobile layouts:
@@ -283,6 +402,39 @@ surf emulate.touch --enabled false # Disable touch
283
402
 
284
403
  Available devices: iPhone 12-14 (Pro/Max), iPhone SE, iPad (Pro/Mini), Pixel 5-7 (Pro), Galaxy S21-S23, Galaxy Tab S7, Nest Hub (Max).
285
404
 
405
+ ### Animation Recording
406
+
407
+ Capture a screenshot burst and assemble it into an animated GIF with ImageMagick:
408
+
409
+ ```bash
410
+ surf record --duration 2000 --fps 10 --output /tmp/anim.gif
411
+ surf record --trigger "click:#btn" --output /tmp/click.gif
412
+ surf record --rect 0,200,1440,800 --output /tmp/region.gif
413
+ ```
414
+
415
+ `record` defaults to 2000ms at 10fps and writes to `/tmp/surf-record-*.gif` when no output is provided. `--duration` is capped at 10000ms and `--fps` is capped at 30. `--trigger` supports `click:<selector>`, `scroll:up|down|left|right|top|bottom`, and `scroll:<selector>` to scroll a container to the bottom before capture. `--rect` crops the GIF using `x,y,width,height`. ImageMagick must be available as `magick` or `convert`.
416
+
417
+ ### Animation Audit
418
+
419
+ Sample matching elements over time and return a bounded JSON timeline for agent inspection:
420
+
421
+ ```bash
422
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10
423
+ ```
424
+
425
+ The command captures rect, opacity, transform, visibility, display, and a short text snippet for up to 25 matching elements per sample. `--selector` is required. `--duration` defaults to 2000ms and is capped at 10000ms; `--fps` defaults to 10 and is capped at 30. This command returns JSON only and does not record GIF/video output.
426
+
427
+ ### Performance Audit
428
+
429
+ Capture layout shift, long animation frame, event timing, long task, and paint entries during a short window:
430
+
431
+ ```bash
432
+ surf perf-audit --duration 3000 --trigger "click:.cta" --output /tmp/perf.json
433
+ surf perf-audit --duration 1000 --json
434
+ ```
435
+
436
+ `perf-audit` defaults to 3000ms and is capped at 10000ms. `--trigger` supports the same `click:<selector>` and `scroll:<target>` forms as `record`. `--output` writes the JSON snapshot to disk.
437
+
286
438
  ### Performance Tracing
287
439
 
288
440
  Capture performance metrics and traces:
@@ -311,7 +463,7 @@ surf gemini "analyze" --file data.csv # Attach file
311
463
  surf gemini "a robot surfing" --generate-image /tmp/robot.png # Generate image
312
464
  surf gemini "add sunglasses" --edit-image photo.jpg --output out.jpg
313
465
  surf gemini "summarize" --youtube "https://youtube.com/..." # YouTube analysis
314
- surf gemini "hello" --model gemini-2.5-flash # Model selection
466
+ surf gemini "hello" --model gemini-3.5-flash # Model selection
315
467
 
316
468
  # Perplexity
317
469
  surf perplexity "what is quantum computing"
@@ -324,7 +476,7 @@ surf grok "what are the latest AI agent trends on X" # Search X posts
324
476
  surf grok "analyze @username recent activity" # Profile analysis
325
477
  surf grok "summarize this page" --with-page # Include page context
326
478
  surf grok "find viral AI posts" --deep-search # DeepSearch mode
327
- surf grok "quick question" --model fast # Models: auto, fast, expert, thinking
479
+ surf grok "quick question" --model fast # Models: auto, fast, expert, grok-4.20-beta
328
480
  surf grok --validate # Check UI and available models
329
481
  surf grok --validate --save-models # Save discovered models to settings
330
482
 
@@ -342,7 +494,7 @@ surf aistudio.build "game" --keep-open --timeout 600 # Keep tab open, 1
342
494
 
343
495
  Each AI tool uses your existing browser login - no API keys needed. Just be logged into the respective service in Chrome (chatgpt.com, gemini.google.com, perplexity.ai, x.com, or aistudio.google.com).
344
496
 
345
- **Grok troubleshooting:** If queries fail, run `surf grok --validate` to check if the UI structure changed. Use `--save-models` to update the model cache in `surf.json`. Default model is "thinking" (Grok 4.1 Thinking).
497
+ **Grok troubleshooting:** If queries fail, run `surf grok --validate` to check if the UI structure changed. Use `--save-models` to update the model cache in `surf.json`. Default model is `fast`.
346
498
 
347
499
  ### Waiting
348
500
 
@@ -357,8 +509,11 @@ surf wait.url "/dashboard" # Wait for URL pattern
357
509
 
358
510
  ```bash
359
511
  surf js "return document.title" # Execute JavaScript
512
+ surf record --duration 2000 --fps 10 --output /tmp/anim.gif # Animated GIF capture
513
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10 # JSON animation timeline
514
+ surf perf-audit --duration 3000 --output /tmp/perf.json # PerformanceObserver snapshot
360
515
  surf search "login" # Find text in page
361
- surf cookie.list # List cookies
516
+ surf cookie list # List cookies
362
517
  surf zoom 1.5 # Set zoom to 150%
363
518
  surf console # Read console messages
364
519
  surf network # Read network requests
@@ -529,6 +684,7 @@ surf workflow.validate ./my-workflow.json
529
684
  --window-id <id> # Target specific window (isolate agent from your browsing)
530
685
  --json # Output raw JSON
531
686
  --soft-fail # Warn instead of error (exit 0) on restricted pages
687
+ --no-lock # Bypass the per-socket browser request lock
532
688
  --no-screenshot # Skip auto-screenshot after actions
533
689
  --full # Full resolution screenshots (skip resize)
534
690
  --network-path <path> # Custom path for network logs (default: /tmp/surf, or SURF_NETWORK_PATH env)
@@ -538,12 +694,22 @@ surf workflow.validate ./my-workflow.json
538
694
 
539
695
  ```bash
540
696
  SURF_NETWORK_PATH # Path for network capture logs (default: /tmp/surf)
697
+ SURF_SOCKET # Socket path or named pipe (default: /tmp/surf.sock, Windows: //./pipe/surf)
698
+ SURF_REMOTE # Remote Surf endpoint as host:port (overrides SURF_SOCKET)
699
+ SURF_REMOTE_CREDENTIAL # Client Ed25519 credential for the selected remote endpoint
700
+ SURF_REMOTE_STATE_DIR # Host identity/authorization directory (default: ~/.surf/remote)
701
+ SURF_LISTEN # Native-host Tailnet bind address as <tailscale-ip>:<port>
541
702
  SURF_NODE_PATH # Path to node binary (for native host wrapper)
542
703
  SURF_HOST_PATH # Path to native/host.cjs (for native host wrapper)
543
704
  SURF_EXTENSION_PATH # Path to extension dist/ directory
544
705
  ```
545
706
 
546
707
  **Use cases:**
708
+ - `SURF_SOCKET`: Advanced socket override. Set it for both the native host and CLI if you need a non-default socket, including separate sockets for separate browser/profile instances in hard-isolated multi-agent workflows. Each socket gets an independent request lock.
709
+ - `SURF_REMOTE`: Remote client endpoint. `--remote <host>:<port>` overrides it; both override `SURF_SOCKET`.
710
+ - `SURF_REMOTE_CREDENTIAL`: Credential used for mutual remote authentication. `--remote-credential <path>` overrides it.
711
+ - `SURF_REMOTE_STATE_DIR`: Advanced host-side override for the mode-0700 identity and client registry directory.
712
+ - `SURF_LISTEN`: Native-host listener address on the browser machine. Use `surf install ... --listen <tailscale-ip>:<port>` to persist it in that host's wrapper.
547
713
  - `SURF_NODE_PATH` / `SURF_HOST_PATH`: Package manager installs (e.g., Nix) that store binaries in non-standard locations
548
714
  - `SURF_EXTENSION_PATH`: Package managers that create stable symlinks instead of changing paths on reinstall
549
715
 
@@ -554,9 +720,38 @@ export SURF_HOST_PATH=~/.local/share/surf-cli/native/host.cjs
554
720
  export SURF_EXTENSION_PATH=~/.local/share/surf-cli/extension
555
721
  ```
556
722
 
723
+ ## Troubleshooting native host connections
724
+
725
+ If a command fails with `Socket connect failed`, start with:
726
+
727
+ ```bash
728
+ surf doctor
729
+ surf doctor --browser all
730
+ surf doctor --json
731
+ ```
732
+
733
+ `doctor` does not require a working browser connection. It checks the socket path, native messaging manifest, manifest `allowed_origins`, and wrapper path, then prints targeted next steps.
734
+
735
+ Read the `Attempted socket:` line first. The CLI and native host must agree on the same socket path. By default this is `/tmp/surf.sock` on macOS/Linux/WSL2 and `//./pipe/surf` on Windows.
736
+
737
+ Common fixes:
738
+ - Restart the browser after `surf install <extension-id>`.
739
+ - Confirm the Surf extension is enabled and the extension ID matches the one passed to `surf install`.
740
+ - On WSL2 with Windows Chrome, run `surf install <extension-id>` from WSL2 and restart Windows Chrome. Use `--target linux` only for a Linux browser running inside WSLg.
741
+ - If `SURF_SOCKET` is set, set the same value for both the browser-launched native host and the shell running `surf`.
742
+
743
+ macOS checklist:
744
+ - Confirm Chrome has a native messaging manifest at `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/surf.browser.host.json`.
745
+ - Confirm the manifest `allowed_origins` entry uses the same extension ID shown on `chrome://extensions` for the Surf extension.
746
+ - Reinstall the manifest with `surf install <extension-id>` after copying a fresh extension build or if the extension ID changed.
747
+ - Fully restart Chrome, then reload the Surf extension on `chrome://extensions`.
748
+ - Open the extension service worker from `chrome://extensions` and check its console for native messaging or socket errors.
749
+ - If `SURF_SOCKET` is set in your shell, make sure Chrome launches the native host with the same value; otherwise both sides should use `/tmp/surf.sock`.
750
+ - Run a simple CLI command such as `surf tab.list`; if it fails, compare its `Attempted socket:` line with the socket expected by the native host.
751
+
557
752
  ## Socket API
558
753
 
559
- For programmatic integration, send JSON to `/tmp/surf.sock`:
754
+ For programmatic integration, send JSON to `/tmp/surf.sock` by default, or to `SURF_SOCKET` when set:
560
755
 
561
756
  ```bash
562
757
  echo '{"type":"tool_request","method":"execute_tool","params":{"tool":"tab.list","args":{}},"id":"1"}' | nc -U /tmp/surf.sock
@@ -614,7 +809,7 @@ echo '{"type":"tool_request","method":"execute_tool","params":{"tool":"tab.list"
614
809
  | `element.*` | `styles` |
615
810
  | `frame.*` | `list`, `switch`, `main`, `js` |
616
811
  | `wait.*` | `element`, `network`, `url`, `dom`, `load` |
617
- | `cookie.*` | `list`, `get`, `set`, `clear` |
812
+ | `cookie` / `cookie.*` | `list`, `get`, `set`, `clear`, `delete` |
618
813
  | `bookmark.*` | `add`, `remove`, `list` |
619
814
  | `history.*` | `list`, `search` |
620
815
  | `dialog.*` | `accept`, `dismiss`, `info` |
@@ -0,0 +1,116 @@
1
+ var e=`59, 178, 191`,t=1e4,n=null,r=null,i=null,a=!1,o=!1,s=!1,c=!1,l=null,u=t,d=!1,f=!1,p=window===window.top;p&&(chrome.storage.local.get(`heartbeatInterval`).then(({heartbeatInterval:e})=>{e&&typeof e==`number`&&(u=e*1e3)}),chrome.storage.onChanged.addListener((e,t)=>{if(t===`local`&&e.heartbeatInterval){let t=e.heartbeatInterval.newValue;t&&typeof t==`number`&&(u=t*1e3,o&&l&&(clearInterval(l),m()))}}));function m(){l=window.setInterval(async()=>{try{(await chrome.runtime.sendMessage({type:`STATIC_INDICATOR_HEARTBEAT`}))?.success||S()}catch{S()}},u)}function h(){if(document.getElementById(`pi-agent-styles`))return;let t=document.createElement(`style`);t.id=`pi-agent-styles`,t.textContent=`
2
+ @keyframes pi-pulse {
3
+ 0% {
4
+ box-shadow:
5
+ inset 0 0 10px rgba(${e}, 0.5),
6
+ inset 0 0 20px rgba(${e}, 0.3),
7
+ inset 0 0 30px rgba(${e}, 0.1);
8
+ }
9
+ 50% {
10
+ box-shadow:
11
+ inset 0 0 15px rgba(${e}, 0.7),
12
+ inset 0 0 25px rgba(${e}, 0.5),
13
+ inset 0 0 35px rgba(${e}, 0.2);
14
+ }
15
+ 100% {
16
+ box-shadow:
17
+ inset 0 0 10px rgba(${e}, 0.5),
18
+ inset 0 0 20px rgba(${e}, 0.3),
19
+ inset 0 0 30px rgba(${e}, 0.1);
20
+ }
21
+ }
22
+ `,document.head.appendChild(t)}function g(){let t=document.createElement(`div`);return t.id=`pi-agent-glow`,t.style.cssText=`
23
+ position: fixed;
24
+ top: 0;
25
+ left: 0;
26
+ right: 0;
27
+ bottom: 0;
28
+ pointer-events: none;
29
+ z-index: 2147483646;
30
+ opacity: 0;
31
+ transition: opacity 0.3s ease-in-out;
32
+ animation: pi-pulse 2s ease-in-out infinite;
33
+ box-shadow:
34
+ inset 0 0 10px rgba(${e}, 0.5),
35
+ inset 0 0 20px rgba(${e}, 0.3),
36
+ inset 0 0 30px rgba(${e}, 0.1);
37
+ `,t}function _(){let t=document.createElement(`div`);t.id=`pi-agent-stop-container`,t.style.cssText=`
38
+ position: fixed;
39
+ bottom: 16px;
40
+ left: 50%;
41
+ transform: translateX(-50%);
42
+ display: flex;
43
+ justify-content: center;
44
+ align-items: center;
45
+ pointer-events: none;
46
+ z-index: 2147483647;
47
+ `;let n=document.createElement(`button`);return n.id=`pi-agent-stop-button`,n.innerHTML=`
48
+ <svg width="16" height="16" viewBox="0 0 256 256" fill="currentColor" style="margin-right: 12px; vertical-align: middle;">
49
+ <path d="M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm40-112v56a12,12,0,0,1-12,12H100a12,12,0,0,1-12-12V100a12,12,0,0,1,12-12h56A12,12,0,0,1,168,100Z"></path>
50
+ </svg>
51
+ <span style="vertical-align: middle;">Stop Surf</span>
52
+ `,n.style.cssText=`
53
+ position: relative;
54
+ transform: translateY(100px);
55
+ padding: 12px 16px;
56
+ background: #FAF9F5;
57
+ color: #141413;
58
+ border: 0.5px solid rgba(31, 30, 29, 0.4);
59
+ border-radius: 12px;
60
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
61
+ font-size: 14px;
62
+ font-weight: 600;
63
+ cursor: pointer;
64
+ display: inline-flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ box-shadow:
68
+ 0 40px 80px rgba(${e}, 0.24),
69
+ 0 4px 14px rgba(${e}, 0.24);
70
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
71
+ opacity: 0;
72
+ user-select: none;
73
+ pointer-events: auto;
74
+ white-space: nowrap;
75
+ margin: 0 auto;
76
+ `,n.addEventListener(`mouseenter`,()=>{a&&(n.style.background=`#F5F4F0`)}),n.addEventListener(`mouseleave`,()=>{a&&(n.style.background=`#FAF9F5`)}),n.addEventListener(`click`,async()=>{await chrome.runtime.sendMessage({type:`STOP_AGENT`,fromTabId:`CURRENT_TAB`})}),t.appendChild(n),t}function v(){let t=document.createElement(`div`);t.id=`pi-agent-static-indicator`,t.innerHTML=`
77
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="width: 16px; height: 16px; margin-right: 8px; flex-shrink: 0;">
78
+ <circle cx="8" cy="8" r="7" fill="rgb(${e})"/>
79
+ <text x="8" y="11" font-size="9" fill="white" text-anchor="middle" font-weight="bold">π</text>
80
+ </svg>
81
+ <span style="color: #141413; font-size: 14px;">Surf is active in this tab group</span>
82
+ <div style="width: 0.5px; height: 32px; background: rgba(31, 30, 29, 0.15); margin: 0 8px;"></div>
83
+ <button id="pi-static-chat-button" style="display: inline-flex; align-items: center; justify-content: center; padding: 6px; background: transparent; border: none; cursor: pointer; width: 32px; height: 32px; border-radius: 8px; transition: background 0.2s;">
84
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="#141413">
85
+ <path d="M10 2.5C14.1421 2.5 17.5 5.85786 17.5 10C17.5 14.1421 14.1421 17.5 10 17.5H3C2.79779 17.5 2.61549 17.3782 2.53809 17.1914C2.4607 17.0046 2.50349 16.7895 2.64648 16.6465L4.35547 14.9365C3.20124 13.6175 2.5 11.8906 2.5 10C2.5 5.85786 5.85786 2.5 10 2.5Z"/>
86
+ </svg>
87
+ </button>
88
+ <button id="pi-static-close-button" style="display: inline-flex; align-items: center; justify-content: center; padding: 6px; background: transparent; border: none; cursor: pointer; width: 32px; height: 32px; margin-left: 4px; border-radius: 8px; transition: background 0.2s;">
89
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
90
+ <path d="M15.1464 4.14642C15.3417 3.95121 15.6582 3.95118 15.8534 4.14642C16.0486 4.34168 16.0486 4.65822 15.8534 4.85346L10.7069 9.99997L15.8534 15.1465C16.0486 15.3417 16.0486 15.6583 15.8534 15.8535C15.6826 16.0244 15.4186 16.0461 15.2245 15.918L15.1464 15.8535L9.99989 10.707L4.85338 15.8535C4.65813 16.0486 4.34155 16.0486 4.14634 15.8535C3.95115 15.6583 3.95129 15.3418 4.14634 15.1465L9.29286 9.99997L4.14634 4.85346C3.95129 4.65818 3.95115 4.34162 4.14634 4.14642C4.34154 3.95128 4.65812 3.95138 4.85338 4.14642L9.99989 9.29294L15.1464 4.14642Z" fill="#141413"/>
91
+ </svg>
92
+ </button>
93
+ `,t.style.cssText=`
94
+ position: fixed;
95
+ bottom: 16px;
96
+ left: 50%;
97
+ transform: translateX(-50%);
98
+ display: inline-flex;
99
+ align-items: center;
100
+ padding: 6px 6px 6px 16px;
101
+ background: #FAF9F5;
102
+ border: 0.5px solid rgba(31, 30, 29, 0.30);
103
+ border-radius: 14px;
104
+ box-shadow: 0 40px 80px 0 rgba(0, 0, 0, 0.15);
105
+ z-index: 2147483647;
106
+ pointer-events: none;
107
+ white-space: nowrap;
108
+ user-select: none;
109
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
110
+ `;let n=t.querySelector(`#pi-static-chat-button`),r=t.querySelector(`#pi-static-close-button`);return n&&(n.style.pointerEvents=`auto`,n.addEventListener(`mouseenter`,()=>n.style.background=`#F0EEE6`),n.addEventListener(`mouseleave`,()=>n.style.background=`transparent`),n.addEventListener(`click`,async()=>{await chrome.runtime.sendMessage({type:`OPEN_SIDEPANEL`})})),r&&(r.style.pointerEvents=`auto`,r.addEventListener(`mouseenter`,()=>r.style.background=`#F0EEE6`),r.addEventListener(`mouseleave`,()=>r.style.background=`transparent`),r.addEventListener(`click`,async()=>{await chrome.runtime.sendMessage({type:`DISMISS_STATIC_INDICATOR`}),S()})),t}function y(){if(!document.body){d||(d=!0,window.addEventListener(`DOMContentLoaded`,()=>{d&&(d=!1,y())},{once:!0}));return}d=!1,!a&&(a=!0,h(),n?n.style.display=``:(n=g(),document.body.appendChild(n)),r?r.style.display=``:(r=_(),document.body.appendChild(r)),requestAnimationFrame(()=>{if(n&&(n.style.opacity=`1`),r){let e=r.querySelector(`#pi-agent-stop-button`);e&&(e.style.transform=`translateY(0)`,e.style.opacity=`1`)}}))}function b(){if(d=!1,a){if(a=!1,n&&(n.style.opacity=`0`),r){let e=r.querySelector(`#pi-agent-stop-button`);e&&(e.style.transform=`translateY(100px)`,e.style.opacity=`0`)}setTimeout(()=>{a||(n?.parentNode&&(n.parentNode.removeChild(n),n=null),r?.parentNode&&(r.parentNode.removeChild(r),r=null))},300)}}function x(){if(!document.body){f||(f=!0,window.addEventListener(`DOMContentLoaded`,()=>{f&&(f=!1,x())},{once:!0}));return}f=!1,!o&&(o=!0,i?i.style.display=``:(i=v(),document.body.appendChild(i)),l&&clearInterval(l),m())}function S(){f=!1,o&&(o=!1,l&&=(clearInterval(l),null),i?.parentNode&&(i.parentNode.removeChild(i),i=null))}p&&(window.__piVisualIndicatorMessageHandler=e=>{switch(e){case`SHOW_AGENT_INDICATORS`:y();break;case`HIDE_AGENT_INDICATORS`:b();break;case`HIDE_FOR_TOOL_USE`:s=a||d,c=o||f,d=!1,f=!1,n&&(n.style.display=`none`),r&&(r.style.display=`none`),i&&o&&(i.style.display=`none`);break;case`SHOW_AFTER_TOOL_USE`:s&&(a?(n&&(n.style.display=``),r&&(r.style.display=``)):y()),c&&(o&&i?i.style.display=``:x()),s=!1,c=!1;break;case`SHOW_STATIC_INDICATOR`:x();break;case`HIDE_STATIC_INDICATOR`:S();break}},window.addEventListener(`beforeunload`,()=>{b(),S()}));var C=new Set(`alert.alertdialog.application.article.banner.blockquote.button.caption.cell.checkbox.code.columnheader.combobox.complementary.contentinfo.definition.deletion.dialog.directory.document.emphasis.feed.figure.form.generic.grid.gridcell.group.heading.img.insertion.link.list.listbox.listitem.log.main.mark.marquee.math.menu.menubar.menuitem.menuitemcheckbox.menuitemradio.meter.navigation.none.note.option.paragraph.presentation.progressbar.radio.radiogroup.region.row.rowgroup.rowheader.scrollbar.search.searchbox.separator.slider.spinbutton.status.strong.subscript.superscript.switch.tab.table.tablist.tabpanel.term.textbox.time.timer.toolbar.tooltip.tree.treegrid.treeitem`.split(`.`));function w(e){let t=e.tagName.toLowerCase();if([`button`,`input`,`select`,`textarea`].includes(t))return!e.disabled;if(t===`a`&&e.hasAttribute(`href`))return!0;if(e.hasAttribute(`tabindex`)){let t=parseInt(e.getAttribute(`tabindex`)||``,10);return!isNaN(t)&&t>=0}return e.getAttribute(`contenteditable`)===`true`}function T(e){let t=e.getAttribute(`role`);if(!t)return null;let n=t.split(/\s+/).filter(e=>e);for(let e of n)if(C.has(e))return e;return null}function E(e){let t=e.tagName.toLowerCase(),n=e.getAttribute(`type`),r={a:e=>e.hasAttribute(`href`)?`link`:`generic`,article:`article`,aside:`complementary`,button:`button`,datalist:`listbox`,dd:`definition`,details:`group`,dialog:`dialog`,dt:`term`,fieldset:`group`,figure:`figure`,footer:e=>e.closest(`article, aside, main, nav, section`)?`generic`:`contentinfo`,form:e=>e.hasAttribute(`aria-label`)||e.hasAttribute(`aria-labelledby`)?`form`:`generic`,h1:`heading`,h2:`heading`,h3:`heading`,h4:`heading`,h5:`heading`,h6:`heading`,header:e=>e.closest(`article, aside, main, nav, section`)?`generic`:`banner`,hr:`separator`,img:e=>e.getAttribute(`alt`)===``?`presentation`:`img`,li:`listitem`,main:`main`,math:`math`,menu:`list`,meter:`meter`,nav:`navigation`,ol:`list`,optgroup:`group`,option:`option`,output:`status`,p:`paragraph`,progress:`progressbar`,search:`search`,section:e=>e.hasAttribute(`aria-label`)||e.hasAttribute(`aria-labelledby`)?`region`:`generic`,select:e=>{let t=e;return t.hasAttribute(`multiple`)||t.size&&t.size>1?`listbox`:`combobox`},table:`table`,tbody:`rowgroup`,td:`cell`,textarea:`textbox`,tfoot:`rowgroup`,th:`columnheader`,thead:`rowgroup`,time:`time`,tr:`row`,ul:`list`};if(t===`input`)return{button:`button`,checkbox:`checkbox`,email:`textbox`,file:`button`,image:`button`,number:`spinbutton`,radio:`radio`,range:`slider`,reset:`button`,search:`searchbox`,submit:`button`,tel:`textbox`,text:`textbox`,url:`textbox`}[n||``]||`textbox`;let i=r[t];return typeof i==`function`?i(e):i||`generic`}function D(e){let t=T(e);return!t||(t===`none`||t===`presentation`)&&w(e)?E(e):t}window.__piElementMap||(window.__piElementMap={});var O=0;function k(e,t,n){let r=e._piRef;if(r&&r.role===t&&r.name===n)return r.ref;let i=`e${++O}`;return e._piRef={role:t,name:n,ref:i},i}function A(){let e=[];return document.querySelectorAll(`[role="dialog"], [role="alertdialog"], dialog[open]`).forEach(t=>{let n=window.getComputedStyle(t);if(!(n.display!==`none`&&n.visibility!==`hidden`&&n.opacity!==`0`&&t.offsetWidth>0&&t.offsetHeight>0))return;let r=t.getAttribute(`role`)||`dialog`,i=t.getAttribute(`aria-label`)||t.querySelector(`[role="heading"], h1, h2, h3`)?.textContent?.trim()||`Dialog`;i.length>100&&(i=i.substring(0,100)+`...`),e.push({type:r,description:`${r}: ${i}`,clearedBy:`computer(action=key, text=Escape)`})}),e}var j={wait(e){return new Promise(t=>setTimeout(t,e))},async waitForSelector(e,t={}){let{state:n=`visible`,timeout:r=2e4}=t,i=e=>{if(!e)return!1;let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`&&t.opacity!==`0`&&e.offsetWidth>0&&e.offsetHeight>0},a=()=>{let t=document.querySelector(e);switch(n){case`attached`:return t;case`detached`:return t?null:document.body;case`hidden`:return t?i(t)?null:t:document.body;default:return i(t)?t:null}};return new Promise((t,i)=>{let o=a();if(o){t(n===`detached`||n===`hidden`?null:o);return}let s=new MutationObserver(()=>{let e=a();e&&(s.disconnect(),clearTimeout(c),t(n===`detached`||n===`hidden`?null:e))}),c=setTimeout(()=>{s.disconnect(),i(Error(`Timeout waiting for "${e}" to be ${n}`))},r);s.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`style`,`class`,`hidden`]})})},async waitForText(e,t={}){let{selector:n,timeout:r=2e4}=t,i=()=>{let t=n?document.querySelector(n):document.body;if(!t)return null;let r=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;r.nextNode();)if(r.currentNode.textContent?.includes(e))return r.currentNode.parentElement;return null};return new Promise((t,n)=>{let a=i();if(a){t(a);return}let o=new MutationObserver(()=>{let e=i();e&&(o.disconnect(),clearTimeout(s),t(e))}),s=setTimeout(()=>{o.disconnect(),n(Error(`Timeout waiting for text "${e}"`))},r);o.observe(document.documentElement,{childList:!0,subtree:!0,characterData:!0})})},async waitForHidden(e,t=2e4){await j.waitForSelector(e,{state:`hidden`,timeout:t})},getByRole(e,t={}){let{name:n}=t,r={button:[`button`,`input[type="button"]`,`input[type="submit"]`,`input[type="reset"]`],link:[`a[href]`],textbox:[`input:not([type])`,`input[type="text"]`,`input[type="email"]`,`input[type="password"]`,`input[type="search"]`,`input[type="tel"]`,`input[type="url"]`,`textarea`],checkbox:[`input[type="checkbox"]`],radio:[`input[type="radio"]`],combobox:[`select`],heading:[`h1`,`h2`,`h3`,`h4`,`h5`,`h6`],list:[`ul`,`ol`],listitem:[`li`],navigation:[`nav`],main:[`main`],banner:[`header`],contentinfo:[`footer`],form:[`form`],img:[`img`],table:[`table`]},i=[];i.push(...document.querySelectorAll(`[role="${e}"]`));let a=r[e];if(a)for(let e of a)i.push(...document.querySelectorAll(`${e}:not([role])`));if(!n)return i[0]||null;let o=n.toLowerCase().trim();for(let e of i){let t=e.getAttribute(`aria-label`)?.toLowerCase().trim(),n=e.textContent?.toLowerCase().trim(),r=e.getAttribute(`title`)?.toLowerCase().trim(),i=e.getAttribute(`placeholder`)?.toLowerCase().trim();if(t===o||n===o||r===o||i===o||t?.includes(o)||n?.includes(o))return e}return null}};window.__piHelpers||(window.__piHelpers=j,window.piHelpers=j);function M(){return window.__piElementMap}function N(e=`interactive`,t=15,n,r=!1,i=!1){try{window.__piRefs={};function a(e){return D(e)}function o(e){let t=e.tagName.toLowerCase(),n=e.getAttribute(`aria-labelledby`);if(n){let e=n.split(/\s+/).map(e=>document.getElementById(e)?.textContent?.trim()||``).filter(Boolean);if(e.length){let t=e.join(` `);return t.length>100?t.substring(0,100)+`...`:t}}if(t===`select`){let t=e,n=t.querySelector(`option[selected]`)||(t.selectedIndex>=0?t.options[t.selectedIndex]:null);if(n?.textContent?.trim())return n.textContent.trim()}let r=e.getAttribute(`aria-label`);if(r?.trim())return r.trim();let i=e.getAttribute(`placeholder`);if(i?.trim())return i.trim();let a=e.getAttribute(`title`);if(a?.trim())return a.trim();let o=e.getAttribute(`alt`);if(o?.trim())return o.trim();if(e.id){let t=document.querySelector(`label[for="${e.id}"]`);if(t?.textContent?.trim())return t.textContent.trim()}if(t===`input`){let t=e,n=e.getAttribute(`type`)||``,r=e.getAttribute(`value`);if(n===`submit`&&r?.trim())return r.trim();if(t.value&&t.value.length<50&&t.value.trim())return t.value.trim()}if([`button`,`a`,`summary`].includes(t)){let t=e.textContent||``;if(t.trim())return t.trim()}if(/^h[1-6]$/.test(t)){let t=e.textContent;if(t?.trim()){let e=t.trim();return e.length>100?e.substring(0,100)+`...`:e}}if(t===`img`)return``;let s=``;for(let t of e.childNodes)t.nodeType===Node.TEXT_NODE&&(s+=t.textContent);if(s?.trim()&&s.trim().length>=3){let e=s.trim();return e.length>100?e.substring(0,100)+`...`:e}return``}function s(e){let t={},n=e.getAttribute(`aria-checked`);n===`true`?t.checked=!0:n===`false`?t.checked=!1:n===`mixed`?t.checked=`mixed`:e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`)&&(e.type===`checkbox`&&e.indeterminate?t.checked=`mixed`:t.checked=e.checked);let r=e instanceof HTMLButtonElement||e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement;(e.getAttribute(`aria-disabled`)===`true`||r&&e.disabled||e.closest(`fieldset:disabled`))&&(t.disabled=!0);let i=e.getAttribute(`aria-expanded`);i===`true`?t.expanded=!0:i===`false`&&(t.expanded=!1);let a=e.getAttribute(`aria-pressed`);a===`true`?t.pressed=!0:a===`false`?t.pressed=!1:a===`mixed`&&(t.pressed=`mixed`);let o=e.getAttribute(`aria-selected`);o===`true`?t.selected=!0:o===`false`&&(t.selected=!1);let s=e.getAttribute(`aria-current`);s&&s!==`false`&&(t.active=!0);let c=e.tagName.toLowerCase();if(/^h[1-6]$/.test(c))t.level=parseInt(c[1],10);else{let n=e.getAttribute(`aria-level`);n&&(t.level=parseInt(n,10))}return t}function c(e){let t=[];return e.checked!==void 0&&t.push(e.checked===`mixed`?`[checked=mixed]`:e.checked?`[checked]`:`[unchecked]`),e.disabled&&t.push(`[disabled]`),e.expanded!==void 0&&t.push(e.expanded?`[expanded]`:`[collapsed]`),e.pressed!==void 0&&t.push(e.pressed===`mixed`?`[pressed=mixed]`:e.pressed?`[pressed]`:`[not-pressed]`),e.selected!==void 0&&t.push(e.selected?`[selected]`:`[not-selected]`),e.active&&t.push(`[active]`),e.level!==void 0&&t.push(`[level=${e.level}]`),t.join(` `)}function l(e){let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`&&t.opacity!==`0`&&e.offsetWidth>0&&e.offsetHeight>0}function u(e){let t=e.tagName.toLowerCase();return[`a`,`button`,`input`,`select`,`textarea`,`details`,`summary`].includes(t)||e.hasAttribute(`onclick`)||e.hasAttribute(`tabindex`)||e.getAttribute(`role`)===`button`||e.getAttribute(`role`)===`link`||e.getAttribute(`contenteditable`)===`true`}function d(e){let t=e.tagName.toLowerCase();return[`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`nav`,`main`,`header`,`footer`,`section`,`article`,`aside`].includes(t)||e.hasAttribute(`role`)}function f(e){return window.getComputedStyle(e).cursor===`pointer`}function p(e,t){let n=e.tagName.toLowerCase();if([`script`,`style`,`meta`,`link`,`title`,`noscript`].includes(n)||t.filter!==`all`&&e.getAttribute(`aria-hidden`)===`true`||t.filter!==`all`&&!l(e))return!1;if(t.filter!==`all`&&!t.refId){let t=e.getBoundingClientRect();if(!(t.top<window.innerHeight&&t.bottom>0&&t.left<window.innerWidth&&t.right>0))return!1}if(t.filter===`interactive`)return u(e);if(u(e)||d(e)||o(e).length>0)return!0;let r=a(e);return t.compact&&new Set([`generic`,`group`,`region`,`article`,`section`,`complementary`]).has(r)&&o(e).length===0?!1:r!==`generic`&&r!==`img`}function m(r,l){let u=[],d={filter:e,refId:n||null,compact:i},h=M(),g=p(r,d)||n&&l===0;if(g){let e=a(r),t=o(r),n=s(r),i=k(r,e,t);window.__piRefs[i]=r,h[i]={element:new WeakRef(r),role:e,name:t};let d=`${` `.repeat(l)}${e}`;if(t){let e=t.replace(/\s+/g,` `).replace(/"/g,`\\"`);d+=` "${e}"`}d+=` [${i}]`;let p=c(n);p&&(d+=` ${p}`),f(r)&&(d+=` [cursor=pointer]`);let m=r.getAttribute(`href`);m&&(d+=` href="${m}"`);let g=r.getAttribute(`type`);g&&(d+=` type="${g}"`);let _=r.getAttribute(`placeholder`);_&&(d+=` placeholder="${_}"`),u.push(d)}if(l<t)for(let e of r.children)u.push(...m(e,g?l+1:l));return u}function h(e){return e.replace(/\[e\d+\]/g,`[REF]`)}function g(e){let t=new Map;for(let n of e){if(!n.trim())continue;let e=h(n);t.set(e,(t.get(e)||0)+1)}return t}function _(e,t){let n=e.split(`
111
+ `),r=t.split(`
112
+ `),i=g(n),a=g(r),o=[],s=[];for(let e of r){if(!e.trim())continue;let t=h(e),n=i.get(t)||0;(a.get(t)||0)>n&&(o.push(e),i.set(t,n+1))}let c=g(n);for(let e of n){if(!e.trim())continue;let t=h(e),n=c.get(t)||0;n>(a.get(t)||0)&&(s.push(e),c.set(t,n-1))}if(o.length===0&&s.length===0)return{diff:`[NO CHANGES]`,hasChanges:!1};let l=[];return s.length>0&&l.push(...s.map(e=>`- ${e}`)),o.length>0&&l.push(...o.map(e=>`+ ${e}`)),{diff:l.join(`
113
+ `),hasChanges:!0}}let v=M(),y=null;if(n){let e=v[n];if(!e)return{error:`Element with ref_id '${n}' not found. Use read_page without ref_id to get current elements.`,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}};let t=e.element.deref();if(!t)return delete v[n],{error:`Element with ref_id '${n}' no longer exists. Use read_page without ref_id to get current elements.`,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}};y=t}else y=document.body;let b=y?m(y,0):[];for(let e of Object.keys(v))v[e].element.deref()||delete v[e];let x=b.join(`
114
+ `);if(x.length>5e4)return{error:`Output exceeds 50000 character limit (${x.length} characters). Try using filter="interactive" or specify a ref_id.`,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}};let S=A(),C,w=!1,T=window.__piLastSnapshot;return!r&&!n&&T&&Date.now()-T.timestamp<5e3&&(C=_(T.content,x).diff,w=!0),window.__piLastSnapshot={content:x,timestamp:Date.now()},{pageContent:x+`\n\n[Viewport: ${window.innerWidth}x${window.innerHeight}]`,diff:w?C:void 0,viewport:{width:window.innerWidth,height:window.innerHeight},modalStates:S.length>0?S:void 0,modalLimitations:`Only custom modals ([role=dialog]) detected. Native alert/confirm/prompt dialogs and system file choosers cannot be detected from content scripts.`,isIncremental:w}}catch(e){return{error:`Error generating accessibility tree: ${e instanceof Error?e.message:`Unknown error`}`,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}}}}function P(e){return e.length?/[\n\r]/.test(e)||/^[\s]/.test(e)||/[\s]$/.test(e)||/[:"{}[\]]/.test(e)?`"`+e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`).replace(/\n/g,`\\n`).replace(/\r/g,`\\r`)+`"`:e:`""`}function F(e=`interactive`,t=15){try{window.__piRefs={};let n=[];function r(e){return D(e)}function i(e){let t=e.tagName.toLowerCase(),n=e.getAttribute(`aria-labelledby`);if(n){let e=n.split(/\s+/).map(e=>document.getElementById(e)?.textContent?.trim()||``).filter(Boolean);if(e.length){let t=e.join(` `);return t.length>100?t.substring(0,100)+`...`:t}}if(t===`select`){let t=e,n=t.querySelector(`option[selected]`)||(t.selectedIndex>=0?t.options[t.selectedIndex]:null);if(n?.textContent?.trim())return n.textContent.trim()}let r=e.getAttribute(`aria-label`);if(r?.trim())return r.trim();let i=e.getAttribute(`placeholder`);if(i?.trim())return i.trim();let a=e.getAttribute(`title`);if(a?.trim())return a.trim();let o=e.getAttribute(`alt`);if(o?.trim())return o.trim();if(e.id){let t=document.querySelector(`label[for="${e.id}"]`);if(t?.textContent?.trim())return t.textContent.trim()}if(t===`input`){let t=e,n=e.getAttribute(`type`)||``,r=e.getAttribute(`value`);if(n===`submit`&&r?.trim())return r.trim();if(t.value&&t.value.length<50&&t.value.trim())return t.value.trim()}if([`button`,`a`,`summary`].includes(t)){let t=e.textContent||``;if(t.trim())return t.trim()}if(/^h[1-6]$/.test(t)){let t=e.textContent;if(t?.trim()){let e=t.trim();return e.length>100?e.substring(0,100)+`...`:e}}if(t===`img`)return``;let s=``;for(let t of e.childNodes)t.nodeType===Node.TEXT_NODE&&(s+=t.textContent);if(s?.trim()&&s.trim().length>=3){let e=s.trim();return e.length>100?e.substring(0,100)+`...`:e}return``}function a(e){let t={},n=e.getAttribute(`aria-checked`);n===`true`?t.checked=!0:n===`false`?t.checked=!1:n===`mixed`?t.checked=`mixed`:e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`)&&(e.type===`checkbox`&&e.indeterminate?t.checked=`mixed`:t.checked=e.checked);let r=e instanceof HTMLButtonElement||e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement;(e.getAttribute(`aria-disabled`)===`true`||r&&e.disabled||e.closest(`fieldset:disabled`))&&(t.disabled=!0);let i=e.getAttribute(`aria-expanded`);i===`true`?t.expanded=!0:i===`false`&&(t.expanded=!1);let a=e.getAttribute(`aria-pressed`);a===`true`?t.pressed=!0:a===`false`?t.pressed=!1:a===`mixed`&&(t.pressed=`mixed`);let o=e.getAttribute(`aria-selected`);o===`true`?t.selected=!0:o===`false`&&(t.selected=!1);let s=e.getAttribute(`aria-current`);s&&s!==`false`&&(t.active=!0);let c=e.tagName.toLowerCase();if(/^h[1-6]$/.test(c))t.level=parseInt(c[1],10);else{let n=e.getAttribute(`aria-level`);n&&(t.level=parseInt(n,10))}return t}function o(e){let t=[];return e.checked!==void 0&&t.push(e.checked===`mixed`?`[checked=mixed]`:e.checked?`[checked]`:`[unchecked]`),e.disabled&&t.push(`[disabled]`),e.expanded!==void 0&&t.push(e.expanded?`[expanded]`:`[collapsed]`),e.pressed!==void 0&&t.push(e.pressed===`mixed`?`[pressed=mixed]`:e.pressed?`[pressed]`:`[not-pressed]`),e.selected!==void 0&&t.push(e.selected?`[selected]`:`[not-selected]`),e.active&&t.push(`[active]`),e.level!==void 0&&t.push(`[level=${e.level}]`),t.join(` `)}function s(e){let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`&&t.opacity!==`0`&&e.offsetWidth>0&&e.offsetHeight>0}function c(e){let t=e.tagName.toLowerCase();return[`a`,`button`,`input`,`select`,`textarea`,`details`,`summary`].includes(t)||e.hasAttribute(`onclick`)||e.hasAttribute(`tabindex`)||e.getAttribute(`role`)===`button`||e.getAttribute(`role`)===`link`||e.getAttribute(`contenteditable`)===`true`}function l(e){let t=e.tagName.toLowerCase();return[`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`nav`,`main`,`header`,`footer`,`section`,`article`,`aside`].includes(t)||e.hasAttribute(`role`)}function u(e){return window.getComputedStyle(e).cursor===`pointer`}function d(e,t,n,r){let i=e;t&&(i+=` `+P(t));let a=k(n,e,t);window.__piRefs[a]=n,i+=` [ref=${a}]`;let s=o(r);return s&&(i+=` ${s}`),u(n)&&(i+=` [cursor=pointer]`),i}function f(e){let t={},n=e.getAttribute(`href`);n&&(t.url=n);let r=e.getAttribute(`placeholder`);return r&&(t.placeholder=r),t}function p(o,u,m){if(u>t)return;let h=o.tagName.toLowerCase();if([`script`,`style`,`meta`,`link`,`title`,`noscript`].includes(h)||e!==`all`&&o.getAttribute(`aria-hidden`)===`true`||e!==`all`&&!s(o))return;if(e!==`all`){let e=o.getBoundingClientRect();if(!(e.top<window.innerHeight&&e.bottom>0&&e.left<window.innerWidth&&e.right>0))return}let g=r(o),_=i(o),v=a(o),y=c(o),b=l(o),x=_.length>0,S;if(S=e===`interactive`?y:e===`all`||y||b||x||g!==`generic`&&g!==`img`,S){let e=` `.repeat(u),t=d(g,_,o,v),r=f(o),i=[];for(let e of o.children)i.push(e);let a=i.length>0,s=Object.keys(r).length>0;if(!a&&!s)n.push(`${e}- ${t}`);else{n.push(`${e}- ${t}:`);for(let[t,i]of Object.entries(r))n.push(`${e} - /${t}: ${P(i)}`);for(let e of i)p(e,u+1,!0)}}else for(let e of o.children)p(e,u,m)}p(document.body,0,!1);let m=n.join(`
115
+ `);return m.length>5e4?{error:`Output exceeds 50000 character limit (${m.length} characters). Try using filter="interactive".`,yaml:``,viewport:{width:window.innerWidth,height:window.innerHeight}}:{yaml:m+`\n\n[Viewport: ${window.innerWidth}x${window.innerHeight}]`,viewport:{width:window.innerWidth,height:window.innerHeight}}}catch(e){return{error:`Error generating YAML tree: ${e instanceof Error?e.message:`Unknown error`}`,yaml:``,viewport:{width:window.innerWidth,height:window.innerHeight}}}}function I(e){let t=M(),n=t[e],r;if(n&&(r=n.element.deref(),r||delete t[e]),!r&&window.__piRefs&&(r=window.__piRefs[e]),!r)return{x:0,y:0,error:`Element ${e} not found. Use read_page to get current elements.`};let i=r.getBoundingClientRect();return{x:Math.round(i.left+i.width/2),y:Math.round(i.top+i.height/2)}}function L(e,t){let n=M(),r=n[e],i;if(r&&(i=r.element.deref(),i||delete n[e]),!i&&window.__piRefs&&(i=window.__piRefs[e]),!i)return{success:!1,error:`Element ${e} not found. Use read_page to get current elements.`};let a=i.tagName.toLowerCase();try{if(a===`input`){let e=i,n=e.type.toLowerCase();n===`checkbox`||n===`radio`?(e.checked=!!t,e.dispatchEvent(new Event(`change`,{bubbles:!0}))):(e.value=String(t),e.dispatchEvent(new Event(`input`,{bubbles:!0})),e.dispatchEvent(new Event(`change`,{bubbles:!0})))}else if(a===`textarea`){let e=i;e.value=String(t),e.dispatchEvent(new Event(`input`,{bubbles:!0})),e.dispatchEvent(new Event(`change`,{bubbles:!0}))}else if(a===`select`){let n=i,r=String(t),a=!1;for(let e of n.options)if(e.value===r||e.textContent?.trim()===r){n.value=e.value,a=!0;break}if(!a)return{success:!1,error:`Option "${t}" not found in select element ${e}`};n.dispatchEvent(new Event(`change`,{bubbles:!0}))}else if(i.getAttribute(`contenteditable`)===`true`)i.textContent=String(t),i.dispatchEvent(new Event(`input`,{bubbles:!0}));else return{success:!1,error:`Element ${e} (${a}) is not a form field`};return{success:!0}}catch(e){return{success:!1,error:`Failed to set value: ${e instanceof Error?e.message:`Unknown error`}`}}}function R(e,t,n=!0,r=!1){try{let i=document.querySelector(e);if(!i)return{success:!1,error:`Element not found: ${e}`};let a=i.querySelector(`[contenteditable="true"]`),o=a||i,s=i.isContentEditable||!!a;if(o.focus(),n&&(s?o.textContent=``:o.value=``),s?o.textContent=t:o.value=t,o.dispatchEvent(new Event(`input`,{bubbles:!0})),o.dispatchEvent(new Event(`change`,{bubbles:!0})),r){let e=i.closest(`form`),t=e?.querySelector(`button[type="submit"], input[type="submit"]`)||document.querySelector(`button[type="submit"], button[data-testid*="send"], button[aria-label*="Send"]`);t?t.click():e?e.dispatchEvent(new Event(`submit`,{bubbles:!0})):o.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Enter`,code:`Enter`,keyCode:13,bubbles:!0}))}return{success:!0,contentEditable:s}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function z(e,t){let n=new TextEncoder().encode(e);if(n.length<=t)return e;let r=t;for(;r>0&&(n[r]&192)==128;)r--;return new TextDecoder(`utf-8`,{fatal:!1}).decode(n.subarray(0,r))}function B(e={}){try{let t=document.querySelector(`article`),n=document.querySelector(`main`),r=(t||n||document.body).textContent?.replace(/\s+/g,` `).trim()||``;return{text:Number.isFinite(e.maxBytes)&&e.maxBytes>0?z(r,e.maxBytes):r.substring(0,5e4),title:document.title,url:window.location.href}}catch(e){return{text:``,title:``,url:``,error:`Failed to extract text: ${e instanceof Error?e.message:`Unknown error`}`}}}function V(e){let t=M(),n=t[e],r;return n&&(r=n.element.deref(),r||delete t[e]),!r&&window.__piRefs&&(r=window.__piRefs[e]),r?(r.scrollIntoView({behavior:`smooth`,block:`center`}),{success:!0}):{success:!1,error:`Element ${e} not found. Run read_page to get current element refs.`}}function H(e,t,n,r=`screenshot.png`){try{let i=atob(e),a=new ArrayBuffer(i.length),o=new Uint8Array(a);for(let e=0;e<i.length;e++)o[e]=i.charCodeAt(e);let s=new Blob([a],{type:`image/png`}),c=new File([s],r,{type:`image/png`}),l=null;if(t){let e=M(),n=e[t];if(n&&(l=n.element.deref(),l||delete e[t]),!l&&window.__piRefs&&(l=window.__piRefs[t]),!l)return{success:!1,error:`Element ${t} not found. Run read_page to get current element refs.`}}else if(n&&(l=document.elementFromPoint(n[0],n[1]),!l))return{success:!1,error:`No element at (${n[0]}, ${n[1]})`};if(!l)return{success:!1,error:`No target element`};if(l.tagName===`INPUT`&&l.type===`file`){let e=l,t=new DataTransfer;return t.items.add(c),e.files=t.files,e.dispatchEvent(new Event(`change`,{bubbles:!0})),{success:!0}}let u=new DataTransfer;u.items.add(c);let d=new DragEvent(`drop`,{bubbles:!0,cancelable:!0,dataTransfer:u});return l.dispatchEvent(d),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:`Upload failed`}}}chrome.runtime.onMessage.addListener((e,t,n)=>{switch(e.type){case`SHOW_AGENT_INDICATORS`:case`HIDE_AGENT_INDICATORS`:case`HIDE_FOR_TOOL_USE`:case`SHOW_AFTER_TOOL_USE`:case`SHOW_STATIC_INDICATOR`:case`HIDE_STATIC_INDICATOR`:if(!window.__piVisualIndicatorMessageHandler){n({error:`Visual indicator content script not loaded.`});break}window.__piVisualIndicatorMessageHandler(e.type),n({success:!0});break;case`GENERATE_ACCESSIBILITY_TREE`:{let t=e.options||{};if(t.format===`yaml`){let e=F(t.filter||`interactive`,t.depth??15),r=A();e.error?n({error:e.error,pageContent:``,viewport:e.viewport}):n({pageContent:e.yaml,viewport:e.viewport,modalStates:r.length>0?r:void 0,modalLimitations:`Only custom modals ([role=dialog]) detected. Native alert/confirm/prompt dialogs and system file choosers cannot be detected from content scripts.`})}else n(N(t.filter||`interactive`,t.depth??15,t.refId,t.forceFullSnapshot??!1,t.compact??!1));break}case`GET_ELEMENT_COORDINATES`:n(I(e.ref));break;case`CLICK_ELEMENT`:{let t=M(),r=t[e.ref],i;if(r&&(i=r.element.deref(),i||delete t[e.ref]),!i&&window.__piRefs&&(i=window.__piRefs[e.ref]),!i){n({error:`Element ${e.ref} not found. Use read_page to get current elements.`});break}if(e.button===`triple`){let e=new MouseEvent(`click`,{bubbles:!0,cancelable:!0,view:window,detail:3});i.dispatchEvent(e)}else e.button===`double`?i.dispatchEvent(new MouseEvent(`dblclick`,{bubbles:!0,cancelable:!0,view:window})):e.button===`right`?i.dispatchEvent(new MouseEvent(`contextmenu`,{bubbles:!0,cancelable:!0,view:window})):i.click();n({success:!0});break}case`FORM_INPUT`:n(L(e.ref,e.value));break;case`EVAL_IN_PAGE`:try{let t=document.createElement(`script`);t.textContent=`(function() { ${e.code} })();`,document.documentElement.appendChild(t),t.remove(),n({success:!0})}catch(e){n({success:!1,error:e instanceof Error?e.message:String(e)})}break;case`GET_PAGE_TEXT`:n(B(e.options||{}));break;case`SMART_TYPE`:n(R(e.selector,e.text,e.clear,e.submit));break;case`GET_FRAME_BY_SELECTOR`:try{let t=document.querySelector(e.selector);if(!t||t.tagName.toLowerCase()!==`iframe`){n({error:`No iframe found with selector "${e.selector}"`});break}n({url:t.src,name:t.name||void 0})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`GET_FRAME_NAME`:try{n({name:window.name||null})}catch{n({name:null})}break;case`LOCATE_ROLE`:try{let{role:t,name:r,all:i}=e,a=M(),o={button:[`button`,`input[type="button"]`,`input[type="submit"]`,`input[type="reset"]`,`[role="button"]`],link:[`a[href]`,`[role="link"]`],textbox:[`input:not([type])`,`input[type="text"]`,`input[type="email"]`,`input[type="password"]`,`input[type="search"]`,`input[type="tel"]`,`input[type="url"]`,`textarea`,`[role="textbox"]`],checkbox:[`input[type="checkbox"]`,`[role="checkbox"]`],radio:[`input[type="radio"]`,`[role="radio"]`],combobox:[`select`,`[role="combobox"]`],listbox:[`[role="listbox"]`,`select[multiple]`],option:[`option`,`[role="option"]`],heading:[`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`[role="heading"]`],navigation:[`nav`,`[role="navigation"]`],main:[`main`,`[role="main"]`],img:[`img[alt]`,`[role="img"]`],dialog:[`dialog`,`[role="dialog"]`,`[role="alertdialog"]`],tab:[`[role="tab"]`],tabpanel:[`[role="tabpanel"]`],menu:[`[role="menu"]`],menuitem:[`[role="menuitem"]`]}[t]||[`[role="${t}"]`],s=[];for(let e of o)try{s.push(...document.querySelectorAll(e))}catch{}let c=s.filter(e=>{let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`&&e.offsetWidth>0&&e.offsetHeight>0}),l=c;if(r){let e=r.toLowerCase();l=c.filter(t=>{let n=t.getAttribute(`aria-label`)?.toLowerCase(),r=t.textContent?.trim().toLowerCase(),i=t.getAttribute(`title`)?.toLowerCase(),a=t.placeholder?.toLowerCase(),o=t.value?.toLowerCase();return n?.includes(e)||r?.includes(e)||i?.includes(e)||a?.includes(e)||o?.includes(e)})}if(l.length===0){n({error:`No element found with role "${t}"${r?` and name "${r}"`:``}`});break}let u=l.map(e=>{let n=k(e,t,r||``);return window.__piRefs=window.__piRefs||{},window.__piRefs[n]=e,a[n]={element:new WeakRef(e),role:t,name:r||``},{ref:n,text:e.textContent?.trim().slice(0,50)}});n(i?{matches:u}:{ref:u[0].ref,text:u[0].text})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`LOCATE_TEXT`:try{let{text:t,exact:r}=e,i=M(),a=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),o=[];for(;a.nextNode();){let e=a.currentNode.textContent||``;if(r?e.trim()===t:e.toLowerCase().includes(t.toLowerCase())){let e=a.currentNode.parentElement;if(e&&!o.includes(e)){let t=window.getComputedStyle(e);t.display!==`none`&&t.visibility!==`hidden`&&o.push(e)}}}if(o.length===0){n({error:`No element found with text "${t}"`});break}let s=o.sort((e,t)=>(e.textContent?.length||0)-(t.textContent?.length||0))[0],c=D(s),l=k(s,c,t);window.__piRefs=window.__piRefs||{},window.__piRefs[l]=s,i[l]={element:new WeakRef(s),role:c,name:t},n({ref:l,text:s.textContent?.trim().slice(0,50)})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`LOCATE_LABEL`:try{let{label:t}=e,r=M(),i=document.querySelectorAll(`label`),a=null;for(let e of i)if((e.textContent?.trim().toLowerCase())?.includes(t.toLowerCase())){let t=e.getAttribute(`for`);if(t&&(a=document.getElementById(t)),a||=e.querySelector(`input, select, textarea`),a)break}if(a||=(t.toLowerCase(),document.querySelector(`input[aria-label*="${t}" i], input[placeholder*="${t}" i], textarea[aria-label*="${t}" i], textarea[placeholder*="${t}" i], select[aria-label*="${t}" i]`)),!a){n({error:`No form field found with label "${t}"`});break}let o=D(a),s=k(a,o,t);window.__piRefs=window.__piRefs||{},window.__piRefs[s]=a,r[s]={element:new WeakRef(a),role:o,name:t},n({ref:s,label:t})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`GET_ELEMENT_STYLES`:try{let{selector:t}=e,r=M(),i=e=>{let t=getComputedStyle(e),n=e.getBoundingClientRect();return{tag:e.tagName.toLowerCase(),text:e.innerText?.trim().slice(0,80)||null,box:{x:Math.round(n.x),y:Math.round(n.y),width:Math.round(n.width),height:Math.round(n.height)},styles:{fontSize:t.fontSize,fontWeight:t.fontWeight,fontFamily:t.fontFamily.split(`,`)[0].trim().replace(/"/g,``),color:t.color,backgroundColor:t.backgroundColor,borderRadius:t.borderRadius,border:t.border!==`none`&&t.borderWidth!==`0px`?t.border:null,boxShadow:t.boxShadow===`none`?null:t.boxShadow,padding:t.padding}}};if(/^e\d+$/.test(t)){let e=r[t],a;if(e&&(a=e.element.deref(),a||delete r[t]),!a&&window.__piRefs&&(a=window.__piRefs[t]),!a){n({error:`Element ${t} not found`});break}n({styles:[i(a)]})}else{let e=document.querySelectorAll(t);if(e.length===0){n({error:`No elements found matching "${t}"`});break}n({styles:Array.from(e).map(i)})}}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`SELECT_OPTION`:try{let{selector:t,values:r,by:i}=e,a=M(),o=null;if(/^e\d+$/.test(t)){let e=a[t],r;if(e&&(r=e.element.deref(),r||delete a[t]),!r&&window.__piRefs&&(r=window.__piRefs[t]),!r){n({error:`Element ${t} not found`});break}if(r.tagName!==`SELECT`){n({error:`Element ${t} is not a <select>`});break}o=r}else{if(o=document.querySelector(t),!o){n({error:`No element found matching "${t}"`});break}if(o.tagName!==`SELECT`){n({error:`Element "${t}" is not a <select>`});break}}if(o.multiple)for(let e of o.options)e.selected=!1;let s=[],c=[],l=o.multiple?r:[r[0]];for(let e of l){let t=!1;for(let n of o.options){let r=!1;if(r=i===`index`?n.index===parseInt(e,10):i===`label`?n.text.toLowerCase().includes(e.toLowerCase()):n.value===e,r){n.selected=!0,s.push(n.value),t=!0;break}}t||c.push(e)}o.dispatchEvent(new Event(`change`,{bubbles:!0})),c.length>0?n({selected:s,warning:`Values not found: ${c.join(`, `)}`}):n({selected:s})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`GET_ELEMENT_TEXT`:try{let{ref:t}=e,r=M(),i=r[t],a;if(i&&(a=i.element.deref(),a||delete r[t]),!a&&window.__piRefs&&(a=window.__piRefs[t]),!a){n({error:`Element ${t} not found`});break}n({text:a.textContent?.trim()||``})}catch(e){n({error:e instanceof Error?e.message:String(e)})}break;case`SCROLL_TO_ELEMENT`:n(V(e.ref));break;case`UPLOAD_IMAGE`:n(H(e.base64,e.ref,e.coordinate,e.filename));break;case`WAIT_FOR_ELEMENT`:{let{selector:t,state:r=`visible`,timeout:i=2e4}=e,a=Math.min(i,6e4),o=e=>{if(!e)return!1;let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`&&t.opacity!==`0`&&e.offsetWidth>0&&e.offsetHeight>0},s=()=>{let e=document.querySelector(t);switch(r){case`attached`:return!!e;case`detached`:return!e;case`hidden`:return!e||!o(e);default:return o(e)}},c=Date.now();return new Promise(e=>{if(s()){e({success:!0,waited:Date.now()-c});return}let n=new MutationObserver(()=>{s()&&(n.disconnect(),clearTimeout(i),e({success:!0,waited:Date.now()-c}))}),i=setTimeout(()=>{n.disconnect(),e({success:!1,waited:Date.now()-c,error:`Timeout waiting for "${t}" to be ${r}`})},a);n.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`style`,`class`,`hidden`,`disabled`]})}).then(e=>{if(!e.success){n({error:e.error,waited:e.waited,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}});return}n({...N(`interactive`,15,void 0,!0),waited:e.waited})}),!0}case`WAIT_FOR_URL`:{let{pattern:t,timeout:r=2e4}=e,i=Math.min(r,6e4),a=e=>{if(t.includes(`*`)){let n=t.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*\*/g,`<<<GLOBSTAR>>>`).replace(/\*/g,`[^/]*`).replace(/<<<GLOBSTAR>>>/g,`.*`);return RegExp(`^${n}$`).test(e)}return e.includes(t)},o=Date.now();return new Promise(e=>{if(a(window.location.href)){e({success:!0,waited:Date.now()-o});return}let n=!1,r=()=>{n||a(window.location.href)&&(n=!0,clearInterval(s),clearTimeout(c),window.removeEventListener(`popstate`,r),window.removeEventListener(`hashchange`,r),e({success:!0,waited:Date.now()-o}))},s=setInterval(r,100),c=setTimeout(()=>{n||(n=!0,clearInterval(s),window.removeEventListener(`popstate`,r),window.removeEventListener(`hashchange`,r),e({success:!1,waited:Date.now()-o,error:`Timeout waiting for URL to match "${t}". Current: ${window.location.href}`}))},i);window.addEventListener(`popstate`,r),window.addEventListener(`hashchange`,r)}).then(e=>{if(!e.success){n({error:e.error,waited:e.waited,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}});return}n({...N(`interactive`,15,void 0,!0),waited:e.waited})}),!0}case`WAIT_FOR_DOM_STABLE`:{let{stable:t=100,timeout:r=5e3}=e,i=Math.min(r,3e4),a=Date.now();return new Promise(e=>{let n=Date.now(),r=!1,o=()=>{r||Date.now()-n>=t&&(r=!0,s.disconnect(),clearTimeout(c),clearInterval(l),e({success:!0,waited:Date.now()-a}))},s=new MutationObserver(()=>{n=Date.now()}),c=setTimeout(()=>{r||(r=!0,s.disconnect(),clearInterval(l),e({success:!1,waited:Date.now()-a,error:`Timeout: DOM did not stabilize within ${i}ms`}))},i),l=setInterval(o,Math.max(10,Math.min(50,t/2)));s.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),o()}).then(e=>{if(!e.success){n({error:e.error,waited:e.waited,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}});return}n({...N(`interactive`,15,void 0,!0),waited:e.waited})}),!0}case`FORM_FILL`:{let{data:t}=e;if(!Array.isArray(t))return n({error:`data must be an array of {ref, value} pairs`}),!0;let r=M(),i=[];for(let e of t){let{ref:t,value:n}=e;if(!t){i.push({ref:t||`unknown`,success:!1,error:`Missing ref`});continue}let a=r[t];if(!a){i.push({ref:t,success:!1,error:`Element not found (run page.read first)`});continue}let o=a.element.deref();if(!o){delete r[t],i.push({ref:t,success:!1,error:`Element no longer exists`});continue}try{if(o instanceof HTMLInputElement){let e=o.type.toLowerCase();e===`checkbox`||e===`radio`?(o.checked=n===!0||n===`true`||n===`1`||n===`checked`,o.dispatchEvent(new Event(`change`,{bubbles:!0}))):(o.focus(),o.value=String(n),o.dispatchEvent(new Event(`input`,{bubbles:!0})),o.dispatchEvent(new Event(`change`,{bubbles:!0}))),i.push({ref:t,success:!0})}else o instanceof HTMLTextAreaElement?(o.focus(),o.value=String(n),o.dispatchEvent(new Event(`input`,{bubbles:!0})),o.dispatchEvent(new Event(`change`,{bubbles:!0})),i.push({ref:t,success:!0})):o instanceof HTMLSelectElement?(o.value=String(n),o.dispatchEvent(new Event(`change`,{bubbles:!0})),i.push({ref:t,success:!0})):o.isContentEditable?(o.focus(),o.textContent=String(n),o.dispatchEvent(new Event(`input`,{bubbles:!0})),i.push({ref:t,success:!0})):i.push({ref:t,success:!1,error:`Element is not fillable`})}catch(e){i.push({ref:t,success:!1,error:e instanceof Error?e.message:String(e)})}}let a=i.filter(e=>!e.success);return n({success:a.length===0,filled:i.filter(e=>e.success).length,failed:a.length,results:i}),!0}case`GET_FILE_INPUT_SELECTOR`:{let{ref:t}=e;if(!t)return n({error:`No ref provided`}),!0;let r=M(),i=r[t];if(!i)return n({error:`Element not found (run page.read first)`}),!0;let a=i.element.deref();if(!a)return delete r[t],n({error:`Element no longer exists`}),!0;if(!(a instanceof HTMLInputElement)||a.type!==`file`)return n({error:`Element is not a file input`}),!0;let o=`__pi_file_${Date.now()}`;return a.setAttribute(`data-pi-file-id`,o),n({selector:`[data-pi-file-id="${o}"]`}),!0}case`WAIT_FOR_NETWORK_IDLE`:{let{timeout:t=1e4}=e,r=Math.min(t,6e4),i=[`doubleclick.net`,`googlesyndication.com`,`googletagmanager.com`,`google-analytics.com`,`facebook.net`,`connect.facebook.net`,`analytics`,`ads`,`tracking`,`pixel`,`hotjar.com`,`clarity.ms`,`mixpanel.com`,`segment.com`,`newrelic.com`,`nr-data.net`,`/tracker/`,`/collector/`,`/beacon/`,`/telemetry/`,`/log/`,`/events/`,`/track.`,`/metrics/`],a=[`img`,`image`,`font`,`icon`],o=e=>i.some(t=>e.includes(t)),s=e=>{let t=e.initiatorType||`unknown`;return!!(a.includes(t)||/\.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf|eot)(\?|$)/i.test(e.name))},c=()=>{let e=performance.now();return performance.getEntriesByType(`resource`).filter(t=>{if(t.responseEnd!==0||t.name.startsWith(`data:`)||t.name.length>500||o(t.name))return!1;let n=e-t.startTime;return!(n>1e4||s(t)&&n>3e3)})},l=Date.now();return new Promise(e=>{let t=()=>{let n=c(),i=Date.now()-l;if(n.length===0){e({success:!0,waited:i});return}if(i>=r){e({success:!1,waited:i,pendingCount:n.length});return}setTimeout(t,100)};t()}).then(e=>{if(!e.success){n({error:`Network not idle after ${e.waited}ms (${e.pendingCount} requests pending)`,waited:e.waited,pageContent:``,viewport:{width:window.innerWidth,height:window.innerHeight}});return}n({...N(`interactive`,15,void 0,!0),waited:e.waited})}),!0}case`SEARCH_PAGE`:{let{term:t,caseSensitive:r,limit:i}=e,a=U(t,r||!1,i||10);n({query:t,count:a.length,matches:a});break}case`GET_ELEMENT_BOUNDS_FOR_ANNOTATION`:{let e=M(),t=[];for(let[n,r]of Object.entries(e)){let e=r.element.deref();if(!e)continue;let i=e.getBoundingClientRect();i.width<=0||i.height<=0||i.bottom<0||i.top>window.innerHeight||i.right<0||i.left>window.innerWidth||t.push({ref:n,tag:e.tagName.toLowerCase(),bounds:{x:i.x,y:i.y,width:i.width,height:i.height}})}n({elements:t});break}default:return!1}return!1});function U(e,t,n){let r=[],i=t?e:e.toLowerCase(),a=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),o=M(),s=0;for(;a.nextNode()&&r.length<n;){let c=a.currentNode,l=c.textContent||``,u=t?l:l.toLowerCase(),d=0;for(;(d=u.indexOf(i,d))!==-1&&r.length<n;){let t=c.parentElement;if(!t){d++;continue}let n=document.createRange();n.setStart(c,d),n.setEnd(c,Math.min(d+e.length,l.length));let i=n.getBoundingClientRect();if(i.width===0||i.height===0){d++;continue}let a=c.textContent||``,u=Math.max(0,d-30),f=Math.min(a.length,d+e.length+30),p=a.slice(u,f).trim(),m=null;for(let[e,n]of Object.entries(o)){let r=n.element.deref();if(r&&(r===t||r.contains(t))){m=e;break}}r.push({ref:`m${++s}`,text:a.slice(d,d+e.length),context:p,bounds:{x:Math.round(i.x),y:Math.round(i.y),width:Math.round(i.width),height:Math.round(i.height)},elementRef:m}),d++}}return r}
116
+ //# sourceMappingURL=index.js.map