surf-cli 2.7.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +208 -13
  2. package/dist/content/index.js +116 -0
  3. package/dist/content/index.js.map +1 -0
  4. package/dist/manifest.json +2 -11
  5. package/dist/options/options.js +3 -3
  6. package/dist/options/options.js.map +1 -1
  7. package/dist/service-worker/index.js +261 -61
  8. package/dist/service-worker/index.js.map +1 -1
  9. package/native/abort.cjs +65 -0
  10. package/native/ai-queue.cjs +64 -0
  11. package/native/aistudio-build.cjs +21 -13
  12. package/native/aistudio-client.cjs +40 -20
  13. package/native/browser-lock.cjs +169 -0
  14. package/native/chatgpt-client.cjs +63 -30
  15. package/native/cli.cjs +947 -460
  16. package/native/client-transport.cjs +168 -0
  17. package/native/config.cjs +2 -2
  18. package/native/do-executor.cjs +25 -51
  19. package/native/do-parser.cjs +12 -0
  20. package/native/doctor.cjs +633 -0
  21. package/native/endpoint.cjs +174 -0
  22. package/native/file-transfer.cjs +734 -0
  23. package/native/gemini-client.cjs +244 -88
  24. package/native/grok-client.cjs +321 -212
  25. package/native/host-helpers.cjs +88 -16
  26. package/native/host-sessions.cjs +283 -0
  27. package/native/host.cjs +811 -616
  28. package/native/listener.cjs +20 -0
  29. package/native/mcp-server.cjs +60 -62
  30. package/native/network-export.cjs +113 -0
  31. package/native/perplexity-client.cjs +46 -17
  32. package/native/remote-auth.cjs +279 -0
  33. package/native/remote-transport.cjs +337 -0
  34. package/native/request-pending.cjs +148 -0
  35. package/native/socket-path.cjs +46 -0
  36. package/package.json +11 -9
  37. package/scripts/install-native-host.cjs +184 -51
  38. package/scripts/uninstall-native-host.cjs +93 -15
  39. package/skills/README.md +11 -5
  40. package/skills/deep-x-research/SKILL.md +106 -0
  41. package/skills/surf/SKILL.md +77 -22
  42. package/dist/content/accessibility-tree.js +0 -11
  43. package/dist/content/accessibility-tree.js.map +0 -1
  44. package/dist/content/visual-indicator.js +0 -111
  45. package/dist/content/visual-indicator.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const os = require("os");
5
- const { execSync } = require("child_process");
5
+ const { execFileSync, execSync } = require("child_process");
6
6
 
7
7
  const HOST_NAME = "surf.browser.host";
8
8
 
@@ -12,43 +12,80 @@ const BROWSERS = {
12
12
  darwin: "Library/Application Support/Google/Chrome/NativeMessagingHosts",
13
13
  linux: ".config/google-chrome/NativeMessagingHosts",
14
14
  win32: "Google\\Chrome",
15
+ wsl: "Google/Chrome/User Data/NativeMessagingHosts",
15
16
  },
16
17
  chromium: {
17
18
  name: "Chromium",
18
19
  darwin: "Library/Application Support/Chromium/NativeMessagingHosts",
19
20
  linux: ".config/chromium/NativeMessagingHosts",
20
21
  win32: "Chromium",
22
+ wsl: "Chromium/User Data/NativeMessagingHosts",
21
23
  },
22
24
  brave: {
23
25
  name: "Brave",
24
26
  darwin: "Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts",
25
27
  linux: ".config/BraveSoftware/Brave-Browser/NativeMessagingHosts",
26
28
  win32: "BraveSoftware\\Brave-Browser",
29
+ wsl: "BraveSoftware/Brave-Browser/User Data/NativeMessagingHosts",
27
30
  },
28
31
  edge: {
29
32
  name: "Microsoft Edge",
30
33
  darwin: "Library/Application Support/Microsoft Edge/NativeMessagingHosts",
31
34
  linux: ".config/microsoft-edge/NativeMessagingHosts",
32
35
  win32: "Microsoft\\Edge",
36
+ wsl: "Microsoft/Edge/User Data/NativeMessagingHosts",
33
37
  },
34
38
  arc: {
35
39
  name: "Arc",
36
40
  darwin: "Library/Application Support/Arc/User Data/NativeMessagingHosts",
37
41
  linux: null,
38
42
  win32: null,
43
+ wsl: null,
39
44
  },
