surf-cli 2.8.0 → 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 (42) hide show
  1. package/README.md +98 -4
  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 +2 -2
  14. package/native/chatgpt-client.cjs +47 -31
  15. package/native/cli.cjs +300 -204
  16. package/native/client-transport.cjs +168 -0
  17. package/native/do-executor.cjs +25 -44
  18. package/native/doctor.cjs +55 -5
  19. package/native/endpoint.cjs +174 -0
  20. package/native/file-transfer.cjs +734 -0
  21. package/native/gemini-client.cjs +156 -71
  22. package/native/grok-client.cjs +98 -89
  23. package/native/host-helpers.cjs +37 -12
  24. package/native/host-sessions.cjs +283 -0
  25. package/native/host.cjs +800 -620
  26. package/native/listener.cjs +20 -0
  27. package/native/mcp-server.cjs +60 -65
  28. package/native/network-export.cjs +113 -0
  29. package/native/perplexity-client.cjs +46 -17
  30. package/native/remote-auth.cjs +279 -0
  31. package/native/remote-transport.cjs +337 -0
  32. package/native/request-pending.cjs +148 -0
  33. package/native/socket-path.cjs +1 -1
  34. package/package.json +8 -6
  35. package/scripts/install-native-host.cjs +36 -5
  36. package/skills/README.md +11 -5
  37. package/skills/deep-x-research/SKILL.md +106 -0
  38. package/skills/surf/SKILL.md +31 -4
  39. package/dist/content/accessibility-tree.js +0 -11
  40. package/dist/content/accessibility-tree.js.map +0 -1
  41. package/dist/content/visual-indicator.js +0 -111
  42. package/dist/content/visual-indicator.js.map +0 -1
@@ -0,0 +1,148 @@
1
+ const { abortError } = require("./abort.cjs");
2
+
3
+ class RequestPendingMap extends Map {
4
+ constructor({ getRequest = () => undefined } = {}) {
5
+ super();
6
+ this.getRequest = getRequest;
7
+ this.drainWaiters = new Map();
8
+ }
9
+
10
+ set(id, data) {
11
+ const request = data.request || this.getRequest();
12
+ const cleanup = Boolean(data.cleanup || data.tool === "close_tab");
13
+ const entry = { ...data, id, request, cleanup, aborted: false, settled: false };
14
+ if (request) {
15
+ if (!request.pendingEntries) request.pendingEntries = new Set();
16
+ request.pendingEntries.add(entry);
17
+ if (cleanup) entry.abortCleanup = null;
18
+ else {
19
+ const onAbort = () => {
20
+ if (entry.abortNotified || entry.settled) return;
21
+ entry.abortNotified = true;
22
+ entry.aborted = true;
23
+ const error = abortError(request.signal);
24
+ if (entry.reject) entry.reject(error);
25
+ else entry.onAbort?.(error);
26
+ if (!entry.reject && !entry.onAbort) entry.onComplete?.({ error: error.message, cancelled: true });
27
+ };
28
+ entry.abortCleanup = () => request.signal.removeEventListener("abort", onAbort);
29
+ request.signal.addEventListener("abort", onAbort, { once: true });
30
+ if (request.signal.aborted) onAbort();
31
+ }
32
+ }
33
+ super.set(id, entry);
34
+ return this;
35
+ }
36
+
37
+ get(id) {
38
+ return super.get(id);
39
+ }
40
+
41
+ delete(id) {
42
+ const entry = super.get(id);
43
+ if (!entry) return false;
44
+ this.#removeEntry(entry);
45
+ return true;
46
+ }
47
+
48
+ #removeEntry(entry, notify = true) {
49
+ super.delete(entry.id);
50
+ entry.abortCleanup?.();
51
+ if (entry.tombstoneTimer) clearTimeout(entry.tombstoneTimer);
52
+ entry.request?.pendingEntries?.delete(entry);
53
+ if (notify) this.#notifyDrain(entry.request);
54
+ }
55
+
56
+ #notifyDrain(request) {
57
+ if (!request || request.pendingEntries?.size) return;
58
+ const waiters = this.drainWaiters.get(request);
59
+ if (!waiters) return;
60
+ this.drainWaiters.delete(request);
61
+ for (const waiter of waiters) waiter();
62
+ }
63
+
64
+ onDrain(request, callback) {
65
+ if (!request?.pendingEntries?.size) {
66
+ callback();
67
+ return;
68
+ }
69
+ const waiters = this.drainWaiters.get(request) || [];
70
+ waiters.push(callback);
71
+ this.drainWaiters.set(request, waiters);
72
+ }
73
+
74
+ resolve(id, value) {
75
+ const entry = this.get(id);
76
+ if (!entry) return false;
77
+ this.#removeEntry(entry, false);
78
+ try {
79
+ if (!entry.aborted && !entry.hardBoundary && !entry.settled) {
80
+ entry.settled = true;
81
+ if (entry.resolve) entry.resolve(value);
82
+ else if (entry.onComplete) entry.onComplete(value);
83
+ }
84
+ } finally {
85
+ this.#notifyDrain(entry.request);
86
+ }
87
+ return true;
88
+ }
89
+
90
+ expire(id, error) {
91
+ const entry = this.get(id);
92
+ if (!entry) return false;
93
+ if (!entry.settled) {
94
+ entry.settled = true;
95
+ entry.aborted = true;
96
+ entry.reject?.(error);
97
+ const request = entry.request;
98
+ const remaining = request ? Math.max(0, request.startedAt + request.deadlineMs - Date.now()) : 0;
99
+ entry.tombstoneTimer = setTimeout(() => this.delete(entry.id), remaining);
100
+ }
101
+ return true;
102
+ }
103
+
104
+ reject(id, error) {
105
+ const entry = this.get(id);
106
+ if (!entry) return false;
107
+ this.#removeEntry(entry, false);
108
+ try {
109
+ if (!entry.settled) {
110
+ entry.settled = true;
111
+ entry.reject?.(error);
112
+ }
113
+ } finally {
114
+ this.#notifyDrain(entry.request);
115
+ }
116
+ return true;
117
+ }
118
+
119
+ tombstoneAfterAbort(request) {
120
+ if (!request?.pendingEntries) return;
121
+ for (const entry of request.pendingEntries) {
122
+ if (entry.cleanup || entry.tombstoneTimer) continue;
123
+ const remaining = Math.max(0, request.startedAt + request.deadlineMs - Date.now());
124
+ entry.tombstoneTimer = setTimeout(() => this.delete(entry.id), remaining);
125
+ }
126
+ }
127
+
128
+ hardDeadline(request) {
129
+ if (!request) return;
130
+ request.hardBoundary = true;
131
+ for (const entry of [...(request.pendingEntries || [])]) {
132
+ entry.hardBoundary = true;
133
+ this.#removeEntry(entry);
134
+ if (!entry.settled) {
135
+ entry.settled = true;
136
+ entry.reject?.(abortError(request.signal, "Request timed out"));
137
+ }
138
+ }
139
+ }
140
+
141
+ clear() {
142
+ for (const entry of this.values()) this.#removeEntry(entry);
143
+ this.drainWaiters.clear();
144
+ super.clear();
145
+ }
146
+ }
147
+
148
+ module.exports = { RequestPendingMap };
@@ -4,7 +4,7 @@ const os = require("os");
4
4
  const IS_WIN = process.platform === "win32";
5
5
  const DEFAULT_SOCKET_PATH = IS_WIN ? "//./pipe/surf" : "/tmp/surf.sock";
6
6
  const SOCKET_PATH = process.env.SURF_SOCKET || DEFAULT_SOCKET_PATH;
7
- const SURF_TMP = IS_WIN ? path.join(os.tmpdir(), "surf") : "/tmp";
7
+ const SURF_TMP = process.env.SURF_TMP || (IS_WIN ? path.join(os.tmpdir(), "surf") : "/tmp");
8
8
 
9
9
  function getSocketTroubleshootingHint() {
10
10
  const lines = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surf-cli",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.",
5
5
  "keywords": [
6
6
  "chrome",
@@ -43,6 +43,7 @@
43
43
  "lint:test": "biome check test/",
44
44
  "format": "biome format --write .",
45
45
  "test": "vitest run",
46
+ "test:e2e:chrome": "node test/e2e/real-chrome.mjs",
46
47
  "test:watch": "vitest",
47
48
  "test:coverage": "vitest run --coverage",
48
49
  "test:ui": "vitest --ui",
@@ -56,16 +57,17 @@
56
57
  "crypto-browserify": "^3.12.1",
57
58
  "events": "^3.3.0",
58
59
  "stream-browserify": "^3.0.0",
59
- "vite-plugin-node-polyfills": "^0.25.0",
60
+ "vite-plugin-node-polyfills": "^0.28.0",
60
61
  "zod": "^4.3.6"
61
62
  },
62
63
  "devDependencies": {
63
- "@biomejs/biome": "^2.4.4",
64
- "@types/chrome": "^0.1.37",
64
+ "@biomejs/biome": "^2.5.4",
65
+ "@types/chrome": "^0.2.2",
65
66
  "@vitest/coverage-v8": "^4.1.9",
66
67
  "@vitest/ui": "^4.1.9",
67
- "typescript": "^6.0.3",
68
- "vite": "^7.3.1",
68
+ "puppeteer": "25.3.0",
69
+ "typescript": "^7.0.2",
70
+ "vite": "^8.1.4",
69
71
  "vitest": "^4.1.9"
70
72
  }
71
73
  }
