surf-cli 2.7.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -13
- package/dist/content/index.js +116 -0
- package/dist/content/index.js.map +1 -0
- package/dist/manifest.json +2 -11
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +261 -61
- package/dist/service-worker/index.js.map +1 -1
- package/native/abort.cjs +65 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +169 -0
- package/native/chatgpt-client.cjs +63 -30
- package/native/cli.cjs +947 -460
- package/native/client-transport.cjs +168 -0
- package/native/config.cjs +2 -2
- package/native/do-executor.cjs +25 -51
- package/native/do-parser.cjs +12 -0
- package/native/doctor.cjs +633 -0
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +244 -88
- package/native/grok-client.cjs +321 -212
- package/native/host-helpers.cjs +88 -16
- package/native/host-sessions.cjs +283 -0
- package/native/host.cjs +811 -616
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -62
- package/native/network-export.cjs +113 -0
- package/native/perplexity-client.cjs +46 -17
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +46 -0
- package/package.json +11 -9
- package/scripts/install-native-host.cjs +184 -51
- package/scripts/uninstall-native-host.cjs +93 -15
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +77 -22
- package/dist/content/accessibility-tree.js +0 -11
- package/dist/content/accessibility-tree.js.map +0 -1
- package/dist/content/visual-indicator.js +0 -111
- package/dist/content/visual-indicator.js.map +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const { connectEndpoint } = require("./endpoint.cjs");
|
|
2
|
+
const { createFrameParser, createSocketWriter } = require("./remote-transport.cjs");
|
|
3
|
+
const { createClientTransferController } = require("./file-transfer.cjs");
|
|
4
|
+
|
|
5
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
+
|
|
7
|
+
async function openClientTransport(endpoint, { requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS } = {}) {
|
|
8
|
+
let socket;
|
|
9
|
+
let ready;
|
|
10
|
+
let readyReject;
|
|
11
|
+
let readyErrorListener;
|
|
12
|
+
ready = new Promise((resolve, reject) => {
|
|
13
|
+
readyReject = reject;
|
|
14
|
+
readyErrorListener = (error) => reject(error);
|
|
15
|
+
socket = connectEndpoint(endpoint, () => {
|
|
16
|
+
socket.removeListener("error", readyErrorListener);
|
|
17
|
+
resolve();
|
|
18
|
+
});
|
|
19
|
+
socket.once("error", readyErrorListener);
|
|
20
|
+
});
|
|
21
|
+
await ready;
|
|
22
|
+
const pending = new Map();
|
|
23
|
+
const writer = createSocketWriter(socket, { onOverflow: ({ error }) => socket.destroy(error) });
|
|
24
|
+
const transfers = endpoint.kind === "remote"
|
|
25
|
+
? createClientTransferController({ writer, onActivity: () => {} })
|
|
26
|
+
: null;
|
|
27
|
+
let transferChain = Promise.resolve();
|
|
28
|
+
let transferCleanupPromise = null;
|
|
29
|
+
const cleanupTransfers = (error) => {
|
|
30
|
+
if (!transfers) return Promise.resolve();
|
|
31
|
+
transferCleanupPromise ||= transfers.cleanup(error);
|
|
32
|
+
return transferCleanupPromise;
|
|
33
|
+
};
|
|
34
|
+
const rejectPending = (error) => {
|
|
35
|
+
for (const entry of pending.values()) {
|
|
36
|
+
clearTimeout(entry.timer);
|
|
37
|
+
entry.reject(error);
|
|
38
|
+
}
|
|
39
|
+
pending.clear();
|
|
40
|
+
};
|
|
41
|
+
const parser = createFrameParser({
|
|
42
|
+
onFrame(message) {
|
|
43
|
+
if (transfers && message.type && message.type.startsWith("transfer_")) {
|
|
44
|
+
transferChain = transferChain.then(() => transfers.handle(message)).catch((error) => {
|
|
45
|
+
rejectPending(error);
|
|
46
|
+
return writer.send({ type: "transfer_error", version: 1, transferId: message.transferId, error: error.message }).finally(() => socket.destroy(error)).then(() => { throw error; });
|
|
47
|
+
});
|
|
48
|
+
transferChain.catch(() => undefined);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (message.type === "extension_disconnected") {
|
|
52
|
+
transferChain = transferChain.then(() => {
|
|
53
|
+
const error = new Error(message.message || "Surf extension disconnected");
|
|
54
|
+
rejectPending(error);
|
|
55
|
+
socket.end();
|
|
56
|
+
});
|
|
57
|
+
transferChain.catch(() => undefined);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (message.id === undefined || !pending.has(message.id)) return;
|
|
61
|
+
transferChain = transferChain.then(() => {
|
|
62
|
+
const entry = pending.get(message.id);
|
|
63
|
+
if (!entry) return;
|
|
64
|
+
pending.delete(message.id);
|
|
65
|
+
clearTimeout(entry.timer);
|
|
66
|
+
entry.resolve(message);
|
|
67
|
+
}).catch((error) => {
|
|
68
|
+
rejectPending(error);
|
|
69
|
+
socket.destroy(error);
|
|
70
|
+
});
|
|
71
|
+
transferChain.catch(() => undefined);
|
|
72
|
+
},
|
|
73
|
+
onError(error) {
|
|
74
|
+
rejectPending(error);
|
|
75
|
+
readyReject?.(error);
|
|
76
|
+
socket.destroy();
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
socket.on("data", (chunk) => parser.push(chunk));
|
|
80
|
+
let closed = false;
|
|
81
|
+
socket.on("close", () => {
|
|
82
|
+
parser.close();
|
|
83
|
+
const error = new Error("connection closed");
|
|
84
|
+
rejectPending(error);
|
|
85
|
+
writer.close(error);
|
|
86
|
+
cleanupTransfers(error);
|
|
87
|
+
closed = true;
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
socket,
|
|
91
|
+
transfers,
|
|
92
|
+
async request(message, timeoutMs = requestTimeoutMs, transferPlan = {}) {
|
|
93
|
+
if (closed) throw new Error("client transport is closed");
|
|
94
|
+
if (message.id === undefined || message.id === null) throw new Error("request id is required");
|
|
95
|
+
if (pending.has(message.id)) throw new Error("duplicate request id");
|
|
96
|
+
const downloadIds = (transferPlan.downloads || []).map((download) => download.transferId);
|
|
97
|
+
const timeoutError = new Error("request timed out");
|
|
98
|
+
let rejectTimeout;
|
|
99
|
+
const timeout = new Promise((_, reject) => { rejectTimeout = reject; });
|
|
100
|
+
timeout.catch(() => undefined);
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
closed = true;
|
|
103
|
+
rejectPending(timeoutError);
|
|
104
|
+
cleanupTransfers(timeoutError).catch(() => {});
|
|
105
|
+
writer.close(timeoutError);
|
|
106
|
+
socket.destroy();
|
|
107
|
+
rejectTimeout(timeoutError);
|
|
108
|
+
}, timeoutMs);
|
|
109
|
+
const execute = async () => {
|
|
110
|
+
try {
|
|
111
|
+
if (transfers) {
|
|
112
|
+
for (const download of transferPlan.downloads || []) await transfers.expectDownload(download);
|
|
113
|
+
const uploadDescriptors = [];
|
|
114
|
+
for (const upload of transferPlan.uploads || []) {
|
|
115
|
+
const result = await transfers.upload(upload.path, upload);
|
|
116
|
+
uploadDescriptors.push({ transferId: result.transferId, field: upload.field, original: upload.original, kind: "upload" });
|
|
117
|
+
}
|
|
118
|
+
message = { ...message, _surfTransfers: { uploads: uploadDescriptors, downloads: (transferPlan.downloads || []).map(({ transferId, field, original }) => ({ transferId, field, original, kind: "download" })) }, _surfPaths: transferPlan.pathRefs || [] };
|
|
119
|
+
}
|
|
120
|
+
const response = new Promise((resolve, reject) => {
|
|
121
|
+
pending.set(message.id, { resolve, reject, timer });
|
|
122
|
+
});
|
|
123
|
+
response.catch(() => undefined);
|
|
124
|
+
try {
|
|
125
|
+
await writer.send(message);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const entry = pending.get(message.id);
|
|
128
|
+
if (entry) { pending.delete(message.id); clearTimeout(entry.timer); entry.reject(error); }
|
|
129
|
+
await transfers?.cancelDownloads(downloadIds);
|
|
130
|
+
await response.catch(() => undefined);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
const result = await response;
|
|
134
|
+
if (result.error || (transfers && downloadIds.some((id) => transfers.hasDownload(id)))) {
|
|
135
|
+
await transfers?.cancelDownloads(downloadIds);
|
|
136
|
+
if (!result.error) throw new Error("request completed without completing output download");
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
await transfers?.cancelDownloads(downloadIds);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
return await Promise.race([execute(), timeout]);
|
|
146
|
+
} finally {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async closeAsync() {
|
|
151
|
+
if (closed) return transferCleanupPromise;
|
|
152
|
+
closed = true;
|
|
153
|
+
const error = new Error("client transport is closed");
|
|
154
|
+
parser.close();
|
|
155
|
+
rejectPending(error);
|
|
156
|
+
writer.close(error);
|
|
157
|
+
const cleanup = cleanupTransfers(error);
|
|
158
|
+
socket.end();
|
|
159
|
+
await cleanup;
|
|
160
|
+
return cleanup;
|
|
161
|
+
},
|
|
162
|
+
close() {
|
|
163
|
+
return this.closeAsync();
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { DEFAULT_REQUEST_TIMEOUT_MS, openClientTransport };
|
package/native/config.cjs
CHANGED
|
@@ -25,10 +25,10 @@ const STARTER_CONFIG = {
|
|
|
25
25
|
// {
|
|
26
26
|
// "grok": {
|
|
27
27
|
// "models": {
|
|
28
|
-
// "thinking": { "id": "thinking", "name": "Grok 4.1 Thinking" },
|
|
29
28
|
// "auto": { "id": "auto", "name": "Auto" },
|
|
30
29
|
// "fast": { "id": "fast", "name": "Fast" },
|
|
31
|
-
// "expert": { "id": "expert", "name": "Expert" }
|
|
30
|
+
// "expert": { "id": "expert", "name": "Expert" },
|
|
31
|
+
// "grok-4.20-beta": { "id": "grok-4.20-beta", "name": "Grok 4.20 Beta" }
|
|
32
32
|
// }
|
|
33
33
|
// }
|
|
34
34
|
// }
|
package/native/do-executor.cjs
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* Follows the same socket communication pattern as --script mode in cli.cjs.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
13
|
+
const { selectEndpoint } = require("./endpoint.cjs");
|
|
14
|
+
const { openClientTransport } = require("./client-transport.cjs");
|
|
15
|
+
const { resolveRequestDeadlineMs } = require("./host-sessions.cjs");
|
|
16
|
+
const { prepareRemoteTool, validateLocalToolPaths } = require("./file-transfer.cjs");
|
|
16
17
|
|
|
17
18
|
// Maximum iterations for loops (safety cap)
|
|
18
19
|
const MAX_LOOP_ITERATIONS = 100;
|
|
@@ -72,54 +73,27 @@ function getAutoWaitCommand(cmd) {
|
|
|
72
73
|
* @returns {Promise<object>} - Response from host
|
|
73
74
|
*/
|
|
74
75
|
function sendDoRequest(toolName, toolArgs, context = {}) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const resp = JSON.parse(line);
|
|
97
|
-
sock.end();
|
|
98
|
-
resolve(resp);
|
|
99
|
-
} catch {
|
|
100
|
-
sock.end();
|
|
101
|
-
reject(new Error("Invalid JSON response"));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
sock.on("error", (e) => {
|
|
107
|
-
if (e.code === "ENOENT") {
|
|
108
|
-
reject(new Error("Socket not found. Is Chrome running with the extension?"));
|
|
109
|
-
} else if (e.code === "ECONNREFUSED") {
|
|
110
|
-
reject(new Error("Connection refused. Native host not running."));
|
|
111
|
-
} else {
|
|
112
|
-
reject(e);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const timeoutId = setTimeout(() => {
|
|
117
|
-
sock.destroy();
|
|
118
|
-
reject(new Error("Request timeout"));
|
|
119
|
-
}, 30000);
|
|
120
|
-
|
|
121
|
-
sock.on("close", () => clearTimeout(timeoutId));
|
|
122
|
-
});
|
|
76
|
+
const request = {
|
|
77
|
+
type: "tool_request",
|
|
78
|
+
method: "execute_tool",
|
|
79
|
+
params: { tool: toolName, args: toolArgs },
|
|
80
|
+
id: "do-" + Date.now() + "-" + Math.random(),
|
|
81
|
+
};
|
|
82
|
+
if (context.tabId) request.tabId = context.tabId;
|
|
83
|
+
if (context.windowId) request.windowId = context.windowId;
|
|
84
|
+
const requestTimeoutMs = context.timeoutMs || resolveRequestDeadlineMs(toolName, toolArgs);
|
|
85
|
+
const endpoint = context.endpoint || selectEndpoint([]).endpoint;
|
|
86
|
+
const prepared = endpoint.kind === "remote" ? prepareRemoteTool(toolName, toolArgs) : (() => { const args = validateLocalToolPaths(toolName, toolArgs); return { args, uploads: [], downloads: [] }; })();
|
|
87
|
+
request.params.args = prepared.args;
|
|
88
|
+
if (context.transport) return context.transport.request(request, requestTimeoutMs, prepared);
|
|
89
|
+
return (async () => {
|
|
90
|
+
const transport = await openClientTransport(endpoint);
|
|
91
|
+
try {
|
|
92
|
+
return await transport.request(request, requestTimeoutMs, prepared);
|
|
93
|
+
} finally {
|
|
94
|
+
await transport.close();
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
123
97
|
}
|
|
124
98
|
|
|
125
99
|
/**
|
package/native/do-parser.cjs
CHANGED
|
@@ -165,6 +165,18 @@ function parseCommandLine(line) {
|
|
|
165
165
|
} else if (values.length > 1) {
|
|
166
166
|
args.values = values; // Multiple values as array
|
|
167
167
|
}
|
|
168
|
+
} else if (cmd === 'scroll') {
|
|
169
|
+
if (firstArg === 'top' || firstArg === 'bottom') {
|
|
170
|
+
cmd = `scroll.${firstArg}`;
|
|
171
|
+
i++;
|
|
172
|
+
} else if (['up', 'down', 'left', 'right'].includes(firstArg)) {
|
|
173
|
+
args.direction = firstArg;
|
|
174
|
+
i++;
|
|
175
|
+
if (i < tokens.length && /^-?\d+$/.test(tokens[i])) {
|
|
176
|
+
args.scroll_pixels = parseInt(tokens[i], 10);
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
168
180
|
} else {
|
|
169
181
|
// Use PRIMARY_ARG_MAP for other commands
|
|
170
182
|
const primaryKey = PRIMARY_ARG_MAP[cmd];
|