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
@@ -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
  // }
@@ -10,9 +10,10 @@
10
10
  * Follows the same socket communication pattern as --script mode in cli.cjs.
11
11
  */
12
12
 
13
- const net = require("net");
14
-
15
- const SOCKET_PATH = process.platform === "win32" ? "//./pipe/surf" : "/tmp/surf.sock";
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
- return new Promise((resolve, reject) => {
76
- const sock = net.createConnection(SOCKET_PATH, () => {
77
- const req = {
78
- type: "tool_request",
79
- method: "execute_tool",
80
- params: { tool: toolName, args: toolArgs },
81
- id: "do-" + Date.now() + "-" + Math.random(),
82
- };
83
- if (context.tabId) req.tabId = context.tabId;
84
- if (context.windowId) req.windowId = context.windowId;
85
- sock.write(JSON.stringify(req) + "\n");
86
- });
87
-
88
- let buf = "";
89
- sock.on("data", (d) => {
90
- buf += d.toString();
91
- const lines = buf.split("\n");
92
- buf = lines.pop();
93
- for (const line of lines) {
94
- if (!line.trim()) continue;
95
- try {
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
  /**
@@ -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];