surf-cli 2.9.0 → 2.11.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 +61 -4
- package/dist/content/accessibility-tree.js +11 -0
- package/dist/content/accessibility-tree.js.map +1 -0
- package/dist/content/visual-indicator.js +111 -0
- package/dist/content/visual-indicator.js.map +1 -0
- package/dist/manifest.json +11 -2
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +61 -261
- package/dist/service-worker/index.js.map +1 -1
- package/native/activity-journal.cjs +55 -0
- package/native/chatgpt-client-response.cjs +336 -0
- package/native/chatgpt-client-selection.cjs +119 -0
- package/native/chatgpt-client-ui.cjs +481 -0
- package/native/chatgpt-client.cjs +254 -664
- package/native/cli.cjs +100 -273
- package/native/do-executor.cjs +52 -475
- package/native/do-parser.cjs +8 -249
- package/native/host-helpers.cjs +32 -15
- package/native/host-sessions.cjs +6 -1
- package/native/host.cjs +228 -6
- package/native/network-export.cjs +20 -17
- package/native/network-store.cjs +38 -58
- package/native/oracle-cli.cjs +434 -0
- package/native/oracle-context.cjs +311 -0
- package/native/oracle-host.cjs +301 -0
- package/native/oracle-jobs.cjs +253 -0
- package/native/playbook-authoring.cjs +44 -0
- package/native/playbook-cli.cjs +157 -0
- package/native/playbook-client.cjs +259 -0
- package/native/playbook-receipts.cjs +109 -0
- package/native/playbook-records.cjs +208 -0
- package/native/playbook-runtime.cjs +177 -0
- package/native/playbooks.cjs +235 -0
- package/native/private-state.cjs +156 -0
- package/native/redaction.cjs +104 -0
- package/native/workflow-definition.cjs +369 -0
- package/native/workflow-runtime.cjs +225 -0
- package/package.json +2 -1
- package/playbooks/page/ops/read.json +22 -0
- package/playbooks/page/playbook.json +7 -0
- package/skills/surf/SKILL.md +72 -1
- package/dist/content/index.js +0 -116
- package/dist/content/index.js.map +0 -1
package/native/do-parser.cjs
CHANGED
|
@@ -1,250 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* screenshot'
|
|
10
|
-
*
|
|
11
|
-
* Output:
|
|
12
|
-
* [
|
|
13
|
-
* { cmd: 'navigate', args: { url: 'https://example.com' } },
|
|
14
|
-
* { cmd: 'click', args: { ref: 'e5' } },
|
|
15
|
-
* { cmd: 'screenshot', args: {} }
|
|
16
|
-
* ]
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// Aliases mapping (matches cli.cjs)
|
|
20
|
-
const ALIASES = {
|
|
21
|
-
snap: "screenshot",
|
|
22
|
-
read: "page.read",
|
|
23
|
-
find: "search",
|
|
24
|
-
go: "navigate",
|
|
25
|
-
net: "network",
|
|
26
|
-
"network.dump": "network.get",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// Primary argument mapping for positional args (matches cli.cjs)
|
|
30
|
-
const PRIMARY_ARG_MAP = {
|
|
31
|
-
ai: "query",
|
|
32
|
-
gemini: "query",
|
|
33
|
-
chatgpt: "query",
|
|
34
|
-
perplexity: "query",
|
|
35
|
-
grok: "query",
|
|
36
|
-
navigate: "url",
|
|
37
|
-
go: "url",
|
|
38
|
-
js: "code",
|
|
39
|
-
javascript_tool: "code",
|
|
40
|
-
key: "key",
|
|
41
|
-
wait: "duration",
|
|
42
|
-
health: "url",
|
|
43
|
-
new_tab: "url",
|
|
44
|
-
"tab.new": "url",
|
|
45
|
-
switch_tab: "tab_id",
|
|
46
|
-
"tab.switch": "id",
|
|
47
|
-
close_tab: "tab_id",
|
|
48
|
-
"tab.close": "id",
|
|
49
|
-
"tab.name": "name",
|
|
50
|
-
"tab.unname": "name",
|
|
51
|
-
scroll_to_position: "position",
|
|
52
|
-
type: "text",
|
|
53
|
-
smart_type: "text",
|
|
54
|
-
"emulate.network": "preset",
|
|
55
|
-
"emulate.cpu": "rate",
|
|
56
|
-
search: "term",
|
|
57
|
-
find: "term",
|
|
58
|
-
"wait.element": "selector",
|
|
59
|
-
"wait.url": "pattern",
|
|
60
|
-
zoom: "level",
|
|
61
|
-
"history.search": "query",
|
|
62
|
-
"network.get": "id",
|
|
63
|
-
"network.body": "id",
|
|
64
|
-
"network.curl": "id",
|
|
65
|
-
"network.path": "id",
|
|
66
|
-
"window.new": "url",
|
|
67
|
-
"window.focus": "id",
|
|
68
|
-
"window.close": "id",
|
|
69
|
-
"locate.role": "role",
|
|
70
|
-
"locate.text": "text",
|
|
71
|
-
"locate.label": "label",
|
|
72
|
-
"emulate.device": "device",
|
|
73
|
-
"frame.js": "code",
|
|
74
|
-
"element.styles": "selector",
|
|
75
|
-
"select": "selector",
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Tokenize a command line, respecting single and double quotes
|
|
80
|
-
* @param {string} line - Single line to tokenize
|
|
81
|
-
* @returns {string[]} - Array of tokens
|
|
82
|
-
*/
|
|
83
|
-
function tokenize(line) {
|
|
84
|
-
const tokens = [];
|
|
85
|
-
let current = '';
|
|
86
|
-
let inQuote = null;
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < line.length; i++) {
|
|
89
|
-
const ch = line[i];
|
|
90
|
-
|
|
91
|
-
if (inQuote) {
|
|
92
|
-
if (ch === inQuote) {
|
|
93
|
-
// End of quoted string
|
|
94
|
-
inQuote = null;
|
|
95
|
-
} else {
|
|
96
|
-
current += ch;
|
|
97
|
-
}
|
|
98
|
-
} else if (ch === '"' || ch === "'") {
|
|
99
|
-
// Start of quoted string
|
|
100
|
-
inQuote = ch;
|
|
101
|
-
} else if (ch === ' ' || ch === '\t') {
|
|
102
|
-
// Whitespace separator
|
|
103
|
-
if (current) {
|
|
104
|
-
tokens.push(current);
|
|
105
|
-
current = '';
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
current += ch;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Don't forget last token
|
|
113
|
-
if (current) {
|
|
114
|
-
tokens.push(current);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return tokens;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Parse a single command line into a step object
|
|
122
|
-
* @param {string} line - Single command line
|
|
123
|
-
* @returns {{ cmd: string, args: object } | null}
|
|
124
|
-
*/
|
|
125
|
-
function parseCommandLine(line) {
|
|
126
|
-
const tokens = tokenize(line);
|
|
127
|
-
if (tokens.length === 0) return null;
|
|
128
|
-
|
|
129
|
-
// Get command and apply alias
|
|
130
|
-
let cmd = tokens[0];
|
|
131
|
-
cmd = ALIASES[cmd] || cmd;
|
|
132
|
-
|
|
133
|
-
const args = {};
|
|
134
|
-
let i = 1;
|
|
135
|
-
|
|
136
|
-
// Handle first positional argument based on command type
|
|
137
|
-
if (i < tokens.length && !tokens[i].startsWith('--')) {
|
|
138
|
-
const firstArg = tokens[i];
|
|
139
|
-
|
|
140
|
-
// Special handling for click command
|
|
141
|
-
if (cmd === 'click') {
|
|
142
|
-
if (/^e\d+$/.test(firstArg)) {
|
|
143
|
-
// Element reference: e5 -> ref
|
|
144
|
-
args.ref = firstArg;
|
|
145
|
-
i++;
|
|
146
|
-
} else if (/^\d+$/.test(firstArg) && tokens[i + 1] && /^\d+$/.test(tokens[i + 1])) {
|
|
147
|
-
// Coordinates: 100 200 -> x, y
|
|
148
|
-
args.x = parseInt(firstArg, 10);
|
|
149
|
-
args.y = parseInt(tokens[i + 1], 10);
|
|
150
|
-
i += 2;
|
|
151
|
-
}
|
|
152
|
-
} else if (cmd === 'select') {
|
|
153
|
-
// Select takes selector + one or more values: select e5 "US" or select e5 "opt1" "opt2"
|
|
154
|
-
args.selector = firstArg;
|
|
155
|
-
i++;
|
|
156
|
-
// Collect remaining positional args as values
|
|
157
|
-
const values = [];
|
|
158
|
-
while (i < tokens.length && !tokens[i].startsWith('--')) {
|
|
159
|
-
values.push(tokens[i]);
|
|
160
|
-
i++;
|
|
161
|
-
}
|
|
162
|
-
// Host expects 'values' (always), matching CLI behavior
|
|
163
|
-
if (values.length === 1) {
|
|
164
|
-
args.values = values[0]; // Single value as string (host will wrap in array)
|
|
165
|
-
} else if (values.length > 1) {
|
|
166
|
-
args.values = values; // Multiple values as array
|
|
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
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
// Use PRIMARY_ARG_MAP for other commands
|
|
182
|
-
const primaryKey = PRIMARY_ARG_MAP[cmd];
|
|
183
|
-
if (primaryKey) {
|
|
184
|
-
args[primaryKey] = firstArg;
|
|
185
|
-
i++;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Parse --flag value pairs
|
|
191
|
-
while (i < tokens.length) {
|
|
192
|
-
const token = tokens[i];
|
|
193
|
-
if (token.startsWith('--')) {
|
|
194
|
-
const key = token.slice(2);
|
|
195
|
-
const next = tokens[i + 1];
|
|
196
|
-
if (next && !next.startsWith('--')) {
|
|
197
|
-
// Flag with value
|
|
198
|
-
let val = next;
|
|
199
|
-
// Type coercion
|
|
200
|
-
if (val === "true") val = true;
|
|
201
|
-
else if (val === "false") val = false;
|
|
202
|
-
else if (/^-?\d+$/.test(val)) val = parseInt(val, 10);
|
|
203
|
-
else if (/^-?\d+\.\d+$/.test(val)) val = parseFloat(val);
|
|
204
|
-
args[key] = val;
|
|
205
|
-
i += 2;
|
|
206
|
-
} else {
|
|
207
|
-
// Boolean flag
|
|
208
|
-
args[key] = true;
|
|
209
|
-
i++;
|
|
210
|
-
}
|
|
211
|
-
} else {
|
|
212
|
-
// Skip unrecognized positional (shouldn't happen normally)
|
|
213
|
-
i++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return { cmd, args };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Parse a workflow string into step array
|
|
222
|
-
* Supports pipe-separated (inline) or newline-separated (file) commands
|
|
223
|
-
* @param {string} input - Workflow string
|
|
224
|
-
* @returns {Array<{ cmd: string, args: object }>}
|
|
225
|
-
*/
|
|
226
|
-
function parseDoCommands(input) {
|
|
227
|
-
// Determine separator: use pipe if present, otherwise newlines
|
|
228
|
-
// Pipe is preferred for inline: 'go "url" | click e5 | screenshot'
|
|
229
|
-
// Newlines for files or heredocs
|
|
230
|
-
const hasPipe = input.includes('|');
|
|
231
|
-
const separator = hasPipe ? '|' : '\n';
|
|
232
|
-
|
|
233
|
-
// Also handle literal \n for backwards compatibility
|
|
234
|
-
const normalized = hasPipe ? input : input.replace(/\\n/g, '\n');
|
|
235
|
-
|
|
236
|
-
return normalized
|
|
237
|
-
.split(separator)
|
|
238
|
-
.map(line => line.trim())
|
|
239
|
-
.filter(line => line && !line.startsWith('#'))
|
|
240
|
-
.map(line => parseCommandLine(line))
|
|
241
|
-
.filter(step => step !== null);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
module.exports = {
|
|
245
|
-
parseDoCommands,
|
|
246
|
-
parseCommandLine,
|
|
247
|
-
tokenize,
|
|
248
|
-
ALIASES,
|
|
249
|
-
PRIMARY_ARG_MAP
|
|
1
|
+
const definition = require("./workflow-definition.cjs");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
ALIASES: definition.ALIASES,
|
|
5
|
+
PRIMARY_ARG_MAP: definition.PRIMARY_ARG_MAP,
|
|
6
|
+
parseCommandLine: definition.parseCommandLine,
|
|
7
|
+
parseDoCommands: definition.parseDoCommands,
|
|
8
|
+
tokenize: definition.tokenize,
|
|
250
9
|
};
|
package/native/host-helpers.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const networkFormatters = require("./formatters/network.cjs");
|
|
3
|
-
const networkStore = require("./network-store.cjs");
|
|
4
3
|
|
|
5
4
|
function buildProviderUploadMessage(provider, tabId, filePaths, id) {
|
|
6
5
|
const normalizedProvider = String(provider || "").toLowerCase();
|
|
@@ -14,6 +13,21 @@ function normalizeModelString(model) {
|
|
|
14
13
|
return String(model || "").trim().toLowerCase();
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
function formatToolError(error) {
|
|
17
|
+
const message = error instanceof Error
|
|
18
|
+
? error.message
|
|
19
|
+
: typeof error === "string"
|
|
20
|
+
? error
|
|
21
|
+
: error?.message || String(error);
|
|
22
|
+
const result = { content: [{ type: "text", text: message }] };
|
|
23
|
+
if (error && typeof error === "object") {
|
|
24
|
+
result.message = message;
|
|
25
|
+
if (typeof error.code === "string") result.code = error.code;
|
|
26
|
+
if (typeof error.jobId === "string") result.jobId = error.jobId;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
/**
|
|
18
32
|
* Format tool result content for MCP response
|
|
19
33
|
* @param {*} result - The result object from the extension
|
|
@@ -123,19 +137,6 @@ function formatToolContent(result, log = () => {}, options = {}) {
|
|
|
123
137
|
// Handle both requests (basic) and entries (full) formats
|
|
124
138
|
const items = result.requests || result.entries;
|
|
125
139
|
if (items && Array.isArray(items)) {
|
|
126
|
-
// Persist entries with full data to disk
|
|
127
|
-
if (result.entries && items.length > 0) {
|
|
128
|
-
(async () => {
|
|
129
|
-
for (const entry of items) {
|
|
130
|
-
try {
|
|
131
|
-
await networkStore.appendEntry(entry);
|
|
132
|
-
} catch (err) {
|
|
133
|
-
log(`Failed to persist network entry: ${err.message}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
})();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
140
|
if (items.length === 0) {
|
|
140
141
|
return text("No network requests captured");
|
|
141
142
|
}
|
|
@@ -679,6 +680,9 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
679
680
|
limit: a.limit || a.last,
|
|
680
681
|
format: a.format,
|
|
681
682
|
verbose: a.v ? 1 : (a.vv ? 2 : 0),
|
|
683
|
+
bodyMode: a["body-mode"] || a.bodyMode,
|
|
684
|
+
perBodyBytes: a["per-body-bytes"] || a.perBodyBytes,
|
|
685
|
+
totalBodyBytes: a["total-body-bytes"] || a.totalBodyBytes,
|
|
682
686
|
...baseMsg
|
|
683
687
|
};
|
|
684
688
|
|
|
@@ -732,6 +736,9 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
732
736
|
type: "EXPORT_NETWORK_REQUESTS",
|
|
733
737
|
har: a.har,
|
|
734
738
|
jsonl: a.jsonl,
|
|
739
|
+
bodyMode: a["body-mode"] || a.bodyMode,
|
|
740
|
+
perBodyBytes: a["per-body-bytes"] || a.perBodyBytes,
|
|
741
|
+
totalBodyBytes: a["total-body-bytes"] || a.totalBodyBytes,
|
|
735
742
|
...baseMsg
|
|
736
743
|
};
|
|
737
744
|
|
|
@@ -1090,6 +1097,16 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
1090
1097
|
case "history.search":
|
|
1091
1098
|
if (!a.query) throw new Error("query required");
|
|
1092
1099
|
return { type: "HISTORY_SEARCH", query: a.query, limit: a.limit !== undefined ? parseInt(a.limit, 10) : 20 };
|
|
1100
|
+
case "oracle.ask":
|
|
1101
|
+
if (!a.prompt) throw new Error("prompt required");
|
|
1102
|
+
return { ...a, type: "ORACLE_ASK" };
|
|
1103
|
+
case "oracle.status":
|
|
1104
|
+
return { ...a, type: "ORACLE_STATUS" };
|
|
1105
|
+
case "oracle.result":
|
|
1106
|
+
if (!a.id) throw new Error("id required");
|
|
1107
|
+
return { ...a, type: "ORACLE_RESULT" };
|
|
1108
|
+
case "oracle.list":
|
|
1109
|
+
return { type: "ORACLE_LIST" };
|
|
1093
1110
|
case "chatgpt":
|
|
1094
1111
|
if (!a.query) throw new Error("query required");
|
|
1095
1112
|
return {
|
|
@@ -1204,4 +1221,4 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
1204
1221
|
}
|
|
1205
1222
|
}
|
|
1206
1223
|
|
|
1207
|
-
module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, buildProviderUploadMessage };
|
|
1224
|
+
module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage };
|
package/native/host-sessions.cjs
CHANGED
|
@@ -23,13 +23,18 @@ const PROVIDER_DEFAULT_TIMEOUT_SECONDS = {
|
|
|
23
23
|
gemini: 300,
|
|
24
24
|
grok: 300,
|
|
25
25
|
perplexity: 120,
|
|
26
|
+
"oracle.result": 300,
|
|
27
|
+
"playbook.run": 600,
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
function resolveRequestDeadlineMs(tool, args = {}) {
|
|
29
31
|
const defaultSeconds = PROVIDER_DEFAULT_TIMEOUT_SECONDS[tool];
|
|
30
32
|
if (defaultSeconds === undefined) return DEFAULT_DEADLINE_MS;
|
|
33
|
+
const rawTimeout = tool === "playbook.run" && args && typeof args === "object" && !Array.isArray(args)
|
|
34
|
+
? args.timeout ?? args.args?.timeout
|
|
35
|
+
: args?.timeout;
|
|
31
36
|
const requestedSeconds = Number(
|
|
32
|
-
|
|
37
|
+
rawTimeout,
|
|
33
38
|
);
|
|
34
39
|
const seconds = Number.isFinite(requestedSeconds) && requestedSeconds > 0
|
|
35
40
|
? requestedSeconds
|