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.
- package/README.md +208 -13
- package/dist/content/index.js +116 -0
- package/dist/content/index.js.map +1 -0
- package/dist/manifest.json +2 -11
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +261 -61
- package/dist/service-worker/index.js.map +1 -1
- package/native/abort.cjs +65 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +169 -0
- package/native/chatgpt-client.cjs +63 -30
- package/native/cli.cjs +947 -460
- package/native/client-transport.cjs +168 -0
- package/native/config.cjs +2 -2
- package/native/do-executor.cjs +25 -51
- package/native/do-parser.cjs +12 -0
- package/native/doctor.cjs +633 -0
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +244 -88
- package/native/grok-client.cjs +321 -212
- package/native/host-helpers.cjs +88 -16
- package/native/host-sessions.cjs +283 -0
- package/native/host.cjs +811 -616
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -62
- package/native/network-export.cjs +113 -0
- package/native/perplexity-client.cjs +46 -17
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +46 -0
- package/package.json +11 -9
- package/scripts/install-native-host.cjs +184 -51
- package/scripts/uninstall-native-host.cjs +93 -15
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +77 -22
- package/dist/content/accessibility-tree.js +0 -11
- package/dist/content/accessibility-tree.js.map +0 -1
- package/dist/content/visual-indicator.js +0 -111
- 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
|
-
|
|
|
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
|
|
186
|
-
surf type "email@example.com" --ref e12 #
|
|
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
|
|
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
|
|
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
|
-
#
|
|
355
|
+
# Target that window or its tab from later commands
|
|
254
356
|
surf click e5 --window-id 123456
|
|
255
|
-
surf read --
|
|
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-
|
|
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,
|
|
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
|
|
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
|
|
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
|