surf-cli 2.7.1 → 2.8.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.
@@ -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
  }
@@ -7,6 +7,14 @@ 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
+
10
18
  ## CLI Quick Reference
11
19
 
12
20
  ```bash
@@ -35,6 +43,9 @@ surf type --text "hello"
35
43
 
36
44
  # 5. Screenshot
37
45
  surf screenshot --output /tmp/shot.png
46
+
47
+ # Inspect animation/style changes as JSON
48
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10
38
49
  ```
39
50
 
40
51
  ## AI Assistants (No API Keys)
@@ -75,7 +86,7 @@ surf grok "what are the latest AI trends on X" # Search X posts
75
86
  surf grok "analyze @username recent activity" # Profile analysis
76
87
  surf grok "summarize this page" --with-page # Include page context
77
88
  surf grok "find viral AI posts" --deep-search # DeepSearch mode
78
- surf grok "quick question" --model fast # Models: auto, fast, expert, thinking (default)
89
+ surf grok "quick question" --model fast # Models: auto, fast, expert, grok-4.20-beta
79
90
  ```
80
91
 
81
92
  **Grok Validation & Troubleshooting:**
@@ -122,7 +133,7 @@ When AI queries fail, check these common issues:
122
133
 
123
134
  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
135
  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.
136
+ 3. **Response timeout**: Reasoning-heavy models (ChatGPT o1, Grok Expert) can take 45+ seconds. AI Studio builds can take several minutes.
126
137
  4. **Element not found**: The service's UI changed. Check for surf-cli updates
127
138
 
128
139
  **Debugging workflow for agents:**
@@ -165,6 +176,8 @@ surf tab.groups # List all tab groups
165
176
 
166
177
  ```bash
167
178
  surf window.list # List all windows
179
+ surf resize 1280 720 # Resize current browser window
180
+ surf resize 1280 # Set current window width only
168
181
  surf window.list --tabs # Include tab details
169
182
  surf window.new # New window
170
183
  surf window.new --url "https://example.com" # New window with URL
@@ -176,15 +189,21 @@ surf window.resize --id 123 --width 1920 --height 1080
176
189
  surf window.resize --id 123 --state maximized # States: normal, minimized, maximized, fullscreen
177
190
  ```
178
191
 
179
- **Window isolation for agents:**
192
+ **Multi-agent isolation:**
180
193
  ```bash
181
- # Create isolated window for agent work
194
+ # Create a separate window for one agent and keep using its ID
182
195
  surf window.new "https://example.com"
183
- # Returns window ID, use with subsequent commands:
184
196
  surf --window-id 123 tab.list
185
197
  surf --window-id 123 go "https://other.com"
198
+
199
+ # Pin work to a specific tab, or name it for easier handoff
200
+ surf read --tab-id 456
201
+ surf tab.name agent-a --tab-id 456
202
+ surf tab.switch agent-a
186
203
  ```
187
204
 
205
+ 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.
206
+
188
207
  ## Input Methods
189
208
 
190
209
  ```bash
@@ -210,6 +229,7 @@ surf drag --from-x 100 --from-y 100 --to-x 200 --to-y 200
210
229
  ```bash
211
230
  surf page.read # Accessibility tree with refs + page text
212
231
  surf page.read --no-text # Interactive elements only (no text content)
232
+ surf animate-audit --selector ".thing" --duration 2000 --fps 10 # JSON animation timeline
213
233
  surf page.read --ref e5 # Get specific element details
214
234
  surf page.read --depth 3 # Limit tree depth
215
235
  surf page.read --compact # Minimal output for LLM efficiency
@@ -258,11 +278,13 @@ surf element.styles ".card" # Or by CSS selector
258
278
  ## Scrolling
259
279
 
260
280
  ```bash
261
- surf scroll.bottom
262
- surf scroll.top
263
- surf scroll.to --y 500 # Scroll to Y position
281
+ surf scroll down 800 # Scroll down 800px
282
+ surf scroll up 400 # Scroll up 400px
283
+ surf scroll bottom # Scroll to bottom
284
+ surf scroll top # Scroll to top
285
+ surf scroll.bottom # Dot command form also works
286
+ surf scroll.top
264
287
  surf scroll.to --ref e5 # Scroll element into view
265
- surf scroll.by --y 200 # Scroll by amount
266
288
  surf scroll.info # Get scroll position
267
289
  ```
268
290
 
@@ -395,6 +417,7 @@ surf screenshot # Auto-saves to /tmp/surf-snap-*.png
395
417
  surf screenshot --output /tmp/shot.png # Save to specific file
396
418
  surf screenshot --selector ".card" # Element only
397
419
  surf screenshot --full-page # Full page scroll capture
420
+ surf screenshot --full-page /tmp/full.png # Full page saved to path
398
421
  surf screenshot --no-save # Return base64 only, don't save file
399
422
  ```
400
423
 
@@ -409,11 +432,12 @@ surf zoom 1 # Reset to 100%
409
432
  ## Cookies & Storage
410
433
 
411
434
  ```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
435
+ surf cookie list # List cookies for current page
436
+ surf cookie list --domain .google.com
437
+ surf cookie set --name "token" --value "abc123"
438
+ surf cookie get "token"
439
+ surf cookie clear --all # Clear all cookies
440
+ surf cookie delete "token" # Clear one cookie
417
441
  ```
418
442
 
419
443
  ## History & Bookmarks
@@ -558,13 +582,17 @@ surf wait.element ".missing" --auto-capture --timeout 2000
558
582
  5. **Auto-capture for debugging** - `--auto-capture` saves diagnostics on failure
559
583
  6. **AI tools use browser session** - Must be logged into the service (ChatGPT, Gemini, Perplexity, Grok, AI Studio), no API keys needed
560
584
  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.
585
+ 8. **Long timeouts for reasoning-heavy models** - ChatGPT o1 and Grok Expert can take 60+ seconds. AI Studio builds default to 600s.
562
586
  9. **AI Studio for unrestricted Gemini** - `surf aistudio` gives less filtered responses than `surf gemini` for the same models
563
587
  10. **Use `surf do` for multi-step tasks** - Reduces token overhead and improves reliability
564
588
  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
589
+ 12. **Window isolation** - Use `window.new` + `--window-id` or `--tab-id` to keep agent work separate from your browsing
590
+ 13. **Request lock** - Non-streaming browser CLI requests serialize per socket; use `--no-lock` only when you intentionally want to bypass it
591
+ 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
592
+ 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
593
+ 16. **Hard isolation** - Use separate browser/profile instances plus separate `SURF_SOCKET` values when agents must not share a host or target
594
+ 17. **Semantic locators** - `locate.role`, `locate.text`, `locate.label` for more robust element finding
595
+ 18. **Frame context** - Use `frame.switch` before interacting with iframe content
568
596
 
569
597
  ## Socket API
570
598
 
package/native/edited.png DELETED
Binary file