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.
- package/README.md +110 -9
- package/native/browser-lock.cjs +169 -0
- package/native/chatgpt-client.cjs +394 -105
- package/native/cli.cjs +670 -279
- package/native/config.cjs +2 -2
- package/native/do-executor.cjs +2 -9
- package/native/do-parser.cjs +12 -0
- package/native/doctor.cjs +583 -0
- package/native/gemini-client.cjs +91 -20
- package/native/grok-client.cjs +270 -170
- package/native/host-helpers.cjs +51 -4
- package/native/host.cjs +22 -7
- package/native/mcp-server.cjs +10 -7
- package/native/socket-path.cjs +46 -0
- package/package.json +5 -5
- package/scripts/install-native-host.cjs +155 -53
- package/scripts/uninstall-native-host.cjs +93 -15
- package/skills/surf/SKILL.md +46 -18
- package/native/edited.png +0 -0
|
@@ -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
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
64
|
-
const
|
|
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
|
|
68
|
-
|
|
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/surf/SKILL.md
CHANGED
|
@@ -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,
|
|
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**:
|
|
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
|
-
**
|
|
192
|
+
**Multi-agent isolation:**
|
|
180
193
|
```bash
|
|
181
|
-
# Create
|
|
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
|
|
262
|
-
surf scroll
|
|
263
|
-
surf scroll
|
|
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
|
|
413
|
-
surf cookie
|
|
414
|
-
surf cookie
|
|
415
|
-
surf cookie
|
|
416
|
-
surf cookie
|
|
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
|
|
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. **
|
|
567
|
-
14. **
|
|
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
|