@@ -3,6 +3,8 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
  const os = require("os");
5
5
  const { execFileSync, execSync } = require("child_process");
6
+ const { parseListenEndpoint } = require("../native/listener.cjs");
7
+ const { getStateDir, loadHostIdentity, loadRegistry } = require("../native/remote-auth.cjs");
6
8
 
7
9
  const HOST_NAME = "surf.browser.host";
8
10
 
@@ -164,7 +166,7 @@ function wslPathToWindowsPath(wslPath) {
164
166
  }
165
167
  }
166
168
 
167
- function createWrapper(wrapperDir, nodePath, hostPath, target = process.platform) {
169
+ function createWrapper(wrapperDir, nodePath, hostPath, target = process.platform, listen) {
168
170
  fs.mkdirSync(wrapperDir, { recursive: true });
169
171
 
170
172
  if (target === "wsl-windows") {
@@ -186,13 +188,19 @@ function createWrapper(wrapperDir, nodePath, hostPath, target = process.platform
186
188
  const hostDir = path.dirname(hostPath);
187
189
  const content = `#!/usr/bin/env bash
188
190
  cd "${hostDir}"
189
- exec "${nodePath}" "${hostPath}" "$@"
191
+ ${listen ? `: "\${SURF_LISTEN:=${listen}}"\nexport SURF_LISTEN\n` : ""}exec "${nodePath}" "${hostPath}" "$@"
190
192
  `;
191
193
  fs.writeFileSync(shPath, content);
192
194
  fs.chmodSync(shPath, "755");
193
195
  return shPath;
194
196
  }
195
197
 
198
+ function assertListenTargetSupported(listen, target) {
199
+ if (listen && (target === "win32" || target === "wsl-windows")) {
200
+ throw new Error("--listen is not supported for Windows native-host wrappers");
201
+ }
202
+ }
203
+
196
204
  function readExistingManifest(manifestPath) {
197
205
  if (!fs.existsSync(manifestPath)) return {};
198
206
  return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
@@ -268,7 +276,7 @@ function installWindowsRegistry(browser, extensionId, wrapperPath) {
268
276
 
269
277
  function parseArgs() {
270
278
  const args = process.argv.slice(2);
271
- const result = { extensionId: null, browsers: ["chrome"], target: "auto" };
279
+ const result = { extensionId: null, browsers: ["chrome"], target: "auto", listen: undefined };
272
280
 
273
281
  for (let i = 0; i < args.length; i++) {
274
282
  const arg = args[i];
@@ -281,6 +289,9 @@ function parseArgs() {
281
289
  }
282
290
  } else if (arg === "--target") {
283
291
  result.target = args[++i];
292
+ } else if (arg === "--listen") {
293
+ result.listen = args[++i];
294
+ if (!result.listen || result.listen.startsWith("--")) throw new Error("--listen requires a Tailnet IP and port");
284
295
  } else if (arg === "--help" || arg === "-h") {
285
296
  printHelp();
286
297
  process.exit(0);
@@ -307,17 +318,24 @@ Options:
307
318
  Multiple: --browser chrome,brave
308
319
  --target Install target: auto, linux, windows
309
320
  On WSL2, auto installs for Windows Chrome. Use linux for WSLg/Linux browsers.
321
+ --listen <tailscale-ip>:<port>
322
+ Persist an authenticated Tailnet-only listener endpoint.
323
+ Requires at least one surf remote authorize client first.
324
+ Supports Tailscale IPv4 or IPv6 addresses; POSIX wrappers only.
310
325
 
311
326
  Examples:
312
327
  node install-native-host.cjs abcdefghijklmnopabcdefghijklmnop
313
328
  node install-native-host.cjs abcdefghijklmnop --browser brave
314
329
  node install-native-host.cjs abcdefghijklmnop --browser all
315
330
  node install-native-host.cjs abcdefghijklmnop --target linux
331
+ node install-native-host.cjs abcdefghijklmnop --listen 100.64.1.2:4321
316
332
  `);
317
333
  }
318
334
 
319
335
  function main() {
320
- const { extensionId, browsers, target } = parseArgs();
336
+ let parsed;
337
+ try { parsed = parseArgs(); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); }
338
+ const { extensionId, browsers, target, listen } = parsed;
321
339
 
322
340
  if (!extensionId) {
323
341
  console.error("Error: Extension ID required");
@@ -331,6 +349,17 @@ function main() {
331
349
  console.error("Expected 32 lowercase letters (a-p)");
332
350
  process.exit(1);
333
351
  }
352
+ let listener;
353
+ try {
354
+ listener = listen ? parseListenEndpoint(listen).display : undefined;
355
+ if (listener) {
356
+ const stateDir = getStateDir();
357
+ loadHostIdentity(stateDir);
358
+ if (loadRegistry(stateDir).clients.length === 0) {
359
+ throw new Error("--listen requires at least one authorized remote client; run `surf remote authorize <label> --output <path>` first");
360
+ }
361
+ }
362
+ } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); }
334
363
 
335
364
  if (!["auto", "linux", "windows"].includes(target)) {
336
365
  console.error("Error: Invalid --target value. Expected auto, linux, or windows");
@@ -349,6 +378,7 @@ function main() {
349
378
  }
350
379
 
351
380
  const effectiveTarget = runningInWsl && target !== "linux" ? "wsl-windows" : process.platform;
381
+ try { assertListenTargetSupported(listen, effectiveTarget); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); }
352
382
 
353
383
  const nodePath = findNode();
354
384
  if (!nodePath) {
@@ -377,7 +407,7 @@ function main() {
377
407
  console.log(`Wrapper dir: ${wrapperDir}`);
378
408
  console.log("");
379
409
 
380
- const wrapperPath = createWrapper(wrapperDir, nodePath, hostPath, effectiveTarget);
410
+ const wrapperPath = createWrapper(wrapperDir, nodePath, hostPath, effectiveTarget, listener);
381
411
  console.log(`Created wrapper: ${wrapperPath}`);
382
412
  console.log("");
383
413
 
@@ -419,4 +449,5 @@ if (require.main === module) {
419
449
  module.exports = {
420
450
  createWrapper,
421
451
  writeManifest,
452
+ assertListenTargetSupported,
422
453
  };
package/skills/README.md CHANGED
@@ -1,21 +1,27 @@
1
1
  # Surf Skills
2
2
 
3
- This directory contains skill files for AI coding agents.
3
+ This directory contains skill files for AI coding agents:
4
+
5
+ - **`surf/`** — the core browser-automation reference: every surf command, workflows, AI assistants, troubleshooting.
6
+ - **`deep-x-research/`** — a research procedure built on surf: exhaustive, multi-angle X (Twitter) research with categorized findings and full post-URL traceability. Requires x.com login in Chrome.
7
+
8
+ Install each skill folder the same way (symlink or copy).
4
9
 
5
10
  ## Pi Agent
6
11
 
7
- To use the surf skill with [Pi coding agent](https://github.com/badlogic/pi-mono):
12
+ To use a skill with [Pi coding agent](https://github.com/badlogic/pi-mono):
8
13
 
9
14
  ```bash
10
15
  # Option 1: Symlink (auto-updates)
11
16
  ln -s "$(pwd)/skills/surf" ~/.agents/skills/surf
17
+ ln -s "$(pwd)/skills/deep-x-research" ~/.agents/skills/deep-x-research
12
18
 
13
19
  # Option 2: Copy
14
- cp -r skills/surf ~/.agents/skills/
20
+ cp -r skills/surf skills/deep-x-research ~/.agents/skills/
15
21
  ```
16
22
 
17
- The skill will be available when pi detects browser automation tasks.
23
+ The skills will be available when pi detects browser automation or X research tasks.
18
24
 
19
25
  ## Other Agents
20
26
 
21
- The `SKILL.md` file is a comprehensive reference that can be adapted for other AI coding agents or used as documentation for LLM prompts.
27
+ Each `SKILL.md` file can be adapted for other AI coding agents (Claude Code, Codex) or used as documentation for LLM prompts — copy the skill folder into the agent's skills directory (e.g. `~/.claude/skills/`, `~/.agents/skills/`).
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: deep-x-research
3
+ description: Deep, exhaustive research on a topic across X (Twitter) by driving Grok (x.com/i/grok) through surf. Use when the user wants comprehensive X research on a concept, technique, trend, tool, or creator scene; needs categorized findings with every claim traceable to post URLs; or when a single Grok query is not enough.
4
+ ---
5
+
6
+ # Deep X Research
7
+
8
+ Research a topic across X by putting Grok to work from multiple angles — it runs keyword and semantic X searches and watches videos natively — then deliver categorized findings where every claim is traceable to a post URL.
9
+
10
+ Requires: surf installed and connected (`surf doctor`), Chrome logged into x.com. Command reference: the `surf` skill or `surf --help`.
11
+
12
+ **Quota:** X caps Grok requests (typically 15 per 20 hours on a standard plan). Every `surf grok` call spends one. Budget the session before the first query and make each query do multi-angle work — never spend a request on what a quota-free step can answer.
13
+
14
+ ## Steps
15
+
16
+ ### 1. Decompose the topic and budget the queries
17
+
18
+ Break the topic into angles: showcases/examples, techniques & tutorials, tools, notable creators, community discussion — adapt to the topic. Plan a Grok budget of **4-8 queries** covering every angle (combine related angles into one query rather than spending two). Done when each angle is assigned to a budgeted query.
19
+
20
+ ### 2. Grok sweep
21
+
22
+ Run the budgeted queries sequentially. Engineer each so Grok does the fan-out internally and returns traceable sources:
23
+
24
+ ```bash
25
+ # Broad pass — force multi-angle search and URLs
26
+ surf grok "Do deep research on TOPIC on X. Search both latest and top posts, keyword and semantic. Return the most relevant posts with full post URLs (x.com/user/status/ID) and a one-line description of each."
27
+
28
+ # Focused passes — one per remaining angle group
29
+ surf grok "TOPIC on X: tutorials, techniques, and the tools people use. Include post URLs for every example."
30
+
31
+ # Deepest pass — spend DeepSearch on the highest-value angle
32
+ surf grok "TOPIC: notable creators, how the trend is evolving, and the standout posts of the last 6 months. Post URLs required." --deep-search
33
+ ```
34
+
35
+ Record every post Grok cites: author, one-line gist, full `https://x.com/USER/status/ID` URL. If a response gives claims without URLs, the *next* query in the budget re-asks for sources — never leave an angle sourceless. Done when every planned angle has been queried and the final response adds no new relevant posts, or the budget is spent.
36
+
37
+ ### 3. Video pass (visual topics)
38
+
39
+ When the topic involves video, editing, or visual style, spend 1-3 budgeted queries having Grok analyze the strongest video posts — it can watch X videos natively:
40
+
41
+ ```bash
42
+ surf grok "Analyze the videos in these posts: URL1 URL2 URL3 — for each, describe the techniques, pacing, and style, and why it works."
43
+ ```
44
+
45
+ Batch several URLs per query to conserve budget. Done when each analyzed video has notes on what it shows and why it matters for the topic.
46
+
47
+ ### 4. Enrich and verify — quota-free
48
+
49
+ For each cited post, open it directly with surf (no Grok spend) to verify the URL resolves and harvest detail Grok didn't give:
50
+
51
+ ```bash
52
+ surf navigate "https://x.com/USER/status/ID" && surf wait 2
53
+ surf page.read --compact # engagement numbers, thread context
54
+ surf network | grep video.twimg # direct video URL after playback
55
+ ```
56
+
57
+ Done when every URL destined for the References section has been resolved (dead or hallucinated links dropped or replaced).
58
+
59
+ ### 5. Categorize and analyze
60
+
61
+ Group findings into categories that fit the topic. Extract trends: momentum on X, recurring techniques, notable creators, how the topic is evolving. Done when every recorded post is either placed in a category or deliberately dropped as irrelevant.
62
+
63
+ ### 6. Report with full traceability
64
+
65
+ ```md
66
+ # Deep Research on [Topic]
67
+
68
+ ## Summary
69
+ [2-4 paragraphs: state of the topic on X]
70
+
71
+ ## Key Trends
72
+ - ...
73
+
74
+ ## Categorized Findings
75
+ ### [Category]
76
+ - [Finding with inline post reference]
77
+
78
+ ## Notable Creators & Techniques
79
+ - ...
80
+
81
+ ## References
82
+ 1. [Author — one-line description]
83
+ https://x.com/USER/status/ID
84
+ Video: https://video.twimg.com/... (when captured)
85
+ ```
86
+
87
+ The report is done when **every post mentioned anywhere in it appears in References with its full, verified URL** — no bare @handles, no "a viral post showed…" without a link.
88
+
89
+ ## Fallback: direct search when the Grok quota is exhausted
90
+
91
+ The x.com search UI costs no Grok requests. Slower and keyword-only, but it keeps the sweep going:
92
+
93
+ ```bash
94
+ surf navigate "https://x.com/search?q=QUERY&f=live" && surf wait 3 # Latest
95
+ surf page.read --compact
96
+ surf scroll down 2000 # then page.read again — repeat to load more
97
+ ```
98
+
99
+ Modes via the `f=` param: **Top is the default (no `f` param)** — there is no `f=top`; `f=live` = Latest, `f=user` = People, `f=media` = Media. Operators compose into the URL-encoded `q=`: `"exact phrase"`, `filter:videos`, `min_faves:100`, `min_retweets:50`, `from:user`, `since:2026-01-01`, `until:2026-06-01`.
100
+
101
+ ## Troubleshooting
102
+
103
+ - Grok replies with a rate-limit message → quota exhausted; switch to the fallback sweep and tell the user when the quota resets.
104
+ - Grok queries fail outright → `surf grok --validate`, then retry with a model from the validation output (see the `surf` skill's AI troubleshooting section).
105
+ - Grok cites posts without URLs → re-ask in the next budgeted query; do not invent URLs.
106
+ - Search page shows a login wall → Chrome isn't logged into x.com; ask the user to log in.
@@ -15,6 +15,28 @@ On macOS, Chrome reads the native messaging manifest at `~/Library/Application S
15
15
 
16
16
  If a command reports `Socket connect failed`, run `surf doctor` first, then check the `Attempted socket:` line. Default sockets are `/tmp/surf.sock` on macOS/Linux/WSL2 and `//./pipe/surf` on Windows. If `SURF_SOCKET` is set, the browser-launched host and the shell running `surf` must use the same value.
17
17
 
18
+ ## Remote Surf
19
+
20
+ Remote clients require a per-client credential; Tailnet reachability alone is not authorization. On the POSIX browser host, authorize the client before installing the listener:
21
+
22
+ ```bash
23
+ surf remote authorize agent-macbook --output ~/agent-macbook.surf-credential.json
24
+ surf install <extension-id> --listen 100.101.102.103:4321
25
+ surf remote list
26
+ ```
27
+
28
+ Move the mode-0600 credential to the client through a secure channel. It grants full trusted Surf authority. Use it explicitly or through `SURF_REMOTE` and `SURF_REMOTE_CREDENTIAL`:
29
+
30
+ ```bash
31
+ surf --remote 100.101.102.103:4321 \
32
+ --remote-credential ~/.config/surf/agent-macbook.json \
33
+ page.read
34
+
35
+ surf remote revoke agent-macbook # Run on the browser host
36
+ ```
37
+
38
+ Remote paths are client-local by default. `local:./file` is explicit client-local syntax; only `remote:/absolute/path` accesses the browser host directly. Remote transfer supports one upload or ChatGPT/Gemini input and one screenshot, network-export, or Gemini image output. Limits are 256 MiB per file, 512 MiB and 32 files per connection, and 256 KiB decoded chunks. `record`, `aistudio.build`, smoke screenshot directories, directories, and multi-file inputs are not supported remotely. Successful action screenshots and failure `--auto-capture` diagnostics are transferred back to client-local paths.
39
+
18
40
  ## CLI Quick Reference
19
41
 
20
42
  ```bash
@@ -68,7 +90,7 @@ surf gemini "analyze" --file data.csv # Attach file
68
90
  surf gemini "a robot surfing" --generate-image /tmp/robot.png
69
91
  surf gemini "add sunglasses" --edit-image photo.jpg --output out.jpg
70
92
  surf gemini "summarize" --youtube "https://youtube.com/..."
71
- surf gemini "hello" --model gemini-2.5-flash # Models: gemini-3-pro (default), gemini-2.5-pro, gemini-2.5-flash
93
+ surf gemini "hello" --model gemini-3.5-flash # Models: gemini-3.1-pro (default), gemini-3.5-flash, gemini-3.1-flash-lite
72
94
  surf gemini "wide banner" --generate-image /tmp/banner.png --aspect-ratio 16:9
73
95
  ```
74
96
 
@@ -89,6 +111,8 @@ surf grok "find viral AI posts" --deep-search # DeepSearch mode
89
111
  surf grok "quick question" --model fast # Models: auto, fast, expert, grok-4.20-beta
90
112
  ```
91
113
 
114
+ For exhaustive, multi-angle X research with categorized findings and full post-URL traceability, use the `deep-x-research` skill (`skills/deep-x-research/`) instead of a single Grok query.
115
+
92
116
  **Grok Validation & Troubleshooting:**
93
117
  ```bash
94
118
  # Validate Grok UI and check available models (no query sent)
@@ -158,6 +182,7 @@ surf tab.list
158
182
  surf tab.new "https://google.com"
159
183
  surf tab.switch 12345
160
184
  surf tab.close 12345
185
+ surf tab.move 12345 --to-window 67890
161
186
  surf tab.reload # Reload current tab
162
187
 
163
188
  # Named tabs (aliases)
@@ -207,12 +232,13 @@ Use `window.new`, `--window-id`, `--tab-id`, and named tabs to keep parallel age
207
232
  ## Input Methods
208
233
 
209
234
  ```bash
210
- # CDP method (real events) - default
235
+ # CDP method (real events) types at the current focus
211
236
  surf type --text "hello"
212
237
  surf click --x 100 --y 200
213
238
 
214
- # JS method (DOM manipulation) - for contenteditable
215
- surf type --text "hello" --selector "#input" --method js
239
+ # Selector/ref targets use frame-aware DOM input
240
+ surf type "hello" --into "#input"
241
+ surf type "hello" --ref e5
216
242
 
217
243
  # Keys
218
244
  surf key Enter
@@ -233,6 +259,7 @@ surf animate-audit --selector ".thing" --duration 2000 --fps 10 # JSON animatio
233
259
  surf page.read --ref e5 # Get specific element details
234
260
  surf page.read --depth 3 # Limit tree depth
235
261
  surf page.read --compact # Minimal output for LLM efficiency
262
+ surf page.read --max-bytes 2000 # Cap visible text at a UTF-8 byte boundary
236
263
  surf page.text # Plain text content only
237
264
  surf page.state # Modals, loading state, scroll info
238
265
  ```
@@ -1,11 +0,0 @@
1
- const J=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"]);function Q(i){const g=i.tagName.toLowerCase();if(["button","input","select","textarea"].includes(g))return!i.disabled;if(g==="a"&&i.hasAttribute("href"))return!0;if(i.hasAttribute("tabindex")){const r=parseInt(i.getAttribute("tabindex")||"",10);return!isNaN(r)&&r>=0}return i.getAttribute("contenteditable")==="true"}function Z(i){const g=i.getAttribute("role");if(!g)return null;const r=g.split(/\s+/).filter(e=>e);for(const e of r)if(J.has(e))return e;return null}function X(i){const g=i.tagName.toLowerCase(),r=i.getAttribute("type"),e={a:o=>o.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:o=>o.closest("article, aside, main, nav, section")?"generic":"contentinfo",form:o=>o.hasAttribute("aria-label")||o.hasAttribute("aria-labelledby")?"form":"generic",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",header:o=>o.closest("article, aside, main, nav, section")?"generic":"banner",hr:"separator",img:o=>o.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:o=>o.hasAttribute("aria-label")||o.hasAttribute("aria-labelledby")?"region":"generic",select:o=>{const t=o;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(g==="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"}[r||""]||"textbox";const c=e[g];return typeof c=="function"?c(i):c||"generic"}function U(i){const g=Z(i);return!g||(g==="none"||g==="presentation")&&Q(i)?X(i):g}window.__piElementMap||(window.__piElementMap={});let ee=0;function q(i,g,r){const e=i._piRef;if(e&&e.role===g&&e.name===r)return e.ref;const c=`e${++ee}`;return i._piRef={role:g,name:r,ref:c},c}function Y(){const i=[];return document.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog[open]').forEach(r=>{const e=window.getComputedStyle(r);if(!(e.display!=="none"&&e.visibility!=="hidden"&&e.opacity!=="0"&&r.offsetWidth>0&&r.offsetHeight>0))return;const o=r.getAttribute("role")||"dialog";let t=r.getAttribute("aria-label")||r.querySelector('[role="heading"], h1, h2, h3')?.textContent?.trim()||"Dialog";t.length>100&&(t=t.substring(0,100)+"..."),i.push({type:o,description:`${o}: ${t}`,clearedBy:"computer(action=key, text=Escape)"})}),i}const j={wait(i){return new Promise(g=>setTimeout(g,i))},async waitForSelector(i,g={}){const{state:r="visible",timeout:e=2e4}=g,c=t=>{if(!t)return!1;const l=window.getComputedStyle(t);return l.display!=="none"&&l.visibility!=="hidden"&&l.opacity!=="0"&&t.offsetWidth>0&&t.offsetHeight>0},o=()=>{const t=document.querySelector(i);switch(r){case"attached":return t;case"detached":return t?null:document.body;case"hidden":return t?c(t)?null:t:document.body;default:return c(t)?t:null}};return new Promise((t,l)=>{const u=o();if(u){t(r==="detached"||r==="hidden"?null:u);return}const d=new MutationObserver(()=>{const a=o();a&&(d.disconnect(),clearTimeout(w),t(r==="detached"||r==="hidden"?null:a))}),w=setTimeout(()=>{d.disconnect(),l(new Error(`Timeout waiting for "${i}" to be ${r}`))},e);d.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["style","class","hidden"]})})},async waitForText(i,g={}){const{selector:r,timeout:e=2e4}=g,c=()=>{const o=r?document.querySelector(r):document.body;if(!o)return null;const t=document.createTreeWalker(o,NodeFilter.SHOW_TEXT);for(;t.nextNode();)if(t.currentNode.textContent?.includes(i))return t.currentNode.parentElement;return null};return new Promise((o,t)=>{const l=c();if(l){o(l);return}const u=new MutationObserver(()=>{const w=c();w&&(u.disconnect(),clearTimeout(d),o(w))}),d=setTimeout(()=>{u.disconnect(),t(new Error(`Timeout waiting for text "${i}"`))},e);u.observe(document.documentElement,{childList:!0,subtree:!0,characterData:!0})})},async waitForHidden(i,g=2e4){await j.waitForSelector(i,{state:"hidden",timeout:g})},getByRole(i,g={}){const{name:r}=g,e={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"]},c=[];c.push(...document.querySelectorAll(`[role="${i}"]`));const o=e[i];if(o)for(const l of o)c.push(...document.querySelectorAll(`${l}:not([role])`));if(!r)return c[0]||null;const t=r.toLowerCase().trim();for(const l of c){const u=l.getAttribute("aria-label")?.toLowerCase().trim(),d=l.textContent?.toLowerCase().trim(),w=l.getAttribute("title")?.toLowerCase().trim(),a=l.getAttribute("placeholder")?.toLowerCase().trim();if(u===t||d===t||w===t||a===t||u?.includes(t)||d?.includes(t))return l}return null}};window.__piHelpers||(window.__piHelpers=j,window.piHelpers=j);function I(){return window.__piElementMap}function B(i="interactive",g=15,r,e=!1,c=!1){try{let o=function(n){return U(n)},t=function(n){const h=n.tagName.toLowerCase(),_=n.getAttribute("aria-labelledby");if(_){const y=_.split(/\s+/).map(E=>document.getElementById(E)?.textContent?.trim()||"").filter(Boolean);if(y.length){const E=y.join(" ");return E.length>100?E.substring(0,100)+"...":E}}if(h==="select"){const y=n,E=y.querySelector("option[selected]")||(y.selectedIndex>=0?y.options[y.selectedIndex]:null);if(E?.textContent?.trim())return E.textContent.trim()}const L=n.getAttribute("aria-label");if(L?.trim())return L.trim();const N=n.getAttribute("placeholder");if(N?.trim())return N.trim();const D=n.getAttribute("title");if(D?.trim())return D.trim();const k=n.getAttribute("alt");if(k?.trim())return k.trim();if(n.id){const y=document.querySelector(`label[for="${n.id}"]`);if(y?.textContent?.trim())return y.textContent.trim()}if(h==="input"){const y=n,E=n.getAttribute("type")||"",H=n.getAttribute("value");if(E==="submit"&&H?.trim())return H.trim();if(y.value&&y.value.length<50&&y.value.trim())return y.value.trim()}if(["button","a","summary"].includes(h)){let y="";for(const E of n.childNodes)E.nodeType===Node.TEXT_NODE&&(y+=E.textContent);if(y.trim())return y.trim()}if(/^h[1-6]$/.test(h)){const y=n.textContent;if(y?.trim()){const E=y.trim();return E.length>100?E.substring(0,100)+"...":E}}if(h==="img")return"";let S="";for(const y of n.childNodes)y.nodeType===Node.TEXT_NODE&&(S+=y.textContent);if(S?.trim()&&S.trim().length>=3){const y=S.trim();return y.length>100?y.substring(0,100)+"...":y}return""},l=function(n){const h={},_=n.getAttribute("aria-checked");_==="true"?h.checked=!0:_==="false"?h.checked=!1:_==="mixed"?h.checked="mixed":n instanceof HTMLInputElement&&(n.type==="checkbox"||n.type==="radio")&&(n.type==="checkbox"&&n.indeterminate?h.checked="mixed":h.checked=n.checked);const L=n instanceof HTMLButtonElement||n instanceof HTMLInputElement||n instanceof HTMLSelectElement||n instanceof HTMLTextAreaElement;(n.getAttribute("aria-disabled")==="true"||L&&n.disabled||n.closest("fieldset:disabled"))&&(h.disabled=!0);const N=n.getAttribute("aria-expanded");N==="true"?h.expanded=!0:N==="false"&&(h.expanded=!1);const D=n.getAttribute("aria-pressed");D==="true"?h.pressed=!0:D==="false"?h.pressed=!1:D==="mixed"&&(h.pressed="mixed");const k=n.getAttribute("aria-selected");k==="true"?h.selected=!0:k==="false"&&(h.selected=!1);const S=n.getAttribute("aria-current");S&&S!=="false"&&(h.active=!0);const y=n.tagName.toLowerCase();if(/^h[1-6]$/.test(y))h.level=parseInt(y[1],10);else{const E=n.getAttribute("aria-level");E&&(h.level=parseInt(E,10))}return h},u=function(n){const h=[];return n.checked!==void 0&&h.push(n.checked==="mixed"?"[checked=mixed]":n.checked?"[checked]":"[unchecked]"),n.disabled&&h.push("[disabled]"),n.expanded!==void 0&&h.push(n.expanded?"[expanded]":"[collapsed]"),n.pressed!==void 0&&h.push(n.pressed==="mixed"?"[pressed=mixed]":n.pressed?"[pressed]":"[not-pressed]"),n.selected!==void 0&&h.push(n.selected?"[selected]":"[not-selected]"),n.active&&h.push("[active]"),n.level!==void 0&&h.push(`[level=${n.level}]`),h.join(" ")},d=function(n){const h=window.getComputedStyle(n);return h.display!=="none"&&h.visibility!=="hidden"&&h.opacity!=="0"&&n.offsetWidth>0&&n.offsetHeight>0},w=function(n){const h=n.tagName.toLowerCase();return["a","button","input","select","textarea","details","summary"].includes(h)||n.hasAttribute("onclick")||n.hasAttribute("tabindex")||n.getAttribute("role")==="button"||n.getAttribute("role")==="link"||n.getAttribute("contenteditable")==="true"},a=function(n){const h=n.tagName.toLowerCase();return["h1","h2","h3","h4","h5","h6","nav","main","header","footer","section","article","aside"].includes(h)||n.hasAttribute("role")},f=function(n){return window.getComputedStyle(n).cursor==="pointer"},m=function(n,h){const _=n.tagName.toLowerCase();if(["script","style","meta","link","title","noscript"].includes(_)||h.filter!=="all"&&n.getAttribute("aria-hidden")==="true"||h.filter!=="all"&&!d(n))return!1;if(h.filter!=="all"&&!h.refId){const N=n.getBoundingClientRect();if(!(N.top<window.innerHeight&&N.bottom>0&&N.left<window.innerWidth&&N.right>0))return!1}if(h.filter==="interactive")return w(n);if(w(n)||a(n)||t(n).length>0)return!0;const L=o(n);return h.compact&&new Set(["generic","group","region","article","section","complementary"]).has(L)&&t(n).length===0?!1:L!=="generic"&&L!=="img"},b=function(n,h){const _=[],L={filter:i,refId:r||null,compact:c},N=I(),D=m(n,L)||r&&h===0;if(D){const k=o(n),S=t(n),y=l(n),E=q(n,k,S);window.__piRefs[E]=n,N[E]={element:new WeakRef(n),role:k,name:S};let W=`${" ".repeat(h)}${k}`;if(S){const K=S.replace(/\s+/g," ").replace(/"/g,'\\"');W+=` "${K}"`}W+=` [${E}]`;const R=u(y);R&&(W+=` ${R}`),f(n)&&(W+=" [cursor=pointer]");const P=n.getAttribute("href");P&&(W+=` href="${P}"`);const G=n.getAttribute("type");G&&(W+=` type="${G}"`);const V=n.getAttribute("placeholder");V&&(W+=` placeholder="${V}"`),_.push(W)}if(h<g)for(const k of n.children)_.push(...b(k,D?h+1:h));return _},s=function(n){return n.replace(/\[e\d+\]/g,"[REF]")},p=function(n){const h=new Map;for(const _ of n){if(!_.trim())continue;const L=s(_);h.set(L,(h.get(L)||0)+1)}return h},T=function(n,h){const _=n.split(`
2
- `),L=h.split(`
3
- `),N=p(_),D=p(L),k=[],S=[];for(const H of L){if(!H.trim())continue;const W=s(H),R=N.get(W)||0;(D.get(W)||0)>R&&(k.push(H),N.set(W,R+1))}const y=p(_);for(const H of _){if(!H.trim())continue;const W=s(H),R=y.get(W)||0,P=D.get(W)||0;R>P&&(S.push(H),y.set(W,R-1))}if(k.length===0&&S.length===0)return{diff:"[NO CHANGES]",hasChanges:!1};const E=[];return S.length>0&&E.push(...S.map(H=>`- ${H}`)),k.length>0&&E.push(...k.map(H=>`+ ${H}`)),{diff:E.join(`
4
- `),hasChanges:!0}};window.__piRefs={};const A=I();let C=null;if(r){const n=A[r];if(!n)return{error:`Element with ref_id '${r}' not found. Use read_page without ref_id to get current elements.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};const h=n.element.deref();if(!h)return delete A[r],{error:`Element with ref_id '${r}' no longer exists. Use read_page without ref_id to get current elements.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};C=h}else C=document.body;const O=C?b(C,0):[];for(const n of Object.keys(A))A[n].element.deref()||delete A[n];const $=O.join(`
5
- `);if($.length>5e4)return{error:`Output exceeds 50000 character limit (${$.length} characters). Try using filter="interactive" or specify a ref_id.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};const M=Y();let x,v=!1;const F=window.__piLastSnapshot;return!e&&!r&&F&&Date.now()-F.timestamp<5e3&&(x=T(F.content,$).diff,v=!0),window.__piLastSnapshot={content:$,timestamp:Date.now()},{pageContent:$+`
6
-
7
- [Viewport: ${window.innerWidth}x${window.innerHeight}]`,diff:v?x:void 0,viewport:{width:window.innerWidth,height:window.innerHeight},modalStates:M.length>0?M: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:v}}catch(o){return{error:`Error generating accessibility tree: ${o instanceof Error?o.message:"Unknown error"}`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}}}}function z(i){return i.length?/[\n\r]/.test(i)||/^[\s]/.test(i)||/[\s]$/.test(i)||/[:"{}[\]]/.test(i)?'"'+i.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")+'"':i:'""'}function te(i="interactive",g=15){try{let r=function(s){return U(s)},e=function(s){const p=s.tagName.toLowerCase(),T=s.getAttribute("aria-labelledby");if(T){const x=T.split(/\s+/).map(v=>document.getElementById(v)?.textContent?.trim()||"").filter(Boolean);if(x.length){const v=x.join(" ");return v.length>100?v.substring(0,100)+"...":v}}if(p==="select"){const x=s,v=x.querySelector("option[selected]")||(x.selectedIndex>=0?x.options[x.selectedIndex]:null);if(v?.textContent?.trim())return v.textContent.trim()}const A=s.getAttribute("aria-label");if(A?.trim())return A.trim();const C=s.getAttribute("placeholder");if(C?.trim())return C.trim();const O=s.getAttribute("title");if(O?.trim())return O.trim();const $=s.getAttribute("alt");if($?.trim())return $.trim();if(s.id){const x=document.querySelector(`label[for="${s.id}"]`);if(x?.textContent?.trim())return x.textContent.trim()}if(p==="input"){const x=s,v=s.getAttribute("type")||"",F=s.getAttribute("value");if(v==="submit"&&F?.trim())return F.trim();if(x.value&&x.value.length<50&&x.value.trim())return x.value.trim()}if(["button","a","summary"].includes(p)){let x="";for(const v of s.childNodes)v.nodeType===Node.TEXT_NODE&&(x+=v.textContent);if(x.trim())return x.trim()}if(/^h[1-6]$/.test(p)){const x=s.textContent;if(x?.trim()){const v=x.trim();return v.length>100?v.substring(0,100)+"...":v}}if(p==="img")return"";let M="";for(const x of s.childNodes)x.nodeType===Node.TEXT_NODE&&(M+=x.textContent);if(M?.trim()&&M.trim().length>=3){const x=M.trim();return x.length>100?x.substring(0,100)+"...":x}return""},c=function(s){const p={},T=s.getAttribute("aria-checked");T==="true"?p.checked=!0:T==="false"?p.checked=!1:T==="mixed"?p.checked="mixed":s instanceof HTMLInputElement&&(s.type==="checkbox"||s.type==="radio")&&(s.type==="checkbox"&&s.indeterminate?p.checked="mixed":p.checked=s.checked);const A=s instanceof HTMLButtonElement||s instanceof HTMLInputElement||s instanceof HTMLSelectElement||s instanceof HTMLTextAreaElement;(s.getAttribute("aria-disabled")==="true"||A&&s.disabled||s.closest("fieldset:disabled"))&&(p.disabled=!0);const C=s.getAttribute("aria-expanded");C==="true"?p.expanded=!0:C==="false"&&(p.expanded=!1);const O=s.getAttribute("aria-pressed");O==="true"?p.pressed=!0:O==="false"?p.pressed=!1:O==="mixed"&&(p.pressed="mixed");const $=s.getAttribute("aria-selected");$==="true"?p.selected=!0:$==="false"&&(p.selected=!1);const M=s.getAttribute("aria-current");M&&M!=="false"&&(p.active=!0);const x=s.tagName.toLowerCase();if(/^h[1-6]$/.test(x))p.level=parseInt(x[1],10);else{const v=s.getAttribute("aria-level");v&&(p.level=parseInt(v,10))}return p},o=function(s){const p=[];return s.checked!==void 0&&p.push(s.checked==="mixed"?"[checked=mixed]":s.checked?"[checked]":"[unchecked]"),s.disabled&&p.push("[disabled]"),s.expanded!==void 0&&p.push(s.expanded?"[expanded]":"[collapsed]"),s.pressed!==void 0&&p.push(s.pressed==="mixed"?"[pressed=mixed]":s.pressed?"[pressed]":"[not-pressed]"),s.selected!==void 0&&p.push(s.selected?"[selected]":"[not-selected]"),s.active&&p.push("[active]"),s.level!==void 0&&p.push(`[level=${s.level}]`),p.join(" ")},t=function(s){const p=window.getComputedStyle(s);return p.display!=="none"&&p.visibility!=="hidden"&&p.opacity!=="0"&&s.offsetWidth>0&&s.offsetHeight>0},l=function(s){const p=s.tagName.toLowerCase();return["a","button","input","select","textarea","details","summary"].includes(p)||s.hasAttribute("onclick")||s.hasAttribute("tabindex")||s.getAttribute("role")==="button"||s.getAttribute("role")==="link"||s.getAttribute("contenteditable")==="true"},u=function(s){const p=s.tagName.toLowerCase();return["h1","h2","h3","h4","h5","h6","nav","main","header","footer","section","article","aside"].includes(p)||s.hasAttribute("role")},d=function(s){return window.getComputedStyle(s).cursor==="pointer"},w=function(s,p,T,A){let C=s;p&&(C+=" "+z(p));const O=q(T,s,p);window.__piRefs[O]=T,C+=` [ref=${O}]`;const $=o(A);return $&&(C+=` ${$}`),d(T)&&(C+=" [cursor=pointer]"),C},a=function(s){const p={},T=s.getAttribute("href");T&&(p.url=T);const A=s.getAttribute("placeholder");return A&&(p.placeholder=A),p},f=function(s,p,T){if(p>g)return;const A=s.tagName.toLowerCase();if(["script","style","meta","link","title","noscript"].includes(A)||i!=="all"&&s.getAttribute("aria-hidden")==="true"||i!=="all"&&!t(s))return;if(i!=="all"){const n=s.getBoundingClientRect();if(!(n.top<window.innerHeight&&n.bottom>0&&n.left<window.innerWidth&&n.right>0))return}const C=r(s),O=e(s),$=c(s),M=l(s),x=u(s),v=O.length>0;let F;if(i==="interactive"?F=M:i==="all"?F=!0:F=M||x||v||C!=="generic"&&C!=="img",F){const n=" ".repeat(p),h=w(C,O,s,$),_=a(s),L=[];for(const k of s.children)L.push(k);const N=L.length>0,D=Object.keys(_).length>0;if(!N&&!D)m.push(`${n}- ${h}`);else{m.push(`${n}- ${h}:`);for(const[k,S]of Object.entries(_))m.push(`${n} - /${k}: ${z(S)}`);for(const k of L)f(k,p+1,!0)}}else for(const n of s.children)f(n,p,T)};window.__piRefs={};const m=[];f(document.body,0,!1);const b=m.join(`
8
- `);return b.length>5e4?{error:`Output exceeds 50000 character limit (${b.length} characters). Try using filter="interactive".`,yaml:"",viewport:{width:window.innerWidth,height:window.innerHeight}}:{yaml:b+`
9
-
10
- [Viewport: ${window.innerWidth}x${window.innerHeight}]`,viewport:{width:window.innerWidth,height:window.innerHeight}}}catch(r){return{error:`Error generating YAML tree: ${r instanceof Error?r.message:"Unknown error"}`,yaml:"",viewport:{width:window.innerWidth,height:window.innerHeight}}}}function re(i){const g=I(),r=g[i];let e;if(r&&(e=r.element.deref(),e||delete g[i]),!e&&window.__piRefs&&(e=window.__piRefs[i]),!e)return{x:0,y:0,error:`Element ${i} not found. Use read_page to get current elements.`};const c=e.getBoundingClientRect(),o=Math.round(c.left+c.width/2),t=Math.round(c.top+c.height/2);return{x:o,y:t}}function ne(i,g){const r=I(),e=r[i];let c;if(e&&(c=e.element.deref(),c||delete r[i]),!c&&window.__piRefs&&(c=window.__piRefs[i]),!c)return{success:!1,error:`Element ${i} not found. Use read_page to get current elements.`};const o=c.tagName.toLowerCase();try{if(o==="input"){const t=c,l=t.type.toLowerCase();l==="checkbox"||l==="radio"?(t.checked=!!g,t.dispatchEvent(new Event("change",{bubbles:!0}))):(t.value=String(g),t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})))}else if(o==="textarea"){const t=c;t.value=String(g),t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0}))}else if(o==="select"){const t=c,l=String(g);let u=!1;for(const d of t.options)if(d.value===l||d.textContent?.trim()===l){t.value=d.value,u=!0;break}if(!u)return{success:!1,error:`Option "${g}" not found in select element ${i}`};t.dispatchEvent(new Event("change",{bubbles:!0}))}else if(c.getAttribute("contenteditable")==="true")c.textContent=String(g),c.dispatchEvent(new Event("input",{bubbles:!0}));else return{success:!1,error:`Element ${i} (${o}) is not a form field`};return{success:!0}}catch(t){return{success:!1,error:`Failed to set value: ${t instanceof Error?t.message:"Unknown error"}`}}}function ie(){try{const i=document.querySelector("article"),g=document.querySelector("main");return{text:(i||g||document.body).textContent?.replace(/\s+/g," ").trim().substring(0,5e4)||"",title:document.title,url:window.location.href}}catch(i){return{text:"",title:"",url:"",error:`Failed to extract text: ${i instanceof Error?i.message:"Unknown error"}`}}}function oe(i){const g=I(),r=g[i];let e;return r&&(e=r.element.deref(),e||delete g[i]),!e&&window.__piRefs&&(e=window.__piRefs[i]),e?(e.scrollIntoView({behavior:"smooth",block:"center"}),{success:!0}):{success:!1,error:`Element ${i} not found. Run read_page to get current element refs.`}}function se(i,g,r,e="screenshot.png"){try{const c=atob(i),o=new ArrayBuffer(c.length),t=new Uint8Array(o);for(let f=0;f<c.length;f++)t[f]=c.charCodeAt(f);const l=new Blob([o],{type:"image/png"}),u=new File([l],e,{type:"image/png"});let d=null;if(g){const f=I(),m=f[g];if(m&&(d=m.element.deref(),d||delete f[g]),!d&&window.__piRefs&&(d=window.__piRefs[g]),!d)return{success:!1,error:`Element ${g} not found. Run read_page to get current element refs.`}}else if(r&&(d=document.elementFromPoint(r[0],r[1]),!d))return{success:!1,error:`No element at (${r[0]}, ${r[1]})`};if(!d)return{success:!1,error:"No target element"};if(d.tagName==="INPUT"&&d.type==="file"){const f=d,m=new DataTransfer;return m.items.add(u),f.files=m.files,f.dispatchEvent(new Event("change",{bubbles:!0})),{success:!0}}const w=new DataTransfer;w.items.add(u);const a=new DragEvent("drop",{bubbles:!0,cancelable:!0,dataTransfer:w});return d.dispatchEvent(a),{success:!0}}catch(c){return{success:!1,error:c instanceof Error?c.message:"Upload failed"}}}chrome.runtime.onMessage.addListener((i,g,r)=>{switch(i.type){case"GENERATE_ACCESSIBILITY_TREE":{const e=i.options||{};if(e.format==="yaml"){const c=te(e.filter||"interactive",e.depth??15),o=Y();c.error?r({error:c.error,pageContent:"",viewport:c.viewport}):r({pageContent:c.yaml,viewport:c.viewport,modalStates:o.length>0?o: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{const c=B(e.filter||"interactive",e.depth??15,e.refId,e.forceFullSnapshot??!1,e.compact??!1);r(c)}break}case"GET_ELEMENT_COORDINATES":{const e=re(i.ref);r(e);break}case"CLICK_ELEMENT":{const e=I(),c=e[i.ref];let o;if(c&&(o=c.element.deref(),o||delete e[i.ref]),!o&&window.__piRefs&&(o=window.__piRefs[i.ref]),!o){r({error:`Element ${i.ref} not found. Use read_page to get current elements.`});break}if(i.button==="triple"){const t=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window,detail:3});o.dispatchEvent(t)}else i.button==="double"?o.dispatchEvent(new MouseEvent("dblclick",{bubbles:!0,cancelable:!0,view:window})):i.button==="right"?o.dispatchEvent(new MouseEvent("contextmenu",{bubbles:!0,cancelable:!0,view:window})):o.click();r({success:!0});break}case"FORM_INPUT":{const e=ne(i.ref,i.value);r(e);break}case"EVAL_IN_PAGE":{try{const e=document.createElement("script");e.textContent=`(function() { ${i.code} })();`,document.documentElement.appendChild(e),e.remove(),r({success:!0})}catch(e){r({success:!1,error:e instanceof Error?e.message:String(e)})}break}case"GET_PAGE_TEXT":{const e=ie();r(e);break}case"GET_FRAME_BY_SELECTOR":{try{const e=document.querySelector(i.selector);if(!e||e.tagName.toLowerCase()!=="iframe"){r({error:`No iframe found with selector "${i.selector}"`});break}r({url:e.src,name:e.name||void 0})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_FRAME_NAME":{try{r({name:window.name||null})}catch{r({name:null})}break}case"LOCATE_ROLE":{try{const{role:e,name:c,all:o}=i,t=I(),u={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"]']}[e]||[`[role="${e}"]`],d=[];for(const m of u)try{d.push(...document.querySelectorAll(m))}catch{}const w=d.filter(m=>{const b=window.getComputedStyle(m);return b.display!=="none"&&b.visibility!=="hidden"&&m.offsetWidth>0&&m.offsetHeight>0});let a=w;if(c){const m=c.toLowerCase();a=w.filter(b=>{const s=b.getAttribute("aria-label")?.toLowerCase(),p=b.textContent?.trim().toLowerCase(),T=b.getAttribute("title")?.toLowerCase(),A=b.placeholder?.toLowerCase(),C=b.value?.toLowerCase();return s?.includes(m)||p?.includes(m)||T?.includes(m)||A?.includes(m)||C?.includes(m)})}if(a.length===0){r({error:`No element found with role "${e}"${c?` and name "${c}"`:""}`});break}const f=a.map(m=>{const b=q(m,e,c||"");return window.__piRefs=window.__piRefs||{},window.__piRefs[b]=m,t[b]={element:new WeakRef(m),role:e,name:c||""},{ref:b,text:m.textContent?.trim().slice(0,50)}});r(o?{matches:f}:{ref:f[0].ref,text:f[0].text})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"LOCATE_TEXT":{try{const{text:e,exact:c}=i,o=I(),t=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),l=[];for(;t.nextNode();){const a=t.currentNode.textContent||"";if(c?a.trim()===e:a.toLowerCase().includes(e.toLowerCase())){const m=t.currentNode.parentElement;if(m&&!l.includes(m)){const b=window.getComputedStyle(m);b.display!=="none"&&b.visibility!=="hidden"&&l.push(m)}}}if(l.length===0){r({error:`No element found with text "${e}"`});break}const u=l.sort((a,f)=>(a.textContent?.length||0)-(f.textContent?.length||0))[0],d=U(u),w=q(u,d,e);window.__piRefs=window.__piRefs||{},window.__piRefs[w]=u,o[w]={element:new WeakRef(u),role:d,name:e},r({ref:w,text:u.textContent?.trim().slice(0,50)})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"LOCATE_LABEL":{try{const{label:e}=i,c=I(),o=document.querySelectorAll("label");let t=null;for(const d of o)if(d.textContent?.trim().toLowerCase()?.includes(e.toLowerCase())){const a=d.getAttribute("for");if(a&&(t=document.getElementById(a)),t||(t=d.querySelector("input, select, textarea")),t)break}if(!t){const d=e.toLowerCase();t=document.querySelector(`input[aria-label*="${e}" i], input[placeholder*="${e}" i], textarea[aria-label*="${e}" i], textarea[placeholder*="${e}" i], select[aria-label*="${e}" i]`)}if(!t){r({error:`No form field found with label "${e}"`});break}const l=U(t),u=q(t,l,e);window.__piRefs=window.__piRefs||{},window.__piRefs[u]=t,c[u]={element:new WeakRef(t),role:l,name:e},r({ref:u,label:e})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_ELEMENT_STYLES":{try{const{selector:e}=i,c=I(),o=t=>{const l=getComputedStyle(t),u=t.getBoundingClientRect();return{tag:t.tagName.toLowerCase(),text:t.innerText?.trim().slice(0,80)||null,box:{x:Math.round(u.x),y:Math.round(u.y),width:Math.round(u.width),height:Math.round(u.height)},styles:{fontSize:l.fontSize,fontWeight:l.fontWeight,fontFamily:l.fontFamily.split(",")[0].trim().replace(/"/g,""),color:l.color,backgroundColor:l.backgroundColor,borderRadius:l.borderRadius,border:l.border!=="none"&&l.borderWidth!=="0px"?l.border:null,boxShadow:l.boxShadow!=="none"?l.boxShadow:null,padding:l.padding}}};if(/^e\d+$/.test(e)){const t=c[e];let l;if(t&&(l=t.element.deref(),l||delete c[e]),!l&&window.__piRefs&&(l=window.__piRefs[e]),!l){r({error:`Element ${e} not found`});break}r({styles:[o(l)]})}else{const t=document.querySelectorAll(e);if(t.length===0){r({error:`No elements found matching "${e}"`});break}const l=Array.from(t).map(o);r({styles:l})}}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"SELECT_OPTION":{try{const{selector:e,values:c,by:o}=i,t=I();let l=null;if(/^e\d+$/.test(e)){const a=t[e];let f;if(a&&(f=a.element.deref(),f||delete t[e]),!f&&window.__piRefs&&(f=window.__piRefs[e]),!f){r({error:`Element ${e} not found`});break}if(f.tagName!=="SELECT"){r({error:`Element ${e} is not a <select>`});break}l=f}else{if(l=document.querySelector(e),!l){r({error:`No element found matching "${e}"`});break}if(l.tagName!=="SELECT"){r({error:`Element "${e}" is not a <select>`});break}}if(l.multiple)for(const a of l.options)a.selected=!1;const u=[],d=[],w=l.multiple?c:[c[0]];for(const a of w){let f=!1;for(const m of l.options){let b=!1;if(o==="index"?b=m.index===parseInt(a,10):o==="label"?b=m.text.toLowerCase().includes(a.toLowerCase()):b=m.value===a,b){m.selected=!0,u.push(m.value),f=!0;break}}f||d.push(a)}l.dispatchEvent(new Event("change",{bubbles:!0})),d.length>0?r({selected:u,warning:`Values not found: ${d.join(", ")}`}):r({selected:u})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_ELEMENT_TEXT":{try{const{ref:e}=i,c=I(),o=c[e];let t;if(o&&(t=o.element.deref(),t||delete c[e]),!t&&window.__piRefs&&(t=window.__piRefs[e]),!t){r({error:`Element ${e} not found`});break}r({text:t.textContent?.trim()||""})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"SCROLL_TO_ELEMENT":{const e=oe(i.ref);r(e);break}case"UPLOAD_IMAGE":{const e=se(i.base64,i.ref,i.coordinate,i.filename);r(e);break}case"WAIT_FOR_ELEMENT":{const{selector:e,state:c="visible",timeout:o=2e4}=i,t=Math.min(o,6e4),l=a=>{if(!a)return!1;const f=window.getComputedStyle(a);return f.display!=="none"&&f.visibility!=="hidden"&&f.opacity!=="0"&&a.offsetWidth>0&&a.offsetHeight>0},u=()=>{const a=document.querySelector(e);switch(c){case"attached":return!!a;case"detached":return!a;case"hidden":return!a||!l(a);default:return l(a)}},d=Date.now();return new Promise(a=>{if(u()){a({success:!0,waited:Date.now()-d});return}const f=new MutationObserver(()=>{u()&&(f.disconnect(),clearTimeout(m),a({success:!0,waited:Date.now()-d}))}),m=setTimeout(()=>{f.disconnect(),a({success:!1,waited:Date.now()-d,error:`Timeout waiting for "${e}" to be ${c}`})},t);f.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["style","class","hidden","disabled"]})}).then(a=>{if(!a.success){r({error:a.error,waited:a.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const f=B("interactive",15,void 0,!0);r({...f,waited:a.waited})}),!0}case"WAIT_FOR_URL":{const{pattern:e,timeout:c=2e4}=i,o=Math.min(c,6e4),t=d=>{if(e.includes("*")){const w=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"<<<GLOBSTAR>>>").replace(/\*/g,"[^/]*").replace(/<<<GLOBSTAR>>>/g,".*");return new RegExp(`^${w}$`).test(d)}return d.includes(e)},l=Date.now();return new Promise(d=>{if(t(window.location.href)){d({success:!0,waited:Date.now()-l});return}let w=!1;const a=()=>{w||t(window.location.href)&&(w=!0,clearInterval(f),clearTimeout(m),window.removeEventListener("popstate",a),window.removeEventListener("hashchange",a),d({success:!0,waited:Date.now()-l}))},f=setInterval(a,100),m=setTimeout(()=>{w||(w=!0,clearInterval(f),window.removeEventListener("popstate",a),window.removeEventListener("hashchange",a),d({success:!1,waited:Date.now()-l,error:`Timeout waiting for URL to match "${e}". Current: ${window.location.href}`}))},o);window.addEventListener("popstate",a),window.addEventListener("hashchange",a)}).then(d=>{if(!d.success){r({error:d.error,waited:d.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const w=B("interactive",15,void 0,!0);r({...w,waited:d.waited})}),!0}case"WAIT_FOR_DOM_STABLE":{const{stable:e=100,timeout:c=5e3}=i,o=Math.min(c,3e4),t=Date.now();return new Promise(u=>{let d=Date.now(),w=!1;const a=()=>{if(w)return;Date.now()-d>=e&&(w=!0,f.disconnect(),clearTimeout(m),clearInterval(b),u({success:!0,waited:Date.now()-t}))},f=new MutationObserver(()=>{d=Date.now()}),m=setTimeout(()=>{w||(w=!0,f.disconnect(),clearInterval(b),u({success:!1,waited:Date.now()-t,error:`Timeout: DOM did not stabilize within ${o}ms`}))},o),b=setInterval(a,Math.max(10,Math.min(50,e/2)));f.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),a()}).then(u=>{if(!u.success){r({error:u.error,waited:u.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const d=B("interactive",15,void 0,!0);r({...d,waited:u.waited})}),!0}case"FORM_FILL":{const{data:e}=i;if(!Array.isArray(e))return r({error:"data must be an array of {ref, value} pairs"}),!0;const c=I(),o=[];for(const l of e){const{ref:u,value:d}=l;if(!u){o.push({ref:u||"unknown",success:!1,error:"Missing ref"});continue}const w=c[u];if(!w){o.push({ref:u,success:!1,error:"Element not found (run page.read first)"});continue}const a=w.element.deref();if(!a){delete c[u],o.push({ref:u,success:!1,error:"Element no longer exists"});continue}try{if(a instanceof HTMLInputElement){const f=a.type.toLowerCase();if(f==="checkbox"||f==="radio"){const m=d===!0||d==="true"||d==="1"||d==="checked";a.checked=m,a.dispatchEvent(new Event("change",{bubbles:!0}))}else a.focus(),a.value=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),a.dispatchEvent(new Event("change",{bubbles:!0}));o.push({ref:u,success:!0})}else a instanceof HTMLTextAreaElement?(a.focus(),a.value=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),a.dispatchEvent(new Event("change",{bubbles:!0})),o.push({ref:u,success:!0})):a instanceof HTMLSelectElement?(a.value=String(d),a.dispatchEvent(new Event("change",{bubbles:!0})),o.push({ref:u,success:!0})):a.isContentEditable?(a.focus(),a.textContent=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),o.push({ref:u,success:!0})):o.push({ref:u,success:!1,error:"Element is not fillable"})}catch(f){o.push({ref:u,success:!1,error:f instanceof Error?f.message:String(f)})}}const t=o.filter(l=>!l.success);return r({success:t.length===0,filled:o.filter(l=>l.success).length,failed:t.length,results:o}),!0}case"GET_FILE_INPUT_SELECTOR":{const{ref:e}=i;if(!e)return r({error:"No ref provided"}),!0;const c=I(),o=c[e];if(!o)return r({error:"Element not found (run page.read first)"}),!0;const t=o.element.deref();if(!t)return delete c[e],r({error:"Element no longer exists"}),!0;if(!(t instanceof HTMLInputElement)||t.type!=="file")return r({error:"Element is not a file input"}),!0;const l=`__pi_file_${Date.now()}`;return t.setAttribute("data-pi-file-id",l),r({selector:`[data-pi-file-id="${l}"]`}),!0}case"WAIT_FOR_NETWORK_IDLE":{const{timeout:e=1e4}=i,c=Math.min(e,6e4),o=["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/"],t=["img","image","font","icon"],l=f=>o.some(m=>f.includes(m)),u=f=>{const m=f.initiatorType||"unknown";return!!(t.includes(m)||/\.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf|eot)(\?|$)/i.test(f.name))},d=()=>{const f=performance.now();return performance.getEntriesByType("resource").filter(b=>{if(b.responseEnd!==0||b.name.startsWith("data:")||b.name.length>500||l(b.name))return!1;const s=f-b.startTime;return!(s>1e4||u(b)&&s>3e3)})},w=Date.now();return new Promise(f=>{const m=()=>{const b=d(),s=Date.now()-w;if(b.length===0){f({success:!0,waited:s});return}if(s>=c){f({success:!1,waited:s,pendingCount:b.length});return}setTimeout(m,100)};m()}).then(f=>{if(!f.success){r({error:`Network not idle after ${f.waited}ms (${f.pendingCount} requests pending)`,waited:f.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const m=B("interactive",15,void 0,!0);r({...m,waited:f.waited})}),!0}case"SEARCH_PAGE":{const{term:e,caseSensitive:c,limit:o}=i,t=ce(e,c||!1,o||10);r({query:e,count:t.length,matches:t});break}case"GET_ELEMENT_BOUNDS_FOR_ANNOTATION":{const e=I(),c=[];for(const[o,t]of Object.entries(e)){const l=t.element.deref();if(!l)continue;const u=l.getBoundingClientRect();u.width<=0||u.height<=0||u.bottom<0||u.top>window.innerHeight||u.right<0||u.left>window.innerWidth||c.push({ref:o,tag:l.tagName.toLowerCase(),bounds:{x:u.x,y:u.y,width:u.width,height:u.height}})}r({elements:c});break}default:return!1}return!1});function ce(i,g,r){const e=[],c=g?i:i.toLowerCase(),o=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),t=I();let l=0;for(;o.nextNode()&&e.length<r;){const u=o.currentNode,d=u.textContent||"",w=g?d:d.toLowerCase();let a=0;for(;(a=w.indexOf(c,a))!==-1&&e.length<r;){const f=u.parentElement;if(!f){a++;continue}const m=document.createRange();m.setStart(u,a),m.setEnd(u,Math.min(a+i.length,d.length));const b=m.getBoundingClientRect();if(b.width===0||b.height===0){a++;continue}const s=u.textContent||"",p=Math.max(0,a-30),T=Math.min(s.length,a+i.length+30),A=s.slice(p,T).trim();let C=null;for(const[O,$]of Object.entries(t)){const M=$.element.deref();if(M&&(M===f||M.contains(f))){C=O;break}}e.push({ref:`m${++l}`,text:s.slice(a,a+i.length),context:A,bounds:{x:Math.round(b.x),y:Math.round(b.y),width:Math.round(b.width),height:Math.round(b.height)},elementRef:C}),a++}}return e}
11
- //# sourceMappingURL=accessibility-tree.js.map