surf-cli 2.5.2 → 2.7.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 -3
- package/dist/content/accessibility-tree.js +8 -8
- package/dist/content/accessibility-tree.js.map +1 -1
- package/dist/content/visual-indicator.js +17 -17
- package/dist/content/visual-indicator.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/service-worker/index.js +27 -27
- package/dist/service-worker/index.js.map +1 -1
- package/native/aistudio-build.cjs +562 -0
- package/native/aistudio-client.cjs +502 -0
- package/native/aistudio-model.cjs +225 -0
- package/native/aistudio-parser.cjs +424 -0
- package/native/aistudio-response.cjs +336 -0
- package/native/cli.cjs +95 -14
- package/native/do-executor.cjs +1 -1
- package/native/gemini-client.cjs +304 -63
- package/native/host-helpers.cjs +29 -0
- package/native/host.cjs +261 -14
- package/native/mcp-server.cjs +1 -1
- package/native/network-store.cjs +3 -1
- package/package.json +10 -9
- package/scripts/install-native-host.cjs +14 -2
- package/scripts/uninstall-native-host.cjs +7 -1
- package/skills/README.md +21 -0
- package/skills/surf/SKILL.md +575 -0
package/native/host.cjs
CHANGED
|
@@ -10,9 +10,14 @@ const chatgptClient = require("./chatgpt-client.cjs");
|
|
|
10
10
|
const geminiClient = require("./gemini-client.cjs");
|
|
11
11
|
const perplexityClient = require("./perplexity-client.cjs");
|
|
12
12
|
const grokClient = require("./grok-client.cjs");
|
|
13
|
+
const aistudioClient = require("./aistudio-client.cjs");
|
|
14
|
+
const aistudioBuild = require("./aistudio-build.cjs");
|
|
13
15
|
const { mapToolToMessage, mapComputerAction, formatToolContent } = require("./host-helpers.cjs");
|
|
14
16
|
|
|
15
|
-
const
|
|
17
|
+
const IS_WIN = process.platform === "win32";
|
|
18
|
+
const SURF_TMP = IS_WIN ? path.join(os.tmpdir(), "surf") : "/tmp";
|
|
19
|
+
const SOCKET_PATH = IS_WIN ? "//./pipe/surf" : "/tmp/surf.sock";
|
|
20
|
+
if (IS_WIN) { try { fs.mkdirSync(SURF_TMP, { recursive: true }); } catch {} }
|
|
16
21
|
|
|
17
22
|
// Cross-platform image resize (macOS: sips, Linux: ImageMagick)
|
|
18
23
|
function resizeImage(filePath, maxSize) {
|
|
@@ -27,12 +32,13 @@ function resizeImage(filePath, maxSize) {
|
|
|
27
32
|
const height = parseInt(sizeInfo.match(/pixelHeight:\s*(\d+)/)?.[1] || "0", 10);
|
|
28
33
|
return { success: true, width, height };
|
|
29
34
|
} else {
|
|
30
|
-
// Linux/
|
|
35
|
+
// Linux/Windows: use ImageMagick (try IM6 first, then IM7)
|
|
36
|
+
const resizeArg = IS_WIN ? `"${maxSize}x${maxSize}>"` : `${maxSize}x${maxSize}\\>`;
|
|
31
37
|
try {
|
|
32
|
-
execSync(`convert "${filePath}" -resize ${
|
|
38
|
+
execSync(`convert "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" });
|
|
33
39
|
} catch {
|
|
34
40
|
// IM7 uses 'magick' as main command
|
|
35
|
-
execSync(`magick "${filePath}" -resize ${
|
|
41
|
+
execSync(`magick "${filePath}" -resize ${resizeArg} "${filePath}"`, { stdio: "pipe" });
|
|
36
42
|
}
|
|
37
43
|
// Get dimensions (IM7 may need 'magick identify' instead of just 'identify')
|
|
38
44
|
let sizeInfo;
|
|
@@ -73,7 +79,7 @@ async function processAiQueue() {
|
|
|
73
79
|
setTimeout(processAiQueue, 2000);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
|
-
const LOG_FILE = "
|
|
82
|
+
const LOG_FILE = path.join(SURF_TMP, "surf-host.log");
|
|
77
83
|
const AUTH_FILE = path.join(os.homedir(), ".pi", "agent", "auth.json");
|
|
78
84
|
|
|
79
85
|
const DEFAULT_RETRY_OPTIONS = {
|
|
@@ -288,9 +294,7 @@ const log = (msg) => {
|
|
|
288
294
|
|
|
289
295
|
log("Host starting...");
|
|
290
296
|
|
|
291
|
-
try {
|
|
292
|
-
fs.unlinkSync(SOCKET_PATH);
|
|
293
|
-
} catch {}
|
|
297
|
+
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
294
298
|
|
|
295
299
|
const pendingRequests = new Map();
|
|
296
300
|
const pendingToolRequests = new Map();
|
|
@@ -693,6 +697,57 @@ function handleToolRequest(msg, socket) {
|
|
|
693
697
|
});
|
|
694
698
|
writeMessage({ type: "GET_GOOGLE_COOKIES", id: cookieId });
|
|
695
699
|
}),
|
|
700
|
+
createTab: () => new Promise((resolve) => {
|
|
701
|
+
const tabCreateId = ++requestCounter;
|
|
702
|
+
pendingToolRequests.set(tabCreateId, {
|
|
703
|
+
socket: null,
|
|
704
|
+
originalId: null,
|
|
705
|
+
tool: "create_tab",
|
|
706
|
+
onComplete: (r) => resolve(r)
|
|
707
|
+
});
|
|
708
|
+
writeMessage({ type: "GEMINI_NEW_TAB", id: tabCreateId });
|
|
709
|
+
}),
|
|
710
|
+
closeTab: (tabIdToClose) => new Promise((resolve) => {
|
|
711
|
+
const tabCloseId = ++requestCounter;
|
|
712
|
+
pendingToolRequests.set(tabCloseId, {
|
|
713
|
+
socket: null,
|
|
714
|
+
originalId: null,
|
|
715
|
+
tool: "close_tab",
|
|
716
|
+
onComplete: (r) => resolve(r)
|
|
717
|
+
});
|
|
718
|
+
writeMessage({ type: "GEMINI_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
719
|
+
}),
|
|
720
|
+
jsEval: (tabId, code) => new Promise((resolve) => {
|
|
721
|
+
const jsId = ++requestCounter;
|
|
722
|
+
pendingToolRequests.set(jsId, {
|
|
723
|
+
socket: null,
|
|
724
|
+
originalId: null,
|
|
725
|
+
tool: "js_eval",
|
|
726
|
+
onComplete: (r) => resolve(r)
|
|
727
|
+
});
|
|
728
|
+
log(`[gemini] Sending EXECUTE_JAVASCRIPT id=${jsId} tabId=${tabId} code=${code.length} chars`);
|
|
729
|
+
writeMessage({ type: "EXECUTE_JAVASCRIPT", tabId, code, id: jsId });
|
|
730
|
+
}),
|
|
731
|
+
uploadFile: (tabId, filePaths) => new Promise((resolve) => {
|
|
732
|
+
const uploadId = ++requestCounter;
|
|
733
|
+
pendingToolRequests.set(uploadId, {
|
|
734
|
+
socket: null,
|
|
735
|
+
originalId: null,
|
|
736
|
+
tool: "upload_file",
|
|
737
|
+
onComplete: (r) => resolve(r)
|
|
738
|
+
});
|
|
739
|
+
writeMessage({ type: "UPLOAD_FILE_TO_TAB", tabId, filePaths, id: uploadId });
|
|
740
|
+
}),
|
|
741
|
+
fetchUrl: (url) => new Promise((resolve) => {
|
|
742
|
+
const fetchId = ++requestCounter;
|
|
743
|
+
pendingToolRequests.set(fetchId, {
|
|
744
|
+
socket: null,
|
|
745
|
+
originalId: null,
|
|
746
|
+
tool: "fetch_url",
|
|
747
|
+
onComplete: (r) => resolve(r)
|
|
748
|
+
});
|
|
749
|
+
writeMessage({ type: "GEMINI_FETCH_URL", url, id: fetchId });
|
|
750
|
+
}),
|
|
696
751
|
log: (msg) => log(`[gemini] ${msg}`)
|
|
697
752
|
});
|
|
698
753
|
|
|
@@ -910,6 +965,198 @@ function handleToolRequest(msg, socket) {
|
|
|
910
965
|
|
|
911
966
|
return;
|
|
912
967
|
}
|
|
968
|
+
|
|
969
|
+
if (extensionMsg.type === "AISTUDIO_QUERY") {
|
|
970
|
+
const { query, model, withPage, timeout } = extensionMsg;
|
|
971
|
+
|
|
972
|
+
queueAiRequest(async () => {
|
|
973
|
+
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
974
|
+
|
|
975
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => new Promise((resolve, reject) => {
|
|
976
|
+
const id = ++requestCounter;
|
|
977
|
+
|
|
978
|
+
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
979
|
+
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/prompts/new_chat")}`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const timeoutId = setTimeout(() => {
|
|
983
|
+
pendingToolRequests.delete(id);
|
|
984
|
+
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
985
|
+
}, timeoutMs);
|
|
986
|
+
|
|
987
|
+
pendingToolRequests.set(id, {
|
|
988
|
+
socket: null,
|
|
989
|
+
originalId: null,
|
|
990
|
+
tool: toolName,
|
|
991
|
+
onComplete: (r) => {
|
|
992
|
+
clearTimeout(timeoutId);
|
|
993
|
+
resolve(r);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
writeMessage({ ...msg, id });
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// 1. Get page context if requested
|
|
1001
|
+
let pageContext = null;
|
|
1002
|
+
if (withPage) {
|
|
1003
|
+
const pageResult = await callExtension(
|
|
1004
|
+
"get_page_text",
|
|
1005
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
1006
|
+
45000
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
if (pageResult && !pageResult.error) {
|
|
1010
|
+
pageContext = {
|
|
1011
|
+
url: pageResult.url,
|
|
1012
|
+
text: pageResult.text || pageResult.pageContent || ""
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// 2. Build full prompt
|
|
1018
|
+
let fullPrompt = query || "";
|
|
1019
|
+
if (pageContext) {
|
|
1020
|
+
const MAX_PAGE_CONTEXT_CHARS = 20000;
|
|
1021
|
+
const pageText = String(pageContext.text || "");
|
|
1022
|
+
const truncated = pageText.length > MAX_PAGE_CONTEXT_CHARS
|
|
1023
|
+
? pageText.slice(0, MAX_PAGE_CONTEXT_CHARS) + "\n\n[...truncated...]"
|
|
1024
|
+
: pageText;
|
|
1025
|
+
|
|
1026
|
+
fullPrompt = `Page: ${pageContext.url}\n\n${truncated}\n\n---\n\n${fullPrompt}`;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// 3. Call AI Studio client
|
|
1030
|
+
const result = await aistudioClient.query({
|
|
1031
|
+
prompt: fullPrompt,
|
|
1032
|
+
model: model || undefined,
|
|
1033
|
+
timeout: timeout || 300000,
|
|
1034
|
+
getCookies: () => callExtension("get_cookies", { type: "GET_GOOGLE_COOKIES" }, 45000),
|
|
1035
|
+
createTab: (url) => callExtension(
|
|
1036
|
+
"create_tab",
|
|
1037
|
+
{ type: "AISTUDIO_NEW_TAB", url },
|
|
1038
|
+
45000
|
|
1039
|
+
),
|
|
1040
|
+
closeTab: (tabIdToClose) => callExtension(
|
|
1041
|
+
"close_tab",
|
|
1042
|
+
{ type: "AISTUDIO_CLOSE_TAB", tabId: tabIdToClose },
|
|
1043
|
+
45000
|
|
1044
|
+
),
|
|
1045
|
+
cdpEvaluate: (tabId, expression) => callExtension(
|
|
1046
|
+
"cdp_evaluate",
|
|
1047
|
+
{ type: "AISTUDIO_EVALUATE", tabId, expression }
|
|
1048
|
+
),
|
|
1049
|
+
cdpCommand: (tabId, method, params) => callExtension(
|
|
1050
|
+
"cdp_command",
|
|
1051
|
+
{ type: "AISTUDIO_CDP_COMMAND", tabId, method, params }
|
|
1052
|
+
),
|
|
1053
|
+
readNetworkEntries: (tabIdToRead) => callExtension(
|
|
1054
|
+
"read_network_entries",
|
|
1055
|
+
{
|
|
1056
|
+
type: "READ_NETWORK_REQUESTS",
|
|
1057
|
+
tabId: tabIdToRead,
|
|
1058
|
+
full: true,
|
|
1059
|
+
limit: 100,
|
|
1060
|
+
urlPattern: "MakerSuiteService/GenerateContent"
|
|
1061
|
+
},
|
|
1062
|
+
45000
|
|
1063
|
+
),
|
|
1064
|
+
log: (msg) => log(`[aistudio] ${msg}`)
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
return result;
|
|
1068
|
+
}).then((result) => {
|
|
1069
|
+
const payload = {
|
|
1070
|
+
response: result.response,
|
|
1071
|
+
model: result.model,
|
|
1072
|
+
thinkingTime: result.thinkingTime,
|
|
1073
|
+
tookMs: result.tookMs
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
sendToolResponse(socket, originalId, { output: JSON.stringify(payload) }, null);
|
|
1077
|
+
}).catch((err) => {
|
|
1078
|
+
sendToolResponse(socket, originalId, null, err.message);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (extensionMsg.type === "AISTUDIO_BUILD") {
|
|
1085
|
+
const { query, model, output, keepOpen, timeout } = extensionMsg;
|
|
1086
|
+
|
|
1087
|
+
queueAiRequest(async () => {
|
|
1088
|
+
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
1089
|
+
|
|
1090
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => new Promise((resolve, reject) => {
|
|
1091
|
+
const id = ++requestCounter;
|
|
1092
|
+
|
|
1093
|
+
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
1094
|
+
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/apps")}`);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const timeoutId = setTimeout(() => {
|
|
1098
|
+
pendingToolRequests.delete(id);
|
|
1099
|
+
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
1100
|
+
}, timeoutMs);
|
|
1101
|
+
|
|
1102
|
+
pendingToolRequests.set(id, {
|
|
1103
|
+
socket: null,
|
|
1104
|
+
originalId: null,
|
|
1105
|
+
tool: toolName,
|
|
1106
|
+
onComplete: (r) => {
|
|
1107
|
+
clearTimeout(timeoutId);
|
|
1108
|
+
resolve(r);
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
writeMessage({ ...msg, id });
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
const result = await aistudioBuild.build({
|
|
1116
|
+
prompt: query,
|
|
1117
|
+
model: model || undefined,
|
|
1118
|
+
output,
|
|
1119
|
+
keepOpen,
|
|
1120
|
+
timeout: timeout || 600000,
|
|
1121
|
+
getCookies: () => callExtension("get_cookies", { type: "GET_GOOGLE_COOKIES" }, 45000),
|
|
1122
|
+
createTab: (url) => callExtension(
|
|
1123
|
+
"create_tab",
|
|
1124
|
+
{ type: "AISTUDIO_NEW_TAB", url },
|
|
1125
|
+
45000
|
|
1126
|
+
),
|
|
1127
|
+
closeTab: (tabIdToClose) => callExtension(
|
|
1128
|
+
"close_tab",
|
|
1129
|
+
{ type: "AISTUDIO_CLOSE_TAB", tabId: tabIdToClose },
|
|
1130
|
+
45000
|
|
1131
|
+
),
|
|
1132
|
+
cdpEvaluate: (tabId, expression) => callExtension(
|
|
1133
|
+
"cdp_evaluate",
|
|
1134
|
+
{ type: "AISTUDIO_EVALUATE", tabId, expression }
|
|
1135
|
+
),
|
|
1136
|
+
cdpCommand: (tabId, method, params) => callExtension(
|
|
1137
|
+
"cdp_command",
|
|
1138
|
+
{ type: "AISTUDIO_CDP_COMMAND", tabId, method, params }
|
|
1139
|
+
),
|
|
1140
|
+
searchDownloads: async (params) => {
|
|
1141
|
+
const result = await callExtension(
|
|
1142
|
+
"downloads_search",
|
|
1143
|
+
{ type: "DOWNLOADS_SEARCH", searchParams: params },
|
|
1144
|
+
10000
|
|
1145
|
+
);
|
|
1146
|
+
return result?.downloads || [];
|
|
1147
|
+
},
|
|
1148
|
+
log: (msg) => log(`[aistudio:build] ${msg}`)
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
return result;
|
|
1152
|
+
}).then((result) => {
|
|
1153
|
+
sendToolResponse(socket, originalId, { output: JSON.stringify(result) }, null);
|
|
1154
|
+
}).catch((err) => {
|
|
1155
|
+
sendToolResponse(socket, originalId, null, err.message);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
913
1160
|
|
|
914
1161
|
if (extensionMsg.type === "EXECUTE_KEY_REPEAT") {
|
|
915
1162
|
const { key, repeat, tabId: tid } = extensionMsg;
|
|
@@ -1204,15 +1451,15 @@ function processInput() {
|
|
|
1204
1451
|
} else if (autoScreenshot && tabId && !msg.error && !msg.base64) {
|
|
1205
1452
|
|
|
1206
1453
|
const screenshotId = ++requestCounter;
|
|
1207
|
-
const screenshotPath =
|
|
1454
|
+
const screenshotPath = path.join(SURF_TMP, `pi-auto-${Date.now()}.png`);
|
|
1208
1455
|
|
|
1209
|
-
const autoFiles = fs.readdirSync(
|
|
1456
|
+
const autoFiles = fs.readdirSync(SURF_TMP)
|
|
1210
1457
|
.filter(f => f.startsWith("pi-auto-") && f.endsWith(".png"))
|
|
1211
1458
|
.map(f => ({ name: f, time: parseInt(f.match(/pi-auto-(\d+)\.png/)?.[1] || "0", 10) }))
|
|
1212
1459
|
.sort((a, b) => b.time - a.time);
|
|
1213
1460
|
if (autoFiles.length >= 10) {
|
|
1214
1461
|
autoFiles.slice(9).forEach(f => {
|
|
1215
|
-
try { fs.unlinkSync(path.join(
|
|
1462
|
+
try { fs.unlinkSync(path.join(SURF_TMP, f.name)); } catch (e) {}
|
|
1216
1463
|
});
|
|
1217
1464
|
}
|
|
1218
1465
|
pendingToolRequests.set(screenshotId, {
|
|
@@ -1440,7 +1687,7 @@ const server = net.createServer((socket) => {
|
|
|
1440
1687
|
|
|
1441
1688
|
server.listen(SOCKET_PATH, () => {
|
|
1442
1689
|
log("Socket server listening on " + SOCKET_PATH);
|
|
1443
|
-
fs.chmodSync(SOCKET_PATH, 0o600);
|
|
1690
|
+
if (!IS_WIN) { try { fs.chmodSync(SOCKET_PATH, 0o600); } catch {} }
|
|
1444
1691
|
writeMessage({ type: "HOST_READY" });
|
|
1445
1692
|
log("Sent HOST_READY to extension");
|
|
1446
1693
|
});
|
|
@@ -1452,14 +1699,14 @@ server.on("error", (err) => {
|
|
|
1452
1699
|
process.on("SIGTERM", () => {
|
|
1453
1700
|
log("SIGTERM received");
|
|
1454
1701
|
server.close();
|
|
1455
|
-
try { fs.unlinkSync(SOCKET_PATH); } catch {}
|
|
1702
|
+
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1456
1703
|
process.exit(0);
|
|
1457
1704
|
});
|
|
1458
1705
|
|
|
1459
1706
|
process.on("SIGINT", () => {
|
|
1460
1707
|
log("SIGINT received");
|
|
1461
1708
|
server.close();
|
|
1462
|
-
try { fs.unlinkSync(SOCKET_PATH); } catch {}
|
|
1709
|
+
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1463
1710
|
process.exit(0);
|
|
1464
1711
|
});
|
|
1465
1712
|
|
package/native/mcp-server.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
4
4
|
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
5
|
const { z } = require("zod");
|
|
6
6
|
|
|
7
|
-
const SOCKET_PATH = "/tmp/surf.sock";
|
|
7
|
+
const SOCKET_PATH = process.platform === "win32" ? "//./pipe/surf" : "/tmp/surf.sock";
|
|
8
8
|
const REQUEST_TIMEOUT = 30000;
|
|
9
9
|
|
|
10
10
|
const TOOL_SCHEMAS = {
|
package/native/network-store.cjs
CHANGED
|
@@ -13,7 +13,9 @@ const crypto = require("crypto");
|
|
|
13
13
|
const readline = require("readline");
|
|
14
14
|
|
|
15
15
|
// Configuration
|
|
16
|
-
const DEFAULT_BASE = "
|
|
16
|
+
const DEFAULT_BASE = process.platform === "win32"
|
|
17
|
+
? require("path").join(require("os").tmpdir(), "surf")
|
|
18
|
+
: "/tmp/surf";
|
|
17
19
|
const DEFAULT_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
18
20
|
const DEFAULT_MAX_SIZE = 200 * 1024 * 1024; // 200MB
|
|
19
21
|
const AUTO_CLEANUP_INTERVAL = 60 * 60 * 1000; // 1 hour
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "surf-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chrome",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"native/",
|
|
31
31
|
"scripts/",
|
|
32
32
|
"dist/",
|
|
33
|
+
"skills/",
|
|
33
34
|
"README.md",
|
|
34
35
|
"LICENSE"
|
|
35
36
|
],
|
|
@@ -50,21 +51,21 @@
|
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"@google/generative-ai": "^0.24.1",
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
54
55
|
"buffer": "^6.0.3",
|
|
55
56
|
"crypto-browserify": "^3.12.1",
|
|
56
57
|
"events": "^3.3.0",
|
|
57
58
|
"stream-browserify": "^3.0.0",
|
|
58
|
-
"vite-plugin-node-polyfills": "^0.
|
|
59
|
-
"zod": "^4.3.
|
|
59
|
+
"vite-plugin-node-polyfills": "^0.25.0",
|
|
60
|
+
"zod": "^4.3.6"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
|
-
"@biomejs/biome": "^2.
|
|
63
|
-
"@types/chrome": "^0.
|
|
64
|
-
"@vitest/coverage-v8": "^4.0.
|
|
65
|
-
"@vitest/ui": "^4.0.
|
|
63
|
+
"@biomejs/biome": "^2.4.4",
|
|
64
|
+
"@types/chrome": "^0.1.37",
|
|
65
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
66
|
+
"@vitest/ui": "^4.0.18",
|
|
66
67
|
"typescript": "^5.7.2",
|
|
67
68
|
"vite": "^7.3.1",
|
|
68
|
-
"vitest": "^4.0.
|
|
69
|
+
"vitest": "^4.0.18"
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -39,6 +39,12 @@ const BROWSERS = {
|
|
|
39
39
|
linux: null,
|
|
40
40
|
win32: null,
|
|
41
41
|
},
|
|
42
|
+
helium: {
|
|
43
|
+
name: "Helium",
|
|
44
|
+
darwin: "Library/Application Support/net.imput.helium/NativeMessagingHosts",
|
|
45
|
+
linux: null,
|
|
46
|
+
win32: null,
|
|
47
|
+
},
|
|
42
48
|
};
|
|
43
49
|
|
|
44
50
|
const NODE_PATHS = {
|
|
@@ -58,6 +64,9 @@ const NODE_PATHS = {
|
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
function findNode() {
|
|
67
|
+
if (process.env.SURF_NODE_PATH && fs.existsSync(process.env.SURF_NODE_PATH)) {
|
|
68
|
+
return process.env.SURF_NODE_PATH;
|
|
69
|
+
}
|
|
61
70
|
const platform = process.platform;
|
|
62
71
|
const paths = NODE_PATHS[platform] || [];
|
|
63
72
|
for (const p of paths) {
|
|
@@ -95,6 +104,9 @@ function getWrapperDir() {
|
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
function getHostPath() {
|
|
107
|
+
if (process.env.SURF_HOST_PATH && fs.existsSync(process.env.SURF_HOST_PATH)) {
|
|
108
|
+
return process.env.SURF_HOST_PATH;
|
|
109
|
+
}
|
|
98
110
|
const npmRoot = findNpmGlobalRoot();
|
|
99
111
|
if (npmRoot) {
|
|
100
112
|
const globalPath = path.join(npmRoot, "surf-cli/native/host.cjs");
|
|
@@ -218,7 +230,7 @@ Arguments:
|
|
|
218
230
|
|
|
219
231
|
Options:
|
|
220
232
|
-b, --browser Browser(s) to install for (default: chrome)
|
|
221
|
-
Values: chrome, chromium, brave, edge, arc, all
|
|
233
|
+
Values: chrome, chromium, brave, edge, arc, helium, all
|
|
222
234
|
Multiple: --browser chrome,brave
|
|
223
235
|
|
|
224
236
|
Examples:
|
|
@@ -233,7 +245,7 @@ function main() {
|
|
|
233
245
|
|
|
234
246
|
if (!extensionId) {
|
|
235
247
|
console.error("Error: Extension ID required");
|
|
236
|
-
console.error("Usage: install-native-host.cjs <extension-id> [--browser chrome|brave|edge|all]");
|
|
248
|
+
console.error("Usage: install-native-host.cjs <extension-id> [--browser chrome|chromium|brave|edge|arc|helium|all]");
|
|
237
249
|
console.error("\nFind your extension ID at chrome://extensions (enable Developer Mode)");
|
|
238
250
|
process.exit(1);
|
|
239
251
|
}
|
|
@@ -37,6 +37,12 @@ const BROWSERS = {
|
|
|
37
37
|
linux: null,
|
|
38
38
|
win32: null,
|
|
39
39
|
},
|
|
40
|
+
helium: {
|
|
41
|
+
name: "Helium",
|
|
42
|
+
darwin: "Library/Application Support/net.imput.helium/NativeMessagingHosts",
|
|
43
|
+
linux: null,
|
|
44
|
+
win32: null,
|
|
45
|
+
},
|
|
40
46
|
};
|
|
41
47
|
|
|
42
48
|
function getWrapperDir() {
|
|
@@ -137,7 +143,7 @@ Usage: uninstall-native-host.cjs [options]
|
|
|
137
143
|
|
|
138
144
|
Options:
|
|
139
145
|
-b, --browser Browser(s) to uninstall from (default: chrome)
|
|
140
|
-
Values: chrome, chromium, brave, edge, arc, all
|
|
146
|
+
Values: chrome, chromium, brave, edge, arc, helium, all
|
|
141
147
|
-a, --all Uninstall from all browsers and remove wrapper
|
|
142
148
|
|
|
143
149
|
Examples:
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Surf Skills
|
|
2
|
+
|
|
3
|
+
This directory contains skill files for AI coding agents.
|
|
4
|
+
|
|
5
|
+
## Pi Agent
|
|
6
|
+
|
|
7
|
+
To use the surf skill with [Pi coding agent](https://github.com/badlogic/pi-mono):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Option 1: Symlink (auto-updates)
|
|
11
|
+
ln -s "$(pwd)/skills/surf" ~/.agents/skills/surf
|
|
12
|
+
|
|
13
|
+
# Option 2: Copy
|
|
14
|
+
cp -r skills/surf ~/.agents/skills/
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The skill will be available when pi detects browser automation tasks.
|
|
18
|
+
|
|
19
|
+
## Other Agents
|
|
20
|
+
|
|
21
|
+
The `SKILL.md` file is a comprehensive reference that can be adapted for other AI coding agents or used as documentation for LLM prompts.
|