surf-cli 2.1.0 → 2.3.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 +37 -2
- package/dist/content/accessibility-tree.js +8 -8
- package/dist/content/accessibility-tree.js.map +1 -1
- 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 +67 -1
- package/native/config.cjs +12 -0
- package/native/grok-client.cjs +906 -0
- package/native/host-helpers.cjs +108 -1
- package/native/host.cjs +198 -0
- package/native/perplexity-client.cjs +26 -22
- package/package.json +1 -1
- package/native/stream-viewer.html +0 -415
|
@@ -150,22 +150,23 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
150
150
|
})()`
|
|
151
151
|
);
|
|
152
152
|
await delay(300);
|
|
153
|
+
// Select from menu - loop in Node.js to avoid CDP timeout issues
|
|
153
154
|
const normalizedModel = desiredModel.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
const deadline = Date.now() + timeoutMs;
|
|
156
|
+
|
|
157
|
+
while (Date.now() < deadline) {
|
|
158
|
+
const result = await evaluate(
|
|
159
|
+
cdp,
|
|
160
|
+
`(() => {
|
|
161
|
+
${buildClickDispatcher()}
|
|
162
|
+
const targetModel = ${JSON.stringify(normalizedModel)};
|
|
163
|
+
const menuSelector = '${SELECTORS.menuContainer}';
|
|
164
|
+
const itemSelector = '${SELECTORS.menuItem}';
|
|
165
|
+
const normalize = (text) => (text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
166
|
+
|
|
165
167
|
const menu = document.querySelector(menuSelector);
|
|
166
168
|
if (!menu) {
|
|
167
|
-
|
|
168
|
-
continue;
|
|
169
|
+
return { found: false, waiting: true };
|
|
169
170
|
}
|
|
170
171
|
const items = Array.from(menu.querySelectorAll(itemSelector));
|
|
171
172
|
let bestMatch = null;
|
|
@@ -183,18 +184,24 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
183
184
|
}
|
|
184
185
|
if (bestMatch) {
|
|
185
186
|
dispatchClickSequence(bestMatch);
|
|
186
|
-
|
|
187
|
-
return { success: true, label: bestMatch.textContent?.trim() };
|
|
187
|
+
return { found: true, success: true, label: bestMatch.textContent?.trim() };
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
return { found: true, success: false, error: 'No matching model in menu' };
|
|
190
|
+
})()`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (result && result.found) {
|
|
194
|
+
if (result.success) {
|
|
195
|
+
await delay(200);
|
|
196
|
+
return result.label;
|
|
190
197
|
}
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
throw new Error(`Model not found: ${desiredModel}`);
|
|
198
|
+
throw new Error(`Model not found: ${desiredModel}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await delay(100);
|
|
196
202
|
}
|
|
197
|
-
|
|
203
|
+
|
|
204
|
+
throw new Error(`Model not found: ${desiredModel} (timeout)`);
|
|
198
205
|
}
|
|
199
206
|
|
|
200
207
|
async function typePrompt(cdp, inputCdp, prompt) {
|
package/native/cli.cjs
CHANGED
|
@@ -146,6 +146,27 @@ const TOOLS = {
|
|
|
146
146
|
{ cmd: 'perplexity "latest AI news" --model sonar', desc: "Specify model (Pro)" },
|
|
147
147
|
]
|
|
148
148
|
},
|
|
149
|
+
"grok": {
|
|
150
|
+
desc: "Query Grok AI with real-time X/Twitter data access (uses browser session)",
|
|
151
|
+
args: ["query"],
|
|
152
|
+
opts: {
|
|
153
|
+
"with-page": "Include current page context",
|
|
154
|
+
model: "Model: auto, fast, expert, thinking (default)",
|
|
155
|
+
"deep-search": "Enable DeepSearch for X post searching",
|
|
156
|
+
timeout: "Timeout in seconds (default: 300 for thinking models)",
|
|
157
|
+
validate: "Check Grok UI and scrape available models (no query sent)",
|
|
158
|
+
"save-models": "Save discovered models to surf.json config"
|
|
159
|
+
},
|
|
160
|
+
examples: [
|
|
161
|
+
{ cmd: 'grok "what are the latest AI agent trends on X"', desc: "Search X posts" },
|
|
162
|
+
{ cmd: 'grok "analyze @username recent activity"', desc: "Profile analysis" },
|
|
163
|
+
{ cmd: 'grok "summarize this page" --with-page', desc: "With page context" },
|
|
164
|
+
{ cmd: 'grok "find viral AI posts" --deep-search', desc: "DeepSearch mode" },
|
|
165
|
+
{ cmd: 'grok "quick question" --model fast', desc: "Faster model" },
|
|
166
|
+
{ cmd: 'grok --validate', desc: "Check UI and list available models" },
|
|
167
|
+
{ cmd: 'grok --validate --save-models', desc: "Save discovered models to settings" },
|
|
168
|
+
]
|
|
169
|
+
},
|
|
149
170
|
"ai": {
|
|
150
171
|
desc: "Analyze page with AI (requires GOOGLE_API_KEY)",
|
|
151
172
|
args: ["query"],
|
|
@@ -349,6 +370,37 @@ const TOOLS = {
|
|
|
349
370
|
},
|
|
350
371
|
}
|
|
351
372
|
},
|
|
373
|
+
element: {
|
|
374
|
+
desc: "Element inspection",
|
|
375
|
+
commands: {
|
|
376
|
+
"element.styles": {
|
|
377
|
+
desc: "Get computed styles from element(s)",
|
|
378
|
+
args: ["ref_or_selector"],
|
|
379
|
+
examples: [
|
|
380
|
+
{ cmd: "element.styles e5", desc: "Get styles by ref" },
|
|
381
|
+
{ cmd: 'element.styles ".header"', desc: "Get styles by selector (can return multiple)" },
|
|
382
|
+
]
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
forms: {
|
|
387
|
+
desc: "Form interactions",
|
|
388
|
+
commands: {
|
|
389
|
+
"select": {
|
|
390
|
+
desc: "Select option(s) in dropdown",
|
|
391
|
+
args: ["ref_or_selector", "values..."],
|
|
392
|
+
opts: {
|
|
393
|
+
by: "Match by: value (default), label, index"
|
|
394
|
+
},
|
|
395
|
+
examples: [
|
|
396
|
+
{ cmd: 'select e5 "US"', desc: "Select by value" },
|
|
397
|
+
{ cmd: 'select e5 "opt1" "opt2"', desc: "Multi-select" },
|
|
398
|
+
{ cmd: 'select e5 --by label "United States"', desc: "Select by visible text" },
|
|
399
|
+
{ cmd: 'select e5 --by index 0', desc: "Select first option" },
|
|
400
|
+
]
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
},
|
|
352
404
|
wait: {
|
|
353
405
|
desc: "Waiting",
|
|
354
406
|
commands: {
|
|
@@ -1771,6 +1823,7 @@ const PRIMARY_ARG_MAP = {
|
|
|
1771
1823
|
gemini: "query",
|
|
1772
1824
|
chatgpt: "query",
|
|
1773
1825
|
perplexity: "query",
|
|
1826
|
+
grok: "query",
|
|
1774
1827
|
navigate: "url",
|
|
1775
1828
|
go: "url",
|
|
1776
1829
|
js: "code",
|
|
@@ -1809,6 +1862,8 @@ const PRIMARY_ARG_MAP = {
|
|
|
1809
1862
|
"locate.label": "label",
|
|
1810
1863
|
"emulate.device": "device",
|
|
1811
1864
|
"frame.js": "code",
|
|
1865
|
+
"element.styles": "selector",
|
|
1866
|
+
"select": "selector",
|
|
1812
1867
|
};
|
|
1813
1868
|
|
|
1814
1869
|
const toolArgs = { ...options };
|
|
@@ -1845,6 +1900,17 @@ if (tool === "js" && toolArgs.file) {
|
|
|
1845
1900
|
}
|
|
1846
1901
|
}
|
|
1847
1902
|
|
|
1903
|
+
// Handle select command: capture multiple values after selector
|
|
1904
|
+
if (tool === "select" && positional.length > 2) {
|
|
1905
|
+
const values = positional.slice(2); // All args after "select <selector>"
|
|
1906
|
+
toolArgs.values = values.length === 1 ? values[0] : values;
|
|
1907
|
+
} else if (tool === "select" && positional.length === 2) {
|
|
1908
|
+
// Only selector provided, no values
|
|
1909
|
+
console.error("Error: select requires at least one value");
|
|
1910
|
+
console.error("Usage: surf select <selector> <value...>");
|
|
1911
|
+
process.exit(1);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1848
1914
|
if (toolArgs.into && !toolArgs.selector) {
|
|
1849
1915
|
toolArgs.selector = toolArgs.into;
|
|
1850
1916
|
delete toolArgs.into;
|
|
@@ -2124,7 +2190,7 @@ const socket = net.createConnection(SOCKET_PATH, () => {
|
|
|
2124
2190
|
socket.write(JSON.stringify(request) + "\n");
|
|
2125
2191
|
});
|
|
2126
2192
|
|
|
2127
|
-
const AI_TOOLS = ["smoke", "chatgpt", "gemini", "perplexity", "ai"];
|
|
2193
|
+
const AI_TOOLS = ["smoke", "chatgpt", "gemini", "perplexity", "grok", "ai"];
|
|
2128
2194
|
const requestTimeout = AI_TOOLS.includes(tool) ? 300000 : 30000;
|
|
2129
2195
|
const timeout = setTimeout(() => {
|
|
2130
2196
|
console.error(`Error: Request timed out (${requestTimeout / 1000}s)`);
|
package/native/config.cjs
CHANGED
|
@@ -21,6 +21,18 @@ const STARTER_CONFIG = {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
// Grok models can be customized in surf.json if X.com UI changes:
|
|
25
|
+
// {
|
|
26
|
+
// "grok": {
|
|
27
|
+
// "models": {
|
|
28
|
+
// "thinking": { "id": "thinking", "name": "Grok 4.1 Thinking" },
|
|
29
|
+
// "auto": { "id": "auto", "name": "Auto" },
|
|
30
|
+
// "fast": { "id": "fast", "name": "Fast" },
|
|
31
|
+
// "expert": { "id": "expert", "name": "Expert" }
|
|
32
|
+
// }
|
|
33
|
+
// }
|
|
34
|
+
// }
|
|
35
|
+
|
|
24
36
|
function findConfigPath() {
|
|
25
37
|
const cwdPath = path.join(process.cwd(), CONFIG_NAME);
|
|
26
38
|
if (fs.existsSync(cwdPath)) {
|