surf-cli 2.8.0 → 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 +98 -4
- 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 +2 -2
- package/native/chatgpt-client.cjs +47 -31
- package/native/cli.cjs +300 -204
- package/native/client-transport.cjs +168 -0
- package/native/do-executor.cjs +25 -44
- package/native/doctor.cjs +55 -5
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +156 -71
- package/native/grok-client.cjs +98 -89
- package/native/host-helpers.cjs +37 -12
- package/native/host-sessions.cjs +283 -0
- package/native/host.cjs +800 -620
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -65
- 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 +1 -1
- package/package.json +8 -6
- package/scripts/install-native-host.cjs +36 -5
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +31 -4
- 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
package/native/cli.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const net = require("net");
|
|
3
2
|
const fs = require("fs");
|
|
4
3
|
const path = require("path");
|
|
5
4
|
const os = require("os");
|
|
@@ -9,11 +8,17 @@ const networkFormatters = require("./formatters/network.cjs");
|
|
|
9
8
|
const networkStore = require("./network-store.cjs");
|
|
10
9
|
const { parseDoCommands } = require("./do-parser.cjs");
|
|
11
10
|
const { executeDoSteps } = require("./do-executor.cjs");
|
|
11
|
+
const { openClientTransport } = require("./client-transport.cjs");
|
|
12
12
|
const { version: VERSION } = require("../package.json");
|
|
13
13
|
|
|
14
14
|
const IS_WIN = process.platform === "win32";
|
|
15
|
-
const {
|
|
15
|
+
const { SURF_TMP, formatSocketError } = require("./socket-path.cjs");
|
|
16
16
|
const { acquireBrowserLock } = require("./browser-lock.cjs");
|
|
17
|
+
const { selectEndpoint, connectEndpoint, formatEndpointError } = require("./endpoint.cjs");
|
|
18
|
+
const { createFrameParser, createSocketWriter, writeFrame } = require("./remote-transport.cjs");
|
|
19
|
+
const { resolveRequestDeadlineMs } = require("./host-sessions.cjs");
|
|
20
|
+
const { AUTO_SCREENSHOT_TOOLS, prepareRemoteTool, validateLocalToolPaths } = require("./file-transfer.cjs");
|
|
21
|
+
const { authorizeClient, listClients, revokeClient, getStateDir } = require("./remote-auth.cjs");
|
|
17
22
|
if (IS_WIN) { try { fs.mkdirSync(SURF_TMP, { recursive: true }); } catch {} }
|
|
18
23
|
|
|
19
24
|
function parseBrowserLockOptions(noLockFlag) {
|
|
@@ -29,11 +34,11 @@ function parseBrowserLockOptions(noLockFlag) {
|
|
|
29
34
|
return { noLock, timeoutMs };
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
function installBrowserLock({ noLock, timeoutMs }) {
|
|
37
|
+
function installBrowserLock({ noLock, timeoutMs }, endpoint) {
|
|
33
38
|
let releaseBrowserLock = () => {};
|
|
34
39
|
if (!noLock) {
|
|
35
40
|
try {
|
|
36
|
-
const lock = acquireBrowserLock(
|
|
41
|
+
const lock = acquireBrowserLock(endpoint.key, SURF_TMP, { timeoutMs });
|
|
37
42
|
releaseBrowserLock = lock.release;
|
|
38
43
|
} catch (error) {
|
|
39
44
|
console.error("Error:", error && error.message ? error.message : String(error));
|
|
@@ -336,7 +341,50 @@ function resizeImage(filePath, maxSize) {
|
|
|
336
341
|
return { success: false, error: e.message };
|
|
337
342
|
}
|
|
338
343
|
}
|
|
339
|
-
|
|
344
|
+
let args = process.argv.slice(2);
|
|
345
|
+
if (args[0] === "remote") {
|
|
346
|
+
const remoteArgs = args.slice(1);
|
|
347
|
+
const subcommand = remoteArgs[0];
|
|
348
|
+
const stateDir = getStateDir();
|
|
349
|
+
try {
|
|
350
|
+
if (subcommand === "authorize") {
|
|
351
|
+
const label = remoteArgs[1];
|
|
352
|
+
const outputIndex = remoteArgs.indexOf("--output");
|
|
353
|
+
const output = outputIndex === -1 ? undefined : remoteArgs[outputIndex + 1];
|
|
354
|
+
if (!label || !output || output.startsWith("--")) throw new Error("Usage: surf remote authorize <label> --output <credential-file>");
|
|
355
|
+
const client = authorizeClient(label, output, stateDir);
|
|
356
|
+
console.log(`Authorized remote client: ${client.label}`);
|
|
357
|
+
console.log(`Credential: ${client.output}`);
|
|
358
|
+
process.exit(0);
|
|
359
|
+
}
|
|
360
|
+
if (subcommand === "list") {
|
|
361
|
+
const clients = listClients(stateDir);
|
|
362
|
+
if (clients.length === 0) console.log("No authorized remote clients.");
|
|
363
|
+
else for (const client of clients) console.log(`${client.label}\t${client.id}\t${client.createdAt}`);
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
if (subcommand === "revoke") {
|
|
367
|
+
const label = remoteArgs[1];
|
|
368
|
+
if (!label || label.startsWith("--")) throw new Error("Usage: surf remote revoke <label>");
|
|
369
|
+
revokeClient(label, stateDir);
|
|
370
|
+
console.log(`Revoked remote client: ${label}`);
|
|
371
|
+
process.exit(0);
|
|
372
|
+
}
|
|
373
|
+
console.error("Usage: surf remote authorize <label> --output <credential-file> | list | revoke <label>");
|
|
374
|
+
process.exit(1);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error(`Error: ${error.message}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let endpoint;
|
|
382
|
+
try {
|
|
383
|
+
({ args, endpoint } = selectEndpoint(args));
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(`Error: ${error.message}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
340
388
|
|
|
341
389
|
const ALIASES = {
|
|
342
390
|
snap: "screenshot",
|
|
@@ -405,7 +453,7 @@ const TOOLS = {
|
|
|
405
453
|
args: ["query"],
|
|
406
454
|
opts: {
|
|
407
455
|
"with-page": "Include current page context",
|
|
408
|
-
model: "Model: gemini-3-pro (default), gemini-
|
|
456
|
+
model: "Model: gemini-3.1-pro (default), gemini-3.5-flash, gemini-3.1-flash-lite",
|
|
409
457
|
file: "Attach file to analyze",
|
|
410
458
|
"generate-image": "Generate image and save to path",
|
|
411
459
|
"edit-image": "Edit existing image (use with --output)",
|
|
@@ -528,6 +576,12 @@ const TOOLS = {
|
|
|
528
576
|
opts: { ids: "Close multiple tabs" },
|
|
529
577
|
examples: [{ cmd: "tab.close 123", desc: "Close tab" }]
|
|
530
578
|
},
|
|
579
|
+
"tab.move": {
|
|
580
|
+
desc: "Move tab to another window",
|
|
581
|
+
args: ["id"],
|
|
582
|
+
opts: { ids: "Move multiple tabs", "to-window": "Destination window ID", index: "Destination index" },
|
|
583
|
+
examples: [{ cmd: "tab.move 123 --to-window 456", desc: "Move tab to window" }]
|
|
584
|
+
},
|
|
531
585
|
"tab.name": {
|
|
532
586
|
desc: "Register current tab with a name",
|
|
533
587
|
args: ["name"],
|
|
@@ -675,6 +729,7 @@ const TOOLS = {
|
|
|
675
729
|
"no-text": "Exclude visible text content",
|
|
676
730
|
depth: "Maximum tree depth (default: unlimited)",
|
|
677
731
|
compact: "Remove empty structural elements",
|
|
732
|
+
"max-bytes": "Maximum visible text bytes",
|
|
678
733
|
},
|
|
679
734
|
examples: [
|
|
680
735
|
{ cmd: "page.read", desc: "Interactive elements + text content" },
|
|
@@ -682,7 +737,7 @@ const TOOLS = {
|
|
|
682
737
|
{ cmd: "page.read --no-text", desc: "Interactive elements only (no text)" },
|
|
683
738
|
{ cmd: "page.read --depth 3", desc: "Limit to 3 levels deep" },
|
|
684
739
|
{ cmd: "page.read --compact", desc: "Skip empty containers" },
|
|
685
|
-
{ cmd: "page.read --depth 3 --compact", desc: "Shallow + compact
|
|
740
|
+
{ cmd: "page.read --depth 3 --compact --max-bytes 2000", desc: "Shallow + compact output" },
|
|
686
741
|
{ cmd: "read", desc: "Alias" },
|
|
687
742
|
]
|
|
688
743
|
},
|
|
@@ -823,7 +878,7 @@ const TOOLS = {
|
|
|
823
878
|
ref: "Element ref (uses JS DOM method, more reliable for modals)",
|
|
824
879
|
submit: "Press enter after",
|
|
825
880
|
clear: "Clear first",
|
|
826
|
-
method: "cdp|js (
|
|
881
|
+
method: "cdp|js (cursor typing uses CDP; selector/ref targets use JS)"
|
|
827
882
|
},
|
|
828
883
|
examples: [
|
|
829
884
|
{ cmd: 'type "hello world"', desc: "Type at cursor (CDP events)" },
|
|
@@ -1567,7 +1622,7 @@ const ALL_SOCKET_TOOLS = [
|
|
|
1567
1622
|
"computer",
|
|
1568
1623
|
"page.read", "page.text", "page.state",
|
|
1569
1624
|
"locate.role", "locate.text", "locate.label",
|
|
1570
|
-
"tab.list", "tab.new", "tab.switch", "tab.close", "tab.name", "tab.unname", "tab.named",
|
|
1625
|
+
"tab.list", "tab.new", "tab.switch", "tab.close", "tab.move", "tab.name", "tab.unname", "tab.named",
|
|
1571
1626
|
"tab.group", "tab.ungroup", "tab.groups", "tab.reload",
|
|
1572
1627
|
"scroll.top", "scroll.bottom", "scroll.to", "scroll.info",
|
|
1573
1628
|
"wait.element", "wait.network", "wait.url", "wait.dom", "wait.load",
|
|
@@ -1659,6 +1714,10 @@ Quick Examples:
|
|
|
1659
1714
|
surf window.new "https://example.com" && surf --window-id 123 go "https://other.com"
|
|
1660
1715
|
|
|
1661
1716
|
More Help:
|
|
1717
|
+
--remote <host>:<port> Route requests to a remote native host
|
|
1718
|
+
--remote-credential <path> Use a mode-0600 Ed25519 remote credential file
|
|
1719
|
+
surf remote authorize <label> --output <path>
|
|
1720
|
+
surf remote list | surf remote revoke <label>
|
|
1662
1721
|
surf --help-full All commands
|
|
1663
1722
|
surf --llm-context Compact reference for AI agents
|
|
1664
1723
|
surf --help-topic <topic> Topic guide (refs, semantic, frames, devices...)
|
|
@@ -1715,6 +1774,8 @@ Usage: surf <command> [args] [options]
|
|
|
1715
1774
|
console.log(`Aliases: snap -> screenshot, read -> page.read, find -> search, go -> navigate
|
|
1716
1775
|
|
|
1717
1776
|
Options:
|
|
1777
|
+
--remote <host>:<port> Route requests to a remote native host
|
|
1778
|
+
--remote-credential <path> Use a mode-0600 Ed25519 remote credential file
|
|
1718
1779
|
--tab-id <id> Target specific tab
|
|
1719
1780
|
--window-id <id> Target specific window (isolate from your browsing)
|
|
1720
1781
|
--json Output raw JSON
|
|
@@ -1722,6 +1783,11 @@ Options:
|
|
|
1722
1783
|
--soft-fail On error: warn and exit 0 (for non-critical commands)
|
|
1723
1784
|
--no-lock Bypass the per-socket browser request lock
|
|
1724
1785
|
|
|
1786
|
+
Remote Credentials (run on the browser host):
|
|
1787
|
+
surf remote authorize <label> --output <credential-file>
|
|
1788
|
+
surf remote list
|
|
1789
|
+
surf remote revoke <label>
|
|
1790
|
+
|
|
1725
1791
|
Script Mode:
|
|
1726
1792
|
surf --script <file> Run workflow from JSON
|
|
1727
1793
|
surf --script <file> --dry-run
|
|
@@ -1937,7 +2003,7 @@ if (args[0] === "server") {
|
|
|
1937
2003
|
process.exit(0);
|
|
1938
2004
|
}
|
|
1939
2005
|
const { PiChromeMcpServer } = require("./mcp-server.cjs");
|
|
1940
|
-
const server = new PiChromeMcpServer();
|
|
2006
|
+
const server = new PiChromeMcpServer(endpoint);
|
|
1941
2007
|
server.start().catch((err) => {
|
|
1942
2008
|
console.error("MCP Server error:", err.message);
|
|
1943
2009
|
process.exit(1);
|
|
@@ -1953,7 +2019,7 @@ if (args[0] === "extension-path" || args[0] === "path") {
|
|
|
1953
2019
|
|
|
1954
2020
|
if (args[0] === "doctor") {
|
|
1955
2021
|
const { runDoctorCli } = require("./doctor.cjs");
|
|
1956
|
-
runDoctorCli(args.slice(1)).then((code) => process.exit(code));
|
|
2022
|
+
runDoctorCli(args.slice(1), endpoint).then((code) => process.exit(code));
|
|
1957
2023
|
return;
|
|
1958
2024
|
}
|
|
1959
2025
|
|
|
@@ -1978,6 +2044,8 @@ Options:
|
|
|
1978
2044
|
Multiple: --browser chrome,brave
|
|
1979
2045
|
--target Install target: auto, linux, windows
|
|
1980
2046
|
On WSL2, auto installs for Windows Chrome. Use linux for WSLg/Linux browsers.
|
|
2047
|
+
--listen <tailscale-ip>:<port>
|
|
2048
|
+
Requires surf remote authorize <label> --output <path> first.
|
|
1981
2049
|
|
|
1982
2050
|
Examples:
|
|
1983
2051
|
surf install hnfbepgmaoklhekckbpjnleifhahkcpl
|
|
@@ -2121,44 +2189,24 @@ if (args.includes("--script")) {
|
|
|
2121
2189
|
process.exit(1);
|
|
2122
2190
|
}
|
|
2123
2191
|
|
|
2192
|
+
let scriptTransport;
|
|
2124
2193
|
const sendScriptRequest = (toolName, toolArgs = {}) => {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
});
|
|
2136
|
-
let buf = "";
|
|
2137
|
-
sock.on("data", (d) => {
|
|
2138
|
-
buf += d.toString();
|
|
2139
|
-
const lines = buf.split("\n");
|
|
2140
|
-
buf = lines.pop();
|
|
2141
|
-
for (const line of lines) {
|
|
2142
|
-
if (!line.trim()) continue;
|
|
2143
|
-
try {
|
|
2144
|
-
const resp = JSON.parse(line);
|
|
2145
|
-
sock.end();
|
|
2146
|
-
resolve(resp);
|
|
2147
|
-
} catch {
|
|
2148
|
-
sock.end();
|
|
2149
|
-
reject(new Error("Invalid JSON"));
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
});
|
|
2153
|
-
sock.on("error", (e) => reject(new Error(formatSocketError(e))));
|
|
2154
|
-
let timeoutId;
|
|
2155
|
-
timeoutId = setTimeout(() => { sock.destroy(); reject(new Error("Timeout")); }, 30000);
|
|
2156
|
-
sock.on("close", () => clearTimeout(timeoutId));
|
|
2157
|
-
});
|
|
2194
|
+
const req = {
|
|
2195
|
+
type: "tool_request",
|
|
2196
|
+
method: "execute_tool",
|
|
2197
|
+
params: { tool: toolName, args: toolArgs },
|
|
2198
|
+
id: "cli-" + Date.now() + "-" + Math.random(),
|
|
2199
|
+
};
|
|
2200
|
+
if (scriptTabId) req.tabId = parseInt(scriptTabId, 10);
|
|
2201
|
+
const prepared = endpoint.kind === "remote" ? prepareRemoteTool(toolName, toolArgs) : (() => { const args = validateLocalToolPaths(toolName, toolArgs); return { args, uploads: [], downloads: [] }; })();
|
|
2202
|
+
req.params.args = prepared.args;
|
|
2203
|
+
return scriptTransport.request(req, resolveRequestDeadlineMs(toolName, prepared.args), prepared);
|
|
2158
2204
|
};
|
|
2159
2205
|
|
|
2160
2206
|
const runScript = async () => {
|
|
2161
|
-
|
|
2207
|
+
try {
|
|
2208
|
+
if (!dryRun) scriptTransport = await openClientTransport(endpoint);
|
|
2209
|
+
const total = script.steps.length;
|
|
2162
2210
|
const results = [];
|
|
2163
2211
|
let failed = 0;
|
|
2164
2212
|
|
|
@@ -2216,14 +2264,22 @@ if (args.includes("--script")) {
|
|
|
2216
2264
|
console.log(`Summary: ${passed} passed, ${failed} failed, ${total} total`);
|
|
2217
2265
|
}
|
|
2218
2266
|
|
|
2219
|
-
|
|
2267
|
+
return failed > 0 ? 1 : 0;
|
|
2268
|
+
} finally {
|
|
2269
|
+
await scriptTransport?.close();
|
|
2270
|
+
}
|
|
2220
2271
|
};
|
|
2221
2272
|
|
|
2222
2273
|
if (!dryRun) {
|
|
2223
|
-
installBrowserLock(parseBrowserLockOptions(args.includes("--no-lock")));
|
|
2274
|
+
installBrowserLock(parseBrowserLockOptions(args.includes("--no-lock")), endpoint);
|
|
2224
2275
|
}
|
|
2225
2276
|
|
|
2226
|
-
runScript()
|
|
2277
|
+
runScript()
|
|
2278
|
+
.then((code) => process.exit(code))
|
|
2279
|
+
.catch((error) => {
|
|
2280
|
+
console.error(`Error: ${error.message}`);
|
|
2281
|
+
process.exit(1);
|
|
2282
|
+
});
|
|
2227
2283
|
return;
|
|
2228
2284
|
}
|
|
2229
2285
|
|
|
@@ -2425,7 +2481,7 @@ if (args[0] === "do") {
|
|
|
2425
2481
|
process.exit(0);
|
|
2426
2482
|
}
|
|
2427
2483
|
|
|
2428
|
-
installBrowserLock(parseBrowserLockOptions(doArgs.includes("--no-lock")));
|
|
2484
|
+
installBrowserLock(parseBrowserLockOptions(doArgs.includes("--no-lock")), endpoint);
|
|
2429
2485
|
|
|
2430
2486
|
if (!wantJson) {
|
|
2431
2487
|
if (workflowName) {
|
|
@@ -2436,7 +2492,10 @@ if (args[0] === "do") {
|
|
|
2436
2492
|
}
|
|
2437
2493
|
|
|
2438
2494
|
const runWorkflow = async () => {
|
|
2439
|
-
|
|
2495
|
+
let transport;
|
|
2496
|
+
try {
|
|
2497
|
+
transport = await openClientTransport(endpoint);
|
|
2498
|
+
const result = await executeDoSteps(steps, {
|
|
2440
2499
|
onError,
|
|
2441
2500
|
autoWait: !noAutoWait,
|
|
2442
2501
|
stepDelay,
|
|
@@ -2445,30 +2504,40 @@ if (args[0] === "do") {
|
|
|
2445
2504
|
context: {
|
|
2446
2505
|
tabId,
|
|
2447
2506
|
windowId,
|
|
2507
|
+
endpoint,
|
|
2508
|
+
transport,
|
|
2448
2509
|
},
|
|
2449
|
-
|
|
2510
|
+
});
|
|
2450
2511
|
|
|
2451
2512
|
// Print summary
|
|
2452
2513
|
if (wantJson) {
|
|
2453
2514
|
console.log(JSON.stringify(result, null, 2));
|
|
2454
|
-
|
|
2515
|
+
return result.status === "completed" ? 0 : 1;
|
|
2455
2516
|
}
|
|
2456
2517
|
|
|
2457
2518
|
console.log("");
|
|
2458
2519
|
if (result.status === "completed") {
|
|
2459
2520
|
console.log(`Completed: ${result.completedSteps}/${result.totalSteps} steps (${result.totalMs}ms)`);
|
|
2460
|
-
|
|
2521
|
+
return 0;
|
|
2461
2522
|
} else if (result.status === "partial") {
|
|
2462
2523
|
console.log(`Partial: ${result.completedSteps}/${result.totalSteps} steps completed, ${result.failed} failed`);
|
|
2463
|
-
|
|
2524
|
+
return 1;
|
|
2464
2525
|
} else {
|
|
2465
2526
|
console.error(`Failed: ${result.completedSteps}/${result.totalSteps} steps completed`);
|
|
2466
2527
|
if (result.error) console.error(`Error: ${result.error}`);
|
|
2467
|
-
|
|
2528
|
+
return 1;
|
|
2529
|
+
}
|
|
2530
|
+
} finally {
|
|
2531
|
+
transport?.close();
|
|
2468
2532
|
}
|
|
2469
2533
|
};
|
|
2470
2534
|
|
|
2471
|
-
runWorkflow()
|
|
2535
|
+
runWorkflow()
|
|
2536
|
+
.then((code) => process.exit(code))
|
|
2537
|
+
.catch((error) => {
|
|
2538
|
+
console.error(`Error: ${error.message}`);
|
|
2539
|
+
process.exit(1);
|
|
2540
|
+
});
|
|
2472
2541
|
return;
|
|
2473
2542
|
}
|
|
2474
2543
|
|
|
@@ -2595,8 +2664,6 @@ if (args[0] === "workflow.validate") {
|
|
|
2595
2664
|
|
|
2596
2665
|
const BOOLEAN_FLAGS = ["auto-capture", "json", "stream", "dry-run", "stop-on-error", "fail-fast", "clear", "submit", "all", "case-sensitive", "hard", "annotate", "fullpage", "full-page", "reset", "no-screenshot", "full", "soft-fail", "has-body", "exclude-static", "v", "vv", "request", "by-tab", "har", "jsonl", "no-save", "no-auto-wait", "no-lock"];
|
|
2597
2666
|
|
|
2598
|
-
const AUTO_SCREENSHOT_TOOLS = ["click", "type", "key", "smart_type", "form.fill", "form_input", "drag", "hover", "scroll", "scroll.top", "scroll.bottom", "scroll.to", "dialog.accept", "dialog.dismiss", "js", "eval"];
|
|
2599
|
-
|
|
2600
2667
|
const parseArgs = (rawArgs) => {
|
|
2601
2668
|
const result = { positional: [], options: {} };
|
|
2602
2669
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
@@ -2732,6 +2799,7 @@ const PRIMARY_ARG_MAP = {
|
|
|
2732
2799
|
"tab.switch": "id",
|
|
2733
2800
|
close_tab: "tab_id",
|
|
2734
2801
|
"tab.close": "id",
|
|
2802
|
+
"tab.move": "id",
|
|
2735
2803
|
"tab.name": "name",
|
|
2736
2804
|
"tab.unname": "name",
|
|
2737
2805
|
scroll_to_position: "position",
|
|
@@ -2763,7 +2831,7 @@ const PRIMARY_ARG_MAP = {
|
|
|
2763
2831
|
"select": "selector",
|
|
2764
2832
|
};
|
|
2765
2833
|
|
|
2766
|
-
|
|
2834
|
+
let toolArgs = { ...options };
|
|
2767
2835
|
|
|
2768
2836
|
if (tool === "scroll" && firstArg) {
|
|
2769
2837
|
if (firstArg === "top" || firstArg === "bottom") {
|
|
@@ -2824,7 +2892,7 @@ if (firstArg !== undefined) {
|
|
|
2824
2892
|
}
|
|
2825
2893
|
}
|
|
2826
2894
|
|
|
2827
|
-
if (tool === "js" && toolArgs.file) {
|
|
2895
|
+
if ((tool === "js" || tool === "frame.js") && toolArgs.file) {
|
|
2828
2896
|
try {
|
|
2829
2897
|
toolArgs.code = fs.readFileSync(toolArgs.file, "utf8");
|
|
2830
2898
|
delete toolArgs.file;
|
|
@@ -2834,6 +2902,17 @@ if (tool === "js" && toolArgs.file) {
|
|
|
2834
2902
|
}
|
|
2835
2903
|
}
|
|
2836
2904
|
|
|
2905
|
+
if (tool === "batch" && toolArgs.file) {
|
|
2906
|
+
try {
|
|
2907
|
+
const parsed = JSON.parse(fs.readFileSync(toolArgs.file, "utf8"));
|
|
2908
|
+
toolArgs.actions = parsed;
|
|
2909
|
+
delete toolArgs.file;
|
|
2910
|
+
} catch (e) {
|
|
2911
|
+
console.error(`Error: Failed to read batch file: ${e.message}`);
|
|
2912
|
+
process.exit(1);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2837
2916
|
// Handle select command: capture multiple values after selector
|
|
2838
2917
|
if (tool === "select" && positional.length > 2) {
|
|
2839
2918
|
const values = positional.slice(2); // All args after "select <selector>"
|
|
@@ -2898,23 +2977,18 @@ if (tool === "aistudio.build" && outputPath) {
|
|
|
2898
2977
|
toolArgs.output = path.resolve(outputPath);
|
|
2899
2978
|
}
|
|
2900
2979
|
if (tool === "gemini") {
|
|
2901
|
-
if (outputPath) toolArgs.output =
|
|
2902
|
-
if (toolArgs
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
toolArgs.file = path.resolve(toolArgs.file);
|
|
2980
|
+
if (outputPath !== undefined) toolArgs.output = outputPath;
|
|
2981
|
+
if (toolArgs.model) {
|
|
2982
|
+
const known = ["gemini-3.1-pro", "gemini-3.5-flash", "gemini-3.1-flash-lite"];
|
|
2983
|
+
if (!known.includes(toolArgs.model)) {
|
|
2984
|
+
process.stderr.write(
|
|
2985
|
+
`warning: unknown Gemini model "${toolArgs.model}"; using "gemini-3.1-pro". Available: ${known.join(", ")}\n`,
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2910
2988
|
}
|
|
2911
2989
|
}
|
|
2912
|
-
if (tool === "
|
|
2913
|
-
|
|
2914
|
-
toolArgs.file = toolArgs.file.map((filePath) => path.resolve(filePath));
|
|
2915
|
-
} else if (typeof toolArgs.file === "string") {
|
|
2916
|
-
toolArgs.file = path.resolve(toolArgs.file);
|
|
2917
|
-
}
|
|
2990
|
+
if (tool === "network.export" && outputPath !== undefined) {
|
|
2991
|
+
toolArgs.output = outputPath;
|
|
2918
2992
|
}
|
|
2919
2993
|
|
|
2920
2994
|
if ((tool === "screenshot" || tool === "record" || tool === "perf-audit") && outputPath && typeof outputPath !== "string") {
|
|
@@ -2938,7 +3012,9 @@ const streamMode = toolArgs.stream === true;
|
|
|
2938
3012
|
delete toolArgs.stream;
|
|
2939
3013
|
|
|
2940
3014
|
const streamLevel = toolArgs.level;
|
|
2941
|
-
|
|
3015
|
+
if (tool === "console" || tool === "network") {
|
|
3016
|
+
delete toolArgs.level;
|
|
3017
|
+
}
|
|
2942
3018
|
|
|
2943
3019
|
const streamFilter = toolArgs.filter;
|
|
2944
3020
|
delete toolArgs.filter;
|
|
@@ -2946,11 +3022,15 @@ delete toolArgs.filter;
|
|
|
2946
3022
|
let finalTool = tool;
|
|
2947
3023
|
if (methodFlag === "js") {
|
|
2948
3024
|
if (tool === "type") {
|
|
2949
|
-
if (
|
|
2950
|
-
|
|
2951
|
-
|
|
3025
|
+
if (toolArgs.ref) {
|
|
3026
|
+
finalTool = "type";
|
|
3027
|
+
} else {
|
|
3028
|
+
if (!toolArgs.selector) {
|
|
3029
|
+
console.error("Error: --selector, --into, or --ref required for type with --method js");
|
|
3030
|
+
process.exit(1);
|
|
3031
|
+
}
|
|
3032
|
+
finalTool = "smart_type";
|
|
2952
3033
|
}
|
|
2953
|
-
finalTool = "smart_type";
|
|
2954
3034
|
} else if (tool === "click") {
|
|
2955
3035
|
if (!toolArgs.selector) {
|
|
2956
3036
|
console.error("Error: --selector required for click with --method js");
|
|
@@ -2961,8 +3041,13 @@ if (methodFlag === "js") {
|
|
|
2961
3041
|
finalTool = "js";
|
|
2962
3042
|
}
|
|
2963
3043
|
} else if (methodFlag === "cdp") {
|
|
3044
|
+
if (tool === "type" && (toolArgs.selector || toolArgs.ref)) {
|
|
3045
|
+
console.error("Error: --method cdp types at the current focus and cannot be combined with --into, --selector, or --ref");
|
|
3046
|
+
process.exit(1);
|
|
3047
|
+
}
|
|
2964
3048
|
if (tool === "smart_type") {
|
|
2965
|
-
|
|
3049
|
+
console.error("Error: smart_type uses the JS input path and cannot be combined with --method cdp");
|
|
3050
|
+
process.exit(1);
|
|
2966
3051
|
}
|
|
2967
3052
|
}
|
|
2968
3053
|
|
|
@@ -2980,8 +3065,10 @@ if (streamMode && (tool === "console" || tool === "network")) {
|
|
|
2980
3065
|
|
|
2981
3066
|
let connectionTimeout = null;
|
|
2982
3067
|
let receivedData = false;
|
|
3068
|
+
let streamWriter;
|
|
2983
3069
|
|
|
2984
|
-
const sock =
|
|
3070
|
+
const sock = connectEndpoint(endpoint, () => {
|
|
3071
|
+
streamWriter = createSocketWriter(sock, { onOverflow: ({ error }) => sock.destroy(error) });
|
|
2985
3072
|
const req = {
|
|
2986
3073
|
type: "stream_request",
|
|
2987
3074
|
streamType,
|
|
@@ -2989,7 +3076,11 @@ if (streamMode && (tool === "console" || tool === "network")) {
|
|
|
2989
3076
|
id: "cli-stream-" + Date.now(),
|
|
2990
3077
|
...globalOpts,
|
|
2991
3078
|
};
|
|
2992
|
-
|
|
3079
|
+
streamWriter.send(req).catch((error) => sock.destroy(error));
|
|
3080
|
+
if (connectionTimeout) {
|
|
3081
|
+
clearTimeout(connectionTimeout);
|
|
3082
|
+
connectionTimeout = null;
|
|
3083
|
+
}
|
|
2993
3084
|
connectionTimeout = setTimeout(() => {
|
|
2994
3085
|
if (!receivedData) {
|
|
2995
3086
|
console.error("Error: Stream connection timeout (10s) - no data received");
|
|
@@ -2999,57 +3090,62 @@ if (streamMode && (tool === "console" || tool === "network")) {
|
|
|
2999
3090
|
}, 10000);
|
|
3000
3091
|
});
|
|
3001
3092
|
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
if (!line.trim()) continue;
|
|
3016
|
-
try {
|
|
3017
|
-
const msg = JSON.parse(line);
|
|
3018
|
-
if (msg.error) {
|
|
3019
|
-
console.error("Error:", msg.error);
|
|
3020
|
-
sock.end();
|
|
3021
|
-
process.exit(1);
|
|
3022
|
-
}
|
|
3023
|
-
if (msg.type === "extension_disconnected") {
|
|
3024
|
-
console.error(msg.message);
|
|
3025
|
-
sock.end();
|
|
3026
|
-
process.exit(1);
|
|
3027
|
-
}
|
|
3028
|
-
if (msg.type === "stream_started") {
|
|
3029
|
-
continue;
|
|
3030
|
-
}
|
|
3031
|
-
if (msg.type === "console_event") {
|
|
3032
|
-
const { level, text, timestamp } = msg;
|
|
3033
|
-
if (streamLevel && level !== streamLevel) continue;
|
|
3034
|
-
console.log(`[console] [${level}] ${formatTime(timestamp)} ${text}`);
|
|
3035
|
-
} else if (msg.type === "network_event") {
|
|
3036
|
-
const { method, url, status, duration } = msg;
|
|
3037
|
-
if (streamFilter && !url.includes(streamFilter)) continue;
|
|
3038
|
-
const statusStr = status !== undefined ? status : "...";
|
|
3039
|
-
const durationStr = duration !== undefined ? ` (${duration}ms)` : "";
|
|
3040
|
-
console.log(`[network] ${method} ${url} ${statusStr}${durationStr}`);
|
|
3093
|
+
connectionTimeout = setTimeout(() => {
|
|
3094
|
+
console.error(`Error: Stream connection timeout (10s) - could not connect to ${endpoint.display}`);
|
|
3095
|
+
sock.destroy();
|
|
3096
|
+
process.exit(1);
|
|
3097
|
+
}, 10000);
|
|
3098
|
+
|
|
3099
|
+
const parser = createFrameParser({
|
|
3100
|
+
onFrame(msg) {
|
|
3101
|
+
if (!receivedData) {
|
|
3102
|
+
receivedData = true;
|
|
3103
|
+
if (connectionTimeout) {
|
|
3104
|
+
clearTimeout(connectionTimeout);
|
|
3105
|
+
connectionTimeout = null;
|
|
3041
3106
|
}
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3107
|
+
}
|
|
3108
|
+
if (msg.error) {
|
|
3109
|
+
console.error("Error:", msg.error);
|
|
3110
|
+
sock.end();
|
|
3111
|
+
process.exit(1);
|
|
3112
|
+
}
|
|
3113
|
+
if (msg.type === "extension_disconnected") {
|
|
3114
|
+
console.error(msg.message);
|
|
3115
|
+
sock.end();
|
|
3116
|
+
process.exit(1);
|
|
3117
|
+
}
|
|
3118
|
+
if (msg.type === "stream_started") return;
|
|
3119
|
+
if (msg.type === "console_event") {
|
|
3120
|
+
const { level, text, timestamp } = msg;
|
|
3121
|
+
if (streamLevel && level !== streamLevel) return;
|
|
3122
|
+
console.log(`[console] [${level}] ${formatTime(timestamp)} ${text}`);
|
|
3123
|
+
} else if (msg.type === "network_event") {
|
|
3124
|
+
const { method, url, status, duration } = msg;
|
|
3125
|
+
if (streamFilter && !url.includes(streamFilter)) return;
|
|
3126
|
+
const statusStr = status !== undefined ? status : "...";
|
|
3127
|
+
const durationStr = duration !== undefined ? ` (${duration}ms)` : "";
|
|
3128
|
+
console.log(`[network] ${method} ${url} ${statusStr}${durationStr}`);
|
|
3129
|
+
}
|
|
3130
|
+
},
|
|
3131
|
+
onError(error) {
|
|
3132
|
+
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
3133
|
+
console.error("Error:", error.message);
|
|
3134
|
+
sock.destroy();
|
|
3135
|
+
process.exit(1);
|
|
3136
|
+
},
|
|
3044
3137
|
});
|
|
3138
|
+
sock.on("data", (data) => parser.push(data));
|
|
3045
3139
|
|
|
3046
3140
|
sock.on("error", (e) => {
|
|
3047
|
-
|
|
3141
|
+
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
3142
|
+
console.error("Error:", formatEndpointError(e, endpoint, formatSocketError));
|
|
3048
3143
|
process.exit(1);
|
|
3049
3144
|
});
|
|
3050
3145
|
|
|
3051
3146
|
process.on("SIGINT", () => {
|
|
3052
|
-
|
|
3147
|
+
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
3148
|
+
streamWriter?.send({ type: "stream_stop" }).catch(() => {});
|
|
3053
3149
|
sock.end();
|
|
3054
3150
|
process.exit(0);
|
|
3055
3151
|
});
|
|
@@ -3057,6 +3153,17 @@ if (streamMode && (tool === "console" || tool === "network")) {
|
|
|
3057
3153
|
return;
|
|
3058
3154
|
}
|
|
3059
3155
|
|
|
3156
|
+
let transferPlan;
|
|
3157
|
+
try {
|
|
3158
|
+
transferPlan = endpoint.kind === "remote" ? prepareRemoteTool(finalTool, toolArgs) : (() => { const args = validateLocalToolPaths(finalTool, toolArgs); return { args, uploads: [], downloads: [] }; })();
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
const message = finalTool === "record" && endpoint.kind === "remote"
|
|
3161
|
+
? `record is not supported with remote endpoint ${endpoint.display}`
|
|
3162
|
+
: error.message;
|
|
3163
|
+
console.error(`Error: ${message}`);
|
|
3164
|
+
process.exit(1);
|
|
3165
|
+
}
|
|
3166
|
+
toolArgs = transferPlan.args;
|
|
3060
3167
|
const request = {
|
|
3061
3168
|
type: "tool_request",
|
|
3062
3169
|
method: "execute_tool",
|
|
@@ -3065,45 +3172,20 @@ const request = {
|
|
|
3065
3172
|
...globalOpts,
|
|
3066
3173
|
};
|
|
3067
3174
|
|
|
3068
|
-
const sendRequest = (toolName, toolArgs = {}, timeoutMs = 5000) => {
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
buf += d.toString();
|
|
3083
|
-
const lines = buf.split("\n");
|
|
3084
|
-
buf = lines.pop();
|
|
3085
|
-
for (const line of lines) {
|
|
3086
|
-
if (!line.trim()) continue;
|
|
3087
|
-
try {
|
|
3088
|
-
const resp = JSON.parse(line);
|
|
3089
|
-
if (resp.type === "extension_disconnected") {
|
|
3090
|
-
sock.end();
|
|
3091
|
-
reject(new Error(resp.message));
|
|
3092
|
-
return;
|
|
3093
|
-
}
|
|
3094
|
-
sock.end();
|
|
3095
|
-
resolve(resp);
|
|
3096
|
-
} catch {
|
|
3097
|
-
sock.end();
|
|
3098
|
-
reject(new Error("Invalid JSON"));
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
});
|
|
3102
|
-
sock.on("error", (e) => reject(new Error(formatSocketError(e))));
|
|
3103
|
-
let timeoutId;
|
|
3104
|
-
timeoutId = setTimeout(() => { sock.destroy(); reject(new Error("Timeout")); }, timeoutMs);
|
|
3105
|
-
sock.on("close", () => clearTimeout(timeoutId));
|
|
3106
|
-
});
|
|
3175
|
+
const sendRequest = async (toolName, toolArgs = {}, timeoutMs = 5000) => {
|
|
3176
|
+
const transport = await openClientTransport(endpoint, { requestTimeoutMs: timeoutMs });
|
|
3177
|
+
try {
|
|
3178
|
+
const prepared = endpoint.kind === "remote" ? prepareRemoteTool(toolName, toolArgs) : (() => { const args = validateLocalToolPaths(toolName, toolArgs); return { args, uploads: [], downloads: [] }; })();
|
|
3179
|
+
return await transport.request({
|
|
3180
|
+
type: "tool_request",
|
|
3181
|
+
method: "execute_tool",
|
|
3182
|
+
params: { tool: toolName, args: prepared.args },
|
|
3183
|
+
id: "cli-" + Date.now() + "-" + Math.random(),
|
|
3184
|
+
...globalOpts,
|
|
3185
|
+
}, timeoutMs, prepared);
|
|
3186
|
+
} finally {
|
|
3187
|
+
await transport.close();
|
|
3188
|
+
}
|
|
3107
3189
|
};
|
|
3108
3190
|
|
|
3109
3191
|
function parseRecordNumber(value, fallback, name, min, max) {
|
|
@@ -3270,7 +3352,11 @@ const performAutoCapture = async () => {
|
|
|
3270
3352
|
};
|
|
3271
3353
|
|
|
3272
3354
|
if (finalTool === "record") {
|
|
3273
|
-
|
|
3355
|
+
if (endpoint.kind === "remote") {
|
|
3356
|
+
console.error(`Error: record is not supported with remote endpoint ${endpoint.display}`);
|
|
3357
|
+
process.exit(1);
|
|
3358
|
+
}
|
|
3359
|
+
installBrowserLock(lockOptions, endpoint);
|
|
3274
3360
|
runRecord()
|
|
3275
3361
|
.then(() => process.exit(0))
|
|
3276
3362
|
.catch((error) => {
|
|
@@ -3280,57 +3366,67 @@ if (finalTool === "record") {
|
|
|
3280
3366
|
return;
|
|
3281
3367
|
}
|
|
3282
3368
|
|
|
3283
|
-
installBrowserLock(lockOptions);
|
|
3369
|
+
installBrowserLock(lockOptions, endpoint);
|
|
3370
|
+
let socket;
|
|
3371
|
+
let timeout;
|
|
3372
|
+
|
|
3373
|
+
if (endpoint.kind === "remote") {
|
|
3374
|
+
socket = { end() {}, destroy() {} };
|
|
3375
|
+
const requestTimeout = resolveRequestDeadlineMs(tool, toolArgs);
|
|
3376
|
+
openClientTransport(endpoint, { requestTimeoutMs: requestTimeout })
|
|
3377
|
+
.then(async (transport) => {
|
|
3378
|
+
try {
|
|
3379
|
+
const response = await transport.request(request, requestTimeout, transferPlan);
|
|
3380
|
+
await handleResponse(response);
|
|
3381
|
+
} finally {
|
|
3382
|
+
await transport.close();
|
|
3383
|
+
}
|
|
3384
|
+
})
|
|
3385
|
+
.catch((error) => {
|
|
3386
|
+
console.error(`Error: ${error.message}`);
|
|
3387
|
+
process.exit(1);
|
|
3388
|
+
});
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3284
3391
|
|
|
3285
|
-
|
|
3286
|
-
socket.
|
|
3392
|
+
socket = connectEndpoint(endpoint, () => {
|
|
3393
|
+
writeFrame(socket, request).catch((error) => socket.destroy(error));
|
|
3287
3394
|
});
|
|
3288
3395
|
|
|
3289
|
-
const
|
|
3290
|
-
|
|
3291
|
-
if (tool === "aistudio.build") {
|
|
3292
|
-
const userTimeoutSec = parseInt(options.timeout || "600", 10);
|
|
3293
|
-
requestTimeout = (userTimeoutSec * 1000) + 60000;
|
|
3294
|
-
}
|
|
3295
|
-
const timeout = setTimeout(() => {
|
|
3396
|
+
const requestTimeout = resolveRequestDeadlineMs(tool, options);
|
|
3397
|
+
timeout = setTimeout(() => {
|
|
3296
3398
|
console.error(`Error: Request timed out (${requestTimeout / 1000}s)`);
|
|
3297
3399
|
socket.destroy();
|
|
3298
3400
|
process.exit(1);
|
|
3299
3401
|
}, requestTimeout);
|
|
3300
3402
|
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
for (const line of lines) {
|
|
3309
|
-
if (!line.trim()) continue;
|
|
3310
|
-
try {
|
|
3311
|
-
const msg = JSON.parse(line);
|
|
3312
|
-
|
|
3313
|
-
if (msg.type === "extension_disconnected") {
|
|
3314
|
-
clearTimeout(timeout);
|
|
3315
|
-
console.error(msg.message);
|
|
3316
|
-
socket.end();
|
|
3317
|
-
process.exit(1);
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
handleResponse(msg).catch((err) => {
|
|
3321
|
-
console.error("Handler error:", err.message);
|
|
3322
|
-
process.exit(1);
|
|
3323
|
-
});
|
|
3324
|
-
} catch (e) {
|
|
3325
|
-
console.error("Invalid JSON response:", line);
|
|
3403
|
+
const responseParser = createFrameParser({
|
|
3404
|
+
onFrame(msg) {
|
|
3405
|
+
if (msg.type === "extension_disconnected") {
|
|
3406
|
+
clearTimeout(timeout);
|
|
3407
|
+
console.error(msg.message);
|
|
3408
|
+
socket.end();
|
|
3326
3409
|
process.exit(1);
|
|
3327
3410
|
}
|
|
3328
|
-
|
|
3411
|
+
if (msg.id !== request.id) return;
|
|
3412
|
+
handleResponse(msg).catch((err) => {
|
|
3413
|
+
console.error("Handler error:", err.message);
|
|
3414
|
+
process.exit(1);
|
|
3415
|
+
});
|
|
3416
|
+
},
|
|
3417
|
+
onError(error) {
|
|
3418
|
+
clearTimeout(timeout);
|
|
3419
|
+
console.error("Invalid response frame:", error.message);
|
|
3420
|
+
socket.destroy();
|
|
3421
|
+
process.exit(1);
|
|
3422
|
+
},
|
|
3329
3423
|
});
|
|
3330
3424
|
|
|
3425
|
+
socket.on("data", (data) => responseParser.push(data));
|
|
3426
|
+
|
|
3331
3427
|
socket.on("error", (err) => {
|
|
3332
3428
|
clearTimeout(timeout);
|
|
3333
|
-
console.error("Error:",
|
|
3429
|
+
console.error("Error:", formatEndpointError(err, endpoint, formatSocketError));
|
|
3334
3430
|
process.exit(1);
|
|
3335
3431
|
});
|
|
3336
3432
|
|
|
@@ -3389,7 +3485,7 @@ async function handleResponse(response) {
|
|
|
3389
3485
|
}
|
|
3390
3486
|
|
|
3391
3487
|
if (tool === "screenshot" && data?.base64 && (outputPath || toolArgs.savePath)) {
|
|
3392
|
-
const saveTo =
|
|
3488
|
+
const saveTo = transferPlan.downloads?.[0]?.destination || toolArgs.savePath || outputPath;
|
|
3393
3489
|
fs.writeFileSync(saveTo, Buffer.from(data.base64, "base64"));
|
|
3394
3490
|
|
|
3395
3491
|
const skipResize = options.full || toolArgs.full;
|