surf-cli 2.10.0 → 2.12.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 +46 -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 +252 -664
- package/native/cli.cjs +86 -3
- package/native/host-helpers.cjs +38 -1
- package/native/host-sessions.cjs +1 -0
- package/native/host.cjs +30 -5
- 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-cli.cjs +3 -2
- package/native/playbook-runtime.cjs +29 -2
- package/native/playbooks.cjs +13 -0
- package/native/workflow-definition.cjs +1 -0
- package/native/workflow-script-runtime.cjs +294 -0
- package/package.json +20 -5
- package/pi-extension/surf.ts +230 -0
- package/skills/surf/SKILL.md +55 -6
package/native/cli.cjs
CHANGED
|
@@ -19,6 +19,11 @@ const {
|
|
|
19
19
|
const { executeDoSteps } = require("./do-executor.cjs");
|
|
20
20
|
const { openClientTransport } = require("./client-transport.cjs");
|
|
21
21
|
const { version: VERSION } = require("../package.json");
|
|
22
|
+
const {
|
|
23
|
+
formatOracleError,
|
|
24
|
+
formatOracleOutput,
|
|
25
|
+
handleOracleCli,
|
|
26
|
+
} = require("./oracle-cli.cjs");
|
|
22
27
|
const { formatPlaybookOutput, handlePlaybookCli, playbookCommandNeedsBrowser } = require("./playbook-cli.cjs");
|
|
23
28
|
|
|
24
29
|
const IS_WIN = process.platform === "win32";
|
|
@@ -73,6 +78,28 @@ function installBrowserLock({ noLock, timeoutMs }, endpoint) {
|
|
|
73
78
|
});
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
async function runWithBrowserLock(lockOptions, endpoint, operation) {
|
|
82
|
+
let releaseBrowserLock = () => {};
|
|
83
|
+
if (!lockOptions.noLock) {
|
|
84
|
+
const lock = acquireBrowserLock(endpoint.key, SURF_TMP, {
|
|
85
|
+
timeoutMs: lockOptions.timeoutMs,
|
|
86
|
+
});
|
|
87
|
+
releaseBrowserLock = lock.release;
|
|
88
|
+
}
|
|
89
|
+
const release = () => {
|
|
90
|
+
const releaseCurrent = releaseBrowserLock;
|
|
91
|
+
releaseBrowserLock = () => {};
|
|
92
|
+
releaseCurrent();
|
|
93
|
+
};
|
|
94
|
+
process.once("exit", release);
|
|
95
|
+
try {
|
|
96
|
+
return await operation();
|
|
97
|
+
} finally {
|
|
98
|
+
process.removeListener("exit", release);
|
|
99
|
+
release();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
// Cross-platform image resize (macOS: sips, Linux: ImageMagick)
|
|
77
104
|
function resizeImage(filePath, maxSize) {
|
|
78
105
|
const platform = process.platform;
|
|
@@ -153,6 +180,28 @@ try {
|
|
|
153
180
|
process.exit(1);
|
|
154
181
|
}
|
|
155
182
|
|
|
183
|
+
if (args[0] === "oracle") {
|
|
184
|
+
handleOracleCli(args, {
|
|
185
|
+
endpoint,
|
|
186
|
+
cwd: process.cwd(),
|
|
187
|
+
withBrowserLock: (operation) => runWithBrowserLock(
|
|
188
|
+
parseBrowserLockOptions(args.includes("--no-lock")),
|
|
189
|
+
endpoint,
|
|
190
|
+
operation,
|
|
191
|
+
),
|
|
192
|
+
})
|
|
193
|
+
.then((result) => {
|
|
194
|
+
if (!result.handled) throw new Error("Oracle command was not handled");
|
|
195
|
+
if (result.value !== undefined) console.log(formatOracleOutput(result.value, result.json));
|
|
196
|
+
process.exit(0);
|
|
197
|
+
})
|
|
198
|
+
.catch((error) => {
|
|
199
|
+
console.error(formatOracleError(error, args.includes("--json")));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
156
205
|
if (["playbook", "pb", "use"].includes(args[0])) {
|
|
157
206
|
if (playbookCommandNeedsBrowser(args)) {
|
|
158
207
|
installBrowserLock(parseBrowserLockOptions(args.includes("--no-lock")), endpoint);
|
|
@@ -484,7 +533,7 @@ const TOOLS = {
|
|
|
484
533
|
"scroll": {
|
|
485
534
|
desc: "Scroll in direction",
|
|
486
535
|
args: ["direction", "pixels"],
|
|
487
|
-
opts: { direction: "up|down|left|right", amount: "Scroll amount (1-10)" },
|
|
536
|
+
opts: { direction: "up|down|left|right", amount: "Scroll amount in 100 px steps (1-10)" },
|
|
488
537
|
examples: [
|
|
489
538
|
{ cmd: "scroll down 800", desc: "Scroll down 800px" },
|
|
490
539
|
{ cmd: "scroll --direction down --amount 3", desc: "Scroll down" },
|
|
@@ -527,6 +576,18 @@ const TOOLS = {
|
|
|
527
576
|
},
|
|
528
577
|
"read": { desc: "Alias for page.read", args: [], alias: "page.read" },
|
|
529
578
|
"page.text": { desc: "Extract all text from page", args: [] },
|
|
579
|
+
"page.html": {
|
|
580
|
+
desc: "Print rendered document HTML",
|
|
581
|
+
args: [],
|
|
582
|
+
opts: { selector: "Export matching CSS selector", "strip-scripts": "Remove script elements" },
|
|
583
|
+
examples: [{ cmd: "page.html", desc: "Print current document HTML" }],
|
|
584
|
+
},
|
|
585
|
+
"page.save": {
|
|
586
|
+
desc: "Save rendered document HTML",
|
|
587
|
+
args: [],
|
|
588
|
+
opts: { output: "File path", selector: "Export matching CSS selector", "strip-scripts": "Remove script elements" },
|
|
589
|
+
examples: [{ cmd: "page.save --output page.html", desc: "Save current document HTML" }],
|
|
590
|
+
},
|
|
530
591
|
"page.state": { desc: "Get page state (modals, loading, etc.)", args: [] },
|
|
531
592
|
}
|
|
532
593
|
},
|
|
@@ -1415,7 +1476,7 @@ const ALL_SOCKET_TOOLS = [
|
|
|
1415
1476
|
"click_type", "click_type_submit", "type", "key", "type_submit",
|
|
1416
1477
|
"scroll", "scroll_to", "hover", "left_click_drag", "drag", "wait",
|
|
1417
1478
|
"computer",
|
|
1418
|
-
"page.read", "page.text", "page.state",
|
|
1479
|
+
"page.read", "page.text", "page.html", "page.save", "page.state",
|
|
1419
1480
|
"locate.role", "locate.text", "locate.label",
|
|
1420
1481
|
"tab.list", "tab.new", "tab.switch", "tab.close", "tab.move", "tab.name", "tab.unname", "tab.named",
|
|
1421
1482
|
"tab.group", "tab.ungroup", "tab.groups", "tab.reload",
|
|
@@ -1496,6 +1557,7 @@ Common Commands:
|
|
|
1496
1557
|
search <term> Search for text in page (alias: find)
|
|
1497
1558
|
window.new <url> Create isolated browser window
|
|
1498
1559
|
doctor Diagnose native host/socket setup
|
|
1560
|
+
oracle ask <prompt> Start a durable ChatGPT consult
|
|
1499
1561
|
wait <seconds> Wait N seconds
|
|
1500
1562
|
|
|
1501
1563
|
Quick Examples:
|
|
@@ -1555,6 +1617,9 @@ const showFullHelp = () => {
|
|
|
1555
1617
|
|
|
1556
1618
|
Usage: surf <command> [args] [options]
|
|
1557
1619
|
|
|
1620
|
+
Oracle:
|
|
1621
|
+
surf oracle <ask|status|result|follow|list>
|
|
1622
|
+
|
|
1558
1623
|
Playbooks:
|
|
1559
1624
|
surf playbook|pb <list|show|ops|run|record|suggest|save|client|trace|export|import>
|
|
1560
1625
|
surf use <playbook> <op> [--arg value]
|
|
@@ -2765,11 +2830,16 @@ if (tool === "network.export" && outputPath !== undefined) {
|
|
|
2765
2830
|
toolArgs.output = outputPath;
|
|
2766
2831
|
}
|
|
2767
2832
|
|
|
2768
|
-
if ((tool === "screenshot" || tool === "record" || tool === "perf-audit") && outputPath && typeof outputPath !== "string") {
|
|
2833
|
+
if ((tool === "screenshot" || tool === "record" || tool === "perf-audit" || tool === "page.save") && outputPath && typeof outputPath !== "string") {
|
|
2769
2834
|
console.error("Error: --output requires a file path");
|
|
2770
2835
|
process.exit(1);
|
|
2771
2836
|
}
|
|
2772
2837
|
|
|
2838
|
+
if (tool === "page.save" && !outputPath) {
|
|
2839
|
+
console.error("Error: page.save requires --output <path>");
|
|
2840
|
+
process.exit(1);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2773
2843
|
if (tool === "screenshot" && outputPath) {
|
|
2774
2844
|
toolArgs.savePath = outputPath;
|
|
2775
2845
|
if (options.full) toolArgs.full = true;
|
|
@@ -3241,6 +3311,17 @@ async function handleResponse(response) {
|
|
|
3241
3311
|
data = { response: data };
|
|
3242
3312
|
}
|
|
3243
3313
|
|
|
3314
|
+
if (tool === "page.save" && typeof data?.html === "string") {
|
|
3315
|
+
const saveTo = path.resolve(outputPath);
|
|
3316
|
+
fs.mkdirSync(path.dirname(saveTo), { recursive: true });
|
|
3317
|
+
fs.writeFileSync(saveTo, data.html);
|
|
3318
|
+
if (!wantJson) {
|
|
3319
|
+
console.log(`Saved rendered page HTML to ${saveTo}`);
|
|
3320
|
+
socket.end();
|
|
3321
|
+
process.exit(0);
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3244
3325
|
if (tool === "perf-audit" && outputPath) {
|
|
3245
3326
|
const saveTo = path.resolve(outputPath);
|
|
3246
3327
|
fs.mkdirSync(path.dirname(saveTo), { recursive: true });
|
|
@@ -3322,6 +3403,8 @@ async function handleResponse(response) {
|
|
|
3322
3403
|
console.log(data.pageContent);
|
|
3323
3404
|
} else if (tool === "page.text" && data?.text) {
|
|
3324
3405
|
console.log(data.text);
|
|
3406
|
+
} else if (tool === "page.html" && typeof data?.html === "string") {
|
|
3407
|
+
console.log(data.html);
|
|
3325
3408
|
} else if (tool === "emulate.device" && data?.devices) {
|
|
3326
3409
|
console.log("Available devices:\n");
|
|
3327
3410
|
const devices = data.devices;
|
package/native/host-helpers.cjs
CHANGED
|
@@ -13,6 +13,21 @@ function normalizeModelString(model) {
|
|
|
13
13
|
return String(model || "").trim().toLowerCase();
|
|
14
14
|
}
|
|
15
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
|
+
|
|
16
31
|
/**
|
|
17
32
|
* Format tool result content for MCP response
|
|
18
33
|
* @param {*} result - The result object from the extension
|
|
@@ -921,6 +936,18 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
921
936
|
}
|
|
922
937
|
case "page.text":
|
|
923
938
|
return { type: "GET_PAGE_TEXT", ...baseMsg };
|
|
939
|
+
case "page.html":
|
|
940
|
+
case "page.save": {
|
|
941
|
+
if (a.selector !== undefined && (typeof a.selector !== "string" || a.selector.length === 0)) {
|
|
942
|
+
throw new Error("selector must be a non-empty string");
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
type: "GET_PAGE_HTML",
|
|
946
|
+
selector: a.selector,
|
|
947
|
+
stripScripts: a["strip-scripts"] === true,
|
|
948
|
+
...baseMsg,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
924
951
|
case "page.state":
|
|
925
952
|
return { type: "PAGE_STATE", ...baseMsg };
|
|
926
953
|
case "locate.role":
|
|
@@ -1082,6 +1109,16 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
1082
1109
|
case "history.search":
|
|
1083
1110
|
if (!a.query) throw new Error("query required");
|
|
1084
1111
|
return { type: "HISTORY_SEARCH", query: a.query, limit: a.limit !== undefined ? parseInt(a.limit, 10) : 20 };
|
|
1112
|
+
case "oracle.ask":
|
|
1113
|
+
if (!a.prompt) throw new Error("prompt required");
|
|
1114
|
+
return { ...a, type: "ORACLE_ASK" };
|
|
1115
|
+
case "oracle.status":
|
|
1116
|
+
return { ...a, type: "ORACLE_STATUS" };
|
|
1117
|
+
case "oracle.result":
|
|
1118
|
+
if (!a.id) throw new Error("id required");
|
|
1119
|
+
return { ...a, type: "ORACLE_RESULT" };
|
|
1120
|
+
case "oracle.list":
|
|
1121
|
+
return { type: "ORACLE_LIST" };
|
|
1085
1122
|
case "chatgpt":
|
|
1086
1123
|
if (!a.query) throw new Error("query required");
|
|
1087
1124
|
return {
|
|
@@ -1196,4 +1233,4 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
1196
1233
|
}
|
|
1197
1234
|
}
|
|
1198
1235
|
|
|
1199
|
-
module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, buildProviderUploadMessage };
|
|
1236
|
+
module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage };
|
package/native/host-sessions.cjs
CHANGED
package/native/host.cjs
CHANGED
|
@@ -14,7 +14,8 @@ const perplexityClient = require("./perplexity-client.cjs");
|
|
|
14
14
|
const grokClient = require("./grok-client.cjs");
|
|
15
15
|
const aistudioClient = require("./aistudio-client.cjs");
|
|
16
16
|
const aistudioBuild = require("./aistudio-build.cjs");
|
|
17
|
-
const { mapToolToMessage, mapComputerAction, formatToolContent, buildProviderUploadMessage } = require("./host-helpers.cjs");
|
|
17
|
+
const { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage } = require("./host-helpers.cjs");
|
|
18
|
+
const { createOracleHost } = require("./oracle-host.cjs");
|
|
18
19
|
|
|
19
20
|
const IS_WIN = process.platform === "win32";
|
|
20
21
|
const { SOCKET_PATH, SURF_TMP } = require("./socket-path.cjs");
|
|
@@ -402,6 +403,15 @@ aiQueue = new BoundedAiQueue({
|
|
|
402
403
|
: handler(),
|
|
403
404
|
});
|
|
404
405
|
|
|
406
|
+
const oracleHost = createOracleHost({
|
|
407
|
+
queueAiRequest,
|
|
408
|
+
requestCallExtension,
|
|
409
|
+
buildProviderUploadMessage,
|
|
410
|
+
log,
|
|
411
|
+
});
|
|
412
|
+
const adoptedOracleJobs = oracleHost.adoptOrphans();
|
|
413
|
+
log(`Oracle adoption: ${adoptedOracleJobs.length} job(s); ids=${adoptedOracleJobs.map((job) => job.id).join(",") || "none"}`);
|
|
414
|
+
|
|
405
415
|
function sendSocket(socket, value, options = {}) {
|
|
406
416
|
const writer = socketWriters.get(socket);
|
|
407
417
|
return writer ? writer.send(value, options) : writeFrame(socket, value);
|
|
@@ -513,6 +523,7 @@ async function runHostPlaybook(msg, request) {
|
|
|
513
523
|
onEvent: report,
|
|
514
524
|
beforeDispatch: async () => updateReceipt(receipt, "dispatched"),
|
|
515
525
|
afterDispatch: async ({ status, error }) => updateReceipt(receipt, status, { error }),
|
|
526
|
+
allowScript: params.allowScript === true,
|
|
516
527
|
});
|
|
517
528
|
}
|
|
518
529
|
|
|
@@ -672,8 +683,14 @@ function sendToolResponse(socket, id, result, error) {
|
|
|
672
683
|
} catch (transferFailure) {
|
|
673
684
|
finalError = transferFailure.message;
|
|
674
685
|
}
|
|
675
|
-
|
|
676
|
-
|
|
686
|
+
const formattedError = finalError ? formatToolError(finalError) : null;
|
|
687
|
+
if (formattedError && request) {
|
|
688
|
+
const rewrittenMessage = rewriteTransferPaths(
|
|
689
|
+
formattedError.content[0].text,
|
|
690
|
+
request.pathRewrites || [],
|
|
691
|
+
);
|
|
692
|
+
formattedError.content[0].text = rewrittenMessage;
|
|
693
|
+
if (formattedError.message) formattedError.message = rewrittenMessage;
|
|
677
694
|
}
|
|
678
695
|
if (request?.tool && !request.tool.startsWith("playbook.")) {
|
|
679
696
|
const metadata = commandMetadata(request.tool);
|
|
@@ -697,7 +714,7 @@ function sendToolResponse(socket, id, result, error) {
|
|
|
697
714
|
: finalError ? "error" : "completed";
|
|
698
715
|
await completeOwnedRequest(context, id, outcome);
|
|
699
716
|
const response = { type: "tool_response", id };
|
|
700
|
-
if (
|
|
717
|
+
if (formattedError) response.error = formattedError;
|
|
701
718
|
else response.result = { content: formatToolContent(output, log, { suppressImages: Boolean(context?.isRemote) }) };
|
|
702
719
|
if (!context?.closed) await sendSocket(socket, response);
|
|
703
720
|
})().catch((sendError) => log(`Error sending tool_response: ${sendError.message}`));
|
|
@@ -800,6 +817,13 @@ function handleToolRequest(msg, socket, requestContext = requestStorage.getStore
|
|
|
800
817
|
.catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
801
818
|
return;
|
|
802
819
|
}
|
|
820
|
+
|
|
821
|
+
if (extensionMsg.type.startsWith("ORACLE_")) {
|
|
822
|
+
Promise.resolve(oracleHost.handle(requestContext, extensionMsg))
|
|
823
|
+
.then((result) => sendToolResponse(socket, originalId, result, null))
|
|
824
|
+
.catch((error) => sendToolResponse(socket, originalId, null, error));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
803
827
|
|
|
804
828
|
if (extensionMsg.type === "BATCH_EXECUTE") {
|
|
805
829
|
executeBatch(extensionMsg.actions, extensionMsg.tabId, socket, originalId, requestContext);
|
|
@@ -1948,6 +1972,7 @@ const handleClient = (socket) => {
|
|
|
1948
1972
|
}
|
|
1949
1973
|
log(`Handling tool_request: ${msg.method} ${tool}${principal ? ` for ${principal.label}` : ""}`);
|
|
1950
1974
|
try {
|
|
1975
|
+
if (tool.startsWith("oracle.")) oracleHost.assertLocal(request);
|
|
1951
1976
|
if (isRemote) {
|
|
1952
1977
|
await applyRequestTransfers(msg, request, transferState, ensureTransferState);
|
|
1953
1978
|
}
|
|
@@ -1955,7 +1980,7 @@ const handleClient = (socket) => {
|
|
|
1955
1980
|
requestStorage.run(request, () => handleToolRequest(msg, socket, request));
|
|
1956
1981
|
} catch (e) {
|
|
1957
1982
|
await discardRequestTransfers(msg, transferState);
|
|
1958
|
-
sendToolResponse(socket, msg.id || null, null, e.message || "Request failed");
|
|
1983
|
+
sendToolResponse(socket, msg.id || null, null, e?.code ? e : e.message || "Request failed");
|
|
1959
1984
|
}
|
|
1960
1985
|
return;
|
|
1961
1986
|
}
|