40
45
  helium: {
41
46
  name: "Helium",
42
47
  darwin: "Library/Application Support/net.imput.helium/NativeMessagingHosts",
43
48
  linux: null,
44
49
  win32: null,
50
+ wsl: null,
45
51
  },
46
52
  };
47
53
 
48
- function getWrapperDir() {
49
- const platform = process.platform;
54
+ function isWsl() {
55
+ if (process.platform !== "linux") return false;
56
+ if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
57
+ try {
58
+ return /microsoft|wsl/i.test(fs.readFileSync("/proc/version", "utf8"));
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function getWindowsEnv(name) {
65
+ try {
66
+ return execFileSync("cmd.exe", ["/c", "echo", `%${name}%`], { encoding: "utf8" })
67
+ .trim()
68
+ .replace(/\r/g, "");
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function windowsPathToWslPath(winPath) {
75
+ const normalized = winPath.replace(/\\/g, "/");
76
+ const match = normalized.match(/^([A-Za-z]):\/(.*)$/);
77
+ if (!match) return normalized;
78
+ return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
79
+ }
80
+
81
+ function getWrapperDir(target = process.platform) {
50
82
  const home = os.homedir();
51
- switch (platform) {
83
+ if (target === "wsl-windows") {
84
+ const localAppData = getWindowsEnv("LOCALAPPDATA");
85
+ if (!localAppData) return null;
86
+ return path.join(windowsPathToWslPath(localAppData), "surf-cli");
87
+ }
88
+ switch (process.platform) {
52
89
  case "darwin":
53
90
  return path.join(home, "Library/Application Support/surf-cli");
54
91
  case "linux":
@@ -60,14 +97,31 @@ function getWrapperDir() {
60
97
  }
61
98
  }
62
99
 
63
- function removeManifest(browser) {
64
- const platform = process.platform;
100
+ function getWslWindowsManifestPath(browserConfig) {
101
+ const localAppData = getWindowsEnv("LOCALAPPDATA");
102
+ if (!localAppData || !browserConfig.wsl) return null;
103
+ return path.join(windowsPathToWslPath(localAppData), browserConfig.wsl, `${HOST_NAME}.json`);
104
+ }
105
+
106
+ function removeManifest(browser, target) {
65
107
  const browserConfig = BROWSERS[browser];
66
108
 
67
- if (!browserConfig || !browserConfig[platform]) {
68
- return null;
109
+ if (!browserConfig) return null;
110
+
111
+ if (target === "wsl-windows") {
112
+ const manifestPath = getWslWindowsManifestPath(browserConfig);
113
+ if (!manifestPath) return null;
114
+ try {
115
+ fs.unlinkSync(manifestPath);
116
+ return manifestPath;
117
+ } catch {
118
+ return null;
119
+ }
69
120
  }
70
121
 
122
+ const platform = process.platform;
123
+ if (!browserConfig[platform]) return null;
124
+
71
125
  if (platform === "win32") {
72
126
  return removeWindowsRegistry(browser);
73
127
  }
@@ -98,8 +152,8 @@ function removeWindowsRegistry(browser) {
98
152
  }
99
153
  }
100
154
 
101
- function removeWrapperDir() {
102
- const wrapperDir = getWrapperDir();
155
+ function removeWrapperDir(target) {
156
+ const wrapperDir = getWrapperDir(target);
103
157
  if (!wrapperDir) return null;
104
158
 
105
159
  try {
@@ -112,7 +166,7 @@ function removeWrapperDir() {
112
166
 
113
167
  function parseArgs() {
114
168
  const args = process.argv.slice(2);
115
- const result = { browsers: ["chrome"], all: false };
169
+ const result = { browsers: ["chrome"], all: false, target: "auto" };
116
170
 
117
171
  for (let i = 0; i < args.length; i++) {
118
172
  const arg = args[i];
@@ -127,6 +181,8 @@ function parseArgs() {
127
181
  } else if (arg === "--all" || arg === "-a") {
128
182
  result.browsers = Object.keys(BROWSERS);
129
183
  result.all = true;
184
+ } else if (arg === "--target") {
185
+ result.target = args[++i];
130
186
  } else if (arg === "--help" || arg === "-h") {
131
187
  printHelp();
132
188
  process.exit(0);
@@ -145,18 +201,40 @@ Options:
145
201
  -b, --browser Browser(s) to uninstall from (default: chrome)
146
202
  Values: chrome, chromium, brave, edge, arc, helium, all
147
203
  -a, --all Uninstall from all browsers and remove wrapper
204
+ --target Install target to remove: auto, linux, windows
205
+ On WSL2, auto removes Windows-browser manifests. Use linux for WSLg/Linux browsers.
148
206
 
149
207
  Examples:
150
208
  node uninstall-native-host.cjs
151
209
  node uninstall-native-host.cjs --browser brave
152
210
  node uninstall-native-host.cjs --all
211
+ node uninstall-native-host.cjs --target linux
153
212
  `);
154
213
  }
155
214
 
156
215
  function main() {
157
- const { browsers, all } = parseArgs();
216
+ const { browsers, all, target } = parseArgs();
217
+
218
+ if (!["auto", "linux", "windows"].includes(target)) {
219
+ console.error("Error: Invalid --target value. Expected auto, linux, or windows");
220
+ process.exit(1);
221
+ }
222
+
223
+ const runningInWsl = isWsl();
224
+ if (target === "windows" && !runningInWsl && process.platform !== "win32") {
225
+ console.error("Error: --target windows is only supported on Windows or WSL2");
226
+ process.exit(1);
227
+ }
228
+
229
+ if (target === "linux" && process.platform !== "linux") {
230
+ console.error("Error: --target linux is only supported on Linux or WSL2");
231
+ process.exit(1);
232
+ }
233
+
234
+ const effectiveTarget = runningInWsl && target !== "linux" ? "wsl-windows" : process.platform;
158
235
 
159
- console.log(`Platform: ${process.platform}`);
236
+ console.log(`Platform: ${process.platform}${runningInWsl ? " (WSL2 detected)" : ""}`);
237
+ console.log(`Target: ${effectiveTarget === "wsl-windows" ? "Windows browser from WSL2" : effectiveTarget}`);
160
238
  console.log("");
161
239
 
162
240
  const removed = [];
@@ -168,7 +246,7 @@ function main() {
168
246
  continue;
169
247
  }
170
248
 
171
- const result = removeManifest(browser);
249
+ const result = removeManifest(browser, effectiveTarget);
172
250
  if (result) {
173
251
  removed.push({ browser: BROWSERS[browser].name, path: result });
174
252
  } else {
@@ -188,7 +266,7 @@ function main() {
188
266
  }
189
267
 
190
268
  if (all) {
191
- const wrapperDir = removeWrapperDir();
269
+ const wrapperDir = removeWrapperDir(effectiveTarget);
192
270
  if (wrapperDir) {
193
271
  console.log(`\nRemoved wrapper directory: ${wrapperDir}`);
194
272
  }
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.
@@ -7,6 +7,36 @@ description: Control Chrome browser via CLI for testing, automation, and debuggi
7
7
 
8
8
  Control Chrome browser via CLI or Unix socket.
9
9
 
10
+ ## Native Host / Socket Notes
11
+
12
+ For WSL2 with Windows Chrome, run `surf install <extension-id>` inside WSL2. Surf detects WSL2 and writes the Windows-side native messaging manifest plus a wrapper that launches the WSL host. Use `surf install <extension-id> --target linux` only for Linux browsers running inside WSLg.
13
+
14
+ On macOS, Chrome reads the native messaging manifest at `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/surf.browser.host.json`. If native messaging fails, confirm that file exists, its `allowed_origins` extension ID matches `chrome://extensions`, then rerun `surf install <extension-id>`, restart Chrome, reload the extension, and inspect the extension service-worker console.
15
+
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
+
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
+
10
40
  ## CLI Quick Reference
11
41
 
12
42
  ```bash
@@ -35,6 +65,9 @@ surf type --text "hello"
35
65
 
36
66
  # 5. Screenshot
37
67
  surf screenshot --output /tmp/shot.png
68
+
69
+ # Inspect animation/style changes as JSON
70
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10
38
71
  ```
39
72
 
40
73
  ## AI Assistants (No API Keys)
@@ -57,7 +90,7 @@ surf gemini "analyze" --file data.csv # Attach file
57
90
  surf gemini "a robot surfing" --generate-image /tmp/robot.png
58
91
  surf gemini "add sunglasses" --edit-image photo.jpg --output out.jpg
59
92
  surf gemini "summarize" --youtube "https://youtube.com/..."
60
- 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
61
94
  surf gemini "wide banner" --generate-image /tmp/banner.png --aspect-ratio 16:9
62
95
  ```
63
96
 
@@ -75,9 +108,11 @@ surf grok "what are the latest AI trends on X" # Search X posts
75
108
  surf grok "analyze @username recent activity" # Profile analysis
76
109
  surf grok "summarize this page" --with-page # Include page context
77
110
  surf grok "find viral AI posts" --deep-search # DeepSearch mode
78
- surf grok "quick question" --model fast # Models: auto, fast, expert, thinking (default)
111
+ surf grok "quick question" --model fast # Models: auto, fast, expert, grok-4.20-beta
79
112
  ```
80
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
+
81
116
  **Grok Validation & Troubleshooting:**
82
117
  ```bash
83
118
  # Validate Grok UI and check available models (no query sent)
@@ -122,7 +157,7 @@ When AI queries fail, check these common issues:
122
157
 
123
158
  1. **Not logged in**: The error "login required" means you need to log into the service in Chrome (chatgpt.com, gemini.google.com, perplexity.ai, x.com, or aistudio.google.com)
124
159
  2. **Model selection failed**: The UI may have changed. Run `surf grok --validate` to check
125
- 3. **Response timeout**: Thinking models (ChatGPT o1, Grok thinking) can take 45+ seconds. AI Studio builds can take several minutes.
160
+ 3. **Response timeout**: Reasoning-heavy models (ChatGPT o1, Grok Expert) can take 45+ seconds. AI Studio builds can take several minutes.
126
161
  4. **Element not found**: The service's UI changed. Check for surf-cli updates
127
162
 
128
163
  **Debugging workflow for agents:**
@@ -147,6 +182,7 @@ surf tab.list
147
182
  surf tab.new "https://google.com"
148
183
  surf tab.switch 12345
149
184
  surf tab.close 12345
185
+ surf tab.move 12345 --to-window 67890
150
186
  surf tab.reload # Reload current tab
151
187
 
152
188
  # Named tabs (aliases)
@@ -165,6 +201,8 @@ surf tab.groups # List all tab groups
165
201
 
166
202
  ```bash
167
203
  surf window.list # List all windows
204
+ surf resize 1280 720 # Resize current browser window
205
+ surf resize 1280 # Set current window width only
168
206
  surf window.list --tabs # Include tab details
169
207
  surf window.new # New window
170
208
  surf window.new --url "https://example.com" # New window with URL
@@ -176,24 +214,31 @@ surf window.resize --id 123 --width 1920 --height 1080
176
214
  surf window.resize --id 123 --state maximized # States: normal, minimized, maximized, fullscreen
177
215
  ```
178
216
 
179
- **Window isolation for agents:**
217
+ **Multi-agent isolation:**
180
218
  ```bash
181
- # Create isolated window for agent work
219
+ # Create a separate window for one agent and keep using its ID
182
220
  surf window.new "https://example.com"
183
- # Returns window ID, use with subsequent commands:
184
221
  surf --window-id 123 tab.list
185
222
  surf --window-id 123 go "https://other.com"
223
+
224
+ # Pin work to a specific tab, or name it for easier handoff
225
+ surf read --tab-id 456
226
+ surf tab.name agent-a --tab-id 456
227
+ surf tab.switch agent-a
186
228
  ```
187
229
 
230
+ Use `window.new`, `--window-id`, `--tab-id`, and named tabs to keep parallel agents on separate targets. Surf serializes non-streaming browser CLI requests per socket with a file-based lock, so agents sharing one native host wait instead of interleaving commands. Use `--no-lock` only for intentional bypasses. For hard isolation, run separate browser/profile instances with separate native hosts and `SURF_SOCKET` values; each socket gets its own lock. Surf does not yet have `session.new`, session IDs, or independent per-agent CDP sessions.
231
+
188
232
  ## Input Methods
189
233
 
190
234
  ```bash
191
- # CDP method (real events) - default
235
+ # CDP method (real events) types at the current focus
192
236
  surf type --text "hello"
193
237
  surf click --x 100 --y 200
194
238
 
195
- # JS method (DOM manipulation) - for contenteditable
196
- 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
197
242
 
198
243
  # Keys
199
244
  surf key Enter
@@ -210,9 +255,11 @@ surf drag --from-x 100 --from-y 100 --to-x 200 --to-y 200
210
255
  ```bash
211
256
  surf page.read # Accessibility tree with refs + page text
212
257
  surf page.read --no-text # Interactive elements only (no text content)
258
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10 # JSON animation timeline
213
259
  surf page.read --ref e5 # Get specific element details
214
260
  surf page.read --depth 3 # Limit tree depth
215
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
216
263
  surf page.text # Plain text content only
217
264
  surf page.state # Modals, loading state, scroll info
218
265
  ```
@@ -258,11 +305,13 @@ surf element.styles ".card" # Or by CSS selector
258
305
  ## Scrolling
259
306
 
260
307
  ```bash
261
- surf scroll.bottom
262
- surf scroll.top
263
- surf scroll.to --y 500 # Scroll to Y position
308
+ surf scroll down 800 # Scroll down 800px
309
+ surf scroll up 400 # Scroll up 400px
310
+ surf scroll bottom # Scroll to bottom
311
+ surf scroll top # Scroll to top
312
+ surf scroll.bottom # Dot command form also works
313
+ surf scroll.top
264
314
  surf scroll.to --ref e5 # Scroll element into view
265
- surf scroll.by --y 200 # Scroll by amount
266
315
  surf scroll.info # Get scroll position
267
316
  ```
268
317
 
@@ -395,6 +444,7 @@ surf screenshot # Auto-saves to /tmp/surf-snap-*.png
395
444
  surf screenshot --output /tmp/shot.png # Save to specific file
396
445
  surf screenshot --selector ".card" # Element only
397
446
  surf screenshot --full-page # Full page scroll capture
447
+ surf screenshot --full-page /tmp/full.png # Full page saved to path
398
448
  surf screenshot --no-save # Return base64 only, don't save file
399
449
  ```
400
450
 
@@ -409,11 +459,12 @@ surf zoom 1 # Reset to 100%
409
459
  ## Cookies & Storage
410
460
 
411
461
  ```bash
412
- surf cookie.list # List cookies for current page
413
- surf cookie.list --domain .google.com
414
- surf cookie.set --name "token" --value "abc123"
415
- surf cookie.get --name "token"
416
- surf cookie.clear # Clear all cookies
462
+ surf cookie list # List cookies for current page
463
+ surf cookie list --domain .google.com
464
+ surf cookie set --name "token" --value "abc123"
465
+ surf cookie get "token"
466
+ surf cookie clear --all # Clear all cookies
467
+ surf cookie delete "token" # Clear one cookie
417
468
  ```
418
469
 
419
470
  ## History & Bookmarks
@@ -558,13 +609,17 @@ surf wait.element ".missing" --auto-capture --timeout 2000
558
609
  5. **Auto-capture for debugging** - `--auto-capture` saves diagnostics on failure
559
610
  6. **AI tools use browser session** - Must be logged into the service (ChatGPT, Gemini, Perplexity, Grok, AI Studio), no API keys needed
560
611
  7. **Grok validation** - Run `surf grok --validate` if queries fail to check UI changes
561
- 8. **Long timeouts for thinking models** - ChatGPT o1, Grok thinking can take 60+ seconds. AI Studio builds default to 600s.
612
+ 8. **Long timeouts for reasoning-heavy models** - ChatGPT o1 and Grok Expert can take 60+ seconds. AI Studio builds default to 600s.
562
613
  9. **AI Studio for unrestricted Gemini** - `surf aistudio` gives less filtered responses than `surf gemini` for the same models
563
614
  10. **Use `surf do` for multi-step tasks** - Reduces token overhead and improves reliability
564
615
  11. **Dry-run workflows first** - `surf do '...' --dry-run` validates without executing
565
- 12. **Window isolation** - Use `window.new` + `--window-id` to keep agent work separate from your browsing
566
- 13. **Semantic locators** - `locate.role`, `locate.text`, `locate.label` for more robust element finding
567
- 14. **Frame context** - Use `frame.switch` before interacting with iframe content
616
+ 12. **Window isolation** - Use `window.new` + `--window-id` or `--tab-id` to keep agent work separate from your browsing
617
+ 13. **Request lock** - Non-streaming browser CLI requests serialize per socket; use `--no-lock` only when you intentionally want to bypass it
618
+ 14. **Native host diagnostics** - If commands fail with socket/native-host errors, run `surf doctor` or `surf doctor --browser all` before guessing at reinstall steps
619
+ 15. **Animation capture** - Use `surf record --duration 2000 --fps 10 --output /tmp/anim.gif` when the agent needs to see motion; use `animate-audit` for numeric timelines and `perf-audit` for jank/layout-shift snapshots
620
+ 16. **Hard isolation** - Use separate browser/profile instances plus separate `SURF_SOCKET` values when agents must not share a host or target
621
+ 17. **Semantic locators** - `locate.role`, `locate.text`, `locate.label` for more robust element finding
622
+ 18. **Frame context** - Use `frame.switch` before interacting with iframe content
568
623
 
569
624
  ## Socket API
570
625