surf-cli 2.2.0 → 2.4.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 +125 -3
- package/dist/service-worker/index.js +11 -11
- package/dist/service-worker/index.js.map +1 -1
- package/native/chatgpt-client.cjs +29 -22
- package/native/cli.cjs +184 -3
- package/native/config.cjs +12 -0
- package/native/do-executor.cjs +273 -0
- package/native/do-parser.cjs +232 -0
- package/native/grok-client.cjs +906 -0
- package/native/host-helpers.cjs +58 -1
- package/native/host.cjs +198 -0
- package/native/perplexity-client.cjs +26 -22
- package/package.json +1 -1
- package/native/CHANGELOG.md +0 -136
- package/native/README.md +0 -141
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for surf `do` workflow commands
|
|
3
|
+
*
|
|
4
|
+
* Parses newline-separated commands into structured step arrays:
|
|
5
|
+
*
|
|
6
|
+
* Input:
|
|
7
|
+
* 'go "https://example.com"
|
|
8
|
+
* click e5
|
|
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 {
|
|
169
|
+
// Use PRIMARY_ARG_MAP for other commands
|
|
170
|
+
const primaryKey = PRIMARY_ARG_MAP[cmd];
|
|
171
|
+
if (primaryKey) {
|
|
172
|
+
args[primaryKey] = firstArg;
|
|
173
|
+
i++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Parse --flag value pairs
|
|
179
|
+
while (i < tokens.length) {
|
|
180
|
+
const token = tokens[i];
|
|
181
|
+
if (token.startsWith('--')) {
|
|
182
|
+
const key = token.slice(2);
|
|
183
|
+
const next = tokens[i + 1];
|
|
184
|
+
if (next && !next.startsWith('--')) {
|
|
185
|
+
// Flag with value
|
|
186
|
+
let val = next;
|
|
187
|
+
// Type coercion
|
|
188
|
+
if (val === "true") val = true;
|
|
189
|
+
else if (val === "false") val = false;
|
|
190
|
+
else if (/^-?\d+$/.test(val)) val = parseInt(val, 10);
|
|
191
|
+
else if (/^-?\d+\.\d+$/.test(val)) val = parseFloat(val);
|
|
192
|
+
args[key] = val;
|
|
193
|
+
i += 2;
|
|
194
|
+
} else {
|
|
195
|
+
// Boolean flag
|
|
196
|
+
args[key] = true;
|
|
197
|
+
i++;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
// Skip unrecognized positional (shouldn't happen normally)
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { cmd, args };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parse a multi-line workflow string into step array
|
|
210
|
+
* @param {string} input - Multi-line workflow string
|
|
211
|
+
* @returns {Array<{ cmd: string, args: object }>}
|
|
212
|
+
*/
|
|
213
|
+
function parseDoCommands(input) {
|
|
214
|
+
// Replace literal \n (backslash + n) with actual newlines
|
|
215
|
+
// This handles bash single-quoted strings like 'go "url"\nclick e5'
|
|
216
|
+
const normalized = input.replace(/\\n/g, '\n');
|
|
217
|
+
|
|
218
|
+
return normalized
|
|
219
|
+
.split('\n')
|
|
220
|
+
.map(line => line.trim())
|
|
221
|
+
.filter(line => line && !line.startsWith('#'))
|
|
222
|
+
.map(line => parseCommandLine(line))
|
|
223
|
+
.filter(step => step !== null);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
parseDoCommands,
|
|
228
|
+
parseCommandLine,
|
|
229
|
+
tokenize,
|
|
230
|
+
ALIASES,
|
|
231
|
+
PRIMARY_ARG_MAP
|
|
232
|
+
};
|