surf-cli 2.5.2 → 2.6.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 +43 -2
- package/dist/manifest.json +1 -1
- package/dist/service-worker/index.js +5 -5
- 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 +83 -14
- package/native/do-executor.cjs +1 -1
- package/native/host-helpers.cjs +29 -0
- package/native/host.cjs +210 -14
- package/native/mcp-server.cjs +1 -1
- package/native/network-store.cjs +3 -1
- package/package.json +3 -2
- 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 +545 -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();
|
|
@@ -910,6 +914,198 @@ function handleToolRequest(msg, socket) {
|
|
|
910
914
|
|
|
911
915
|
return;
|
|
912
916
|
}
|
|
917
|
+
|
|
918
|
+
if (extensionMsg.type === "AISTUDIO_QUERY") {
|
|
919
|
+
const { query, model, withPage, timeout } = extensionMsg;
|
|
920
|
+
|
|
921
|
+
queueAiRequest(async () => {
|
|
922
|
+
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
923
|
+
|
|
924
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => new Promise((resolve, reject) => {
|
|
925
|
+
const id = ++requestCounter;
|
|
926
|
+
|
|
927
|
+
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
928
|
+
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/prompts/new_chat")}`);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const timeoutId = setTimeout(() => {
|
|
932
|
+
pendingToolRequests.delete(id);
|
|
933
|
+
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
934
|
+
}, timeoutMs);
|
|
935
|
+
|
|
936
|
+
pendingToolRequests.set(id, {
|
|
937
|
+
socket: null,
|
|
938
|
+
originalId: null,
|
|
939
|
+
tool: toolName,
|
|
940
|
+
onComplete: (r) => {
|
|
941
|
+
clearTimeout(timeoutId);
|
|
942
|
+
resolve(r);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
writeMessage({ ...msg, id });
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// 1. Get page context if requested
|
|
950
|
+
let pageContext = null;
|
|
951
|
+
if (withPage) {
|
|
952
|
+
const pageResult = await callExtension(
|
|
953
|
+
"get_page_text",
|
|
954
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
955
|
+
45000
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
if (pageResult && !pageResult.error) {
|
|
959
|
+
pageContext = {
|
|
960
|
+
url: pageResult.url,
|
|
961
|
+
text: pageResult.text || pageResult.pageContent || ""
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// 2. Build full prompt
|
|
967
|
+
let fullPrompt = query || "";
|
|
968
|
+
if (pageContext) {
|
|
969
|
+
const MAX_PAGE_CONTEXT_CHARS = 20000;
|
|
970
|
+
const pageText = String(pageContext.text || "");
|
|
971
|
+
const truncated = pageText.length > MAX_PAGE_CONTEXT_CHARS
|
|
972
|
+
? pageText.slice(0, MAX_PAGE_CONTEXT_CHARS) + "\n\n[...truncated...]"
|
|
973
|
+
: pageText;
|
|
974
|
+
|
|
975
|
+
fullPrompt = `Page: ${pageContext.url}\n\n${truncated}\n\n---\n\n${fullPrompt}`;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// 3. Call AI Studio client
|
|
979
|
+
const result = await aistudioClient.query({
|
|
980
|
+
prompt: fullPrompt,
|
|
981
|
+
model: model || undefined,
|
|
982
|
+
timeout: timeout || 300000,
|
|
983
|
+
getCookies: () => callExtension("get_cookies", { type: "GET_GOOGLE_COOKIES" }, 45000),
|
|
984
|
+
createTab: (url) => callExtension(
|
|
985
|
+
"create_tab",
|
|
986
|
+
{ type: "AISTUDIO_NEW_TAB", url },
|
|
987
|
+
45000
|
|
988
|
+
),
|
|
989
|
+
closeTab: (tabIdToClose) => callExtension(
|
|
990
|
+
"close_tab",
|
|
991
|
+
{ type: "AISTUDIO_CLOSE_TAB", tabId: tabIdToClose },
|
|
992
|
+
45000
|
|
993
|
+
),
|
|
994
|
+
cdpEvaluate: (tabId, expression) => callExtension(
|
|
995
|
+
"cdp_evaluate",
|
|
996
|
+
{ type: "AISTUDIO_EVALUATE", tabId, expression }
|
|
997
|
+
),
|
|
998
|
+
cdpCommand: (tabId, method, params) => callExtension(
|
|
999
|
+
"cdp_command",
|
|
1000
|
+
{ type: "AISTUDIO_CDP_COMMAND", tabId, method, params }
|
|
1001
|
+
),
|
|
1002
|
+
readNetworkEntries: (tabIdToRead) => callExtension(
|
|
1003
|
+
"read_network_entries",
|
|
1004
|
+
{
|
|
1005
|
+
type: "READ_NETWORK_REQUESTS",
|
|
1006
|
+
tabId: tabIdToRead,
|
|
1007
|
+
full: true,
|
|
1008
|
+
limit: 100,
|
|
1009
|
+
urlPattern: "MakerSuiteService/GenerateContent"
|
|
1010
|
+
},
|
|
1011
|
+
45000
|
|
1012
|
+
),
|
|
1013
|
+
log: (msg) => log(`[aistudio] ${msg}`)
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
return result;
|
|
1017
|
+
}).then((result) => {
|
|
1018
|
+
const payload = {
|
|
1019
|
+
response: result.response,
|
|
1020
|
+
model: result.model,
|
|
1021
|
+
thinkingTime: result.thinkingTime,
|
|
1022
|
+
tookMs: result.tookMs
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
sendToolResponse(socket, originalId, { output: JSON.stringify(payload) }, null);
|
|
1026
|
+
}).catch((err) => {
|
|
1027
|
+
sendToolResponse(socket, originalId, null, err.message);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (extensionMsg.type === "AISTUDIO_BUILD") {
|
|
1034
|
+
const { query, model, output, keepOpen, timeout } = extensionMsg;
|
|
1035
|
+
|
|
1036
|
+
queueAiRequest(async () => {
|
|
1037
|
+
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
1038
|
+
|
|
1039
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => new Promise((resolve, reject) => {
|
|
1040
|
+
const id = ++requestCounter;
|
|
1041
|
+
|
|
1042
|
+
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
1043
|
+
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/apps")}`);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const timeoutId = setTimeout(() => {
|
|
1047
|
+
pendingToolRequests.delete(id);
|
|
1048
|
+
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
1049
|
+
}, timeoutMs);
|
|
1050
|
+
|
|
1051
|
+
pendingToolRequests.set(id, {
|
|
1052
|
+
socket: null,
|
|
1053
|
+
originalId: null,
|
|
1054
|
+
tool: toolName,
|
|
1055
|
+
onComplete: (r) => {
|
|
1056
|
+
clearTimeout(timeoutId);
|
|
1057
|
+
resolve(r);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
writeMessage({ ...msg, id });
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
const result = await aistudioBuild.build({
|
|
1065
|
+
prompt: query,
|
|
1066
|
+
model: model || undefined,
|
|
1067
|
+
output,
|
|
1068
|
+
keepOpen,
|
|
1069
|
+
timeout: timeout || 600000,
|
|
1070
|
+
getCookies: () => callExtension("get_cookies", { type: "GET_GOOGLE_COOKIES" }, 45000),
|
|
1071
|
+
createTab: (url) => callExtension(
|
|
1072
|
+
"create_tab",
|
|
1073
|
+
{ type: "AISTUDIO_NEW_TAB", url },
|
|
1074
|
+
45000
|
|
1075
|
+
),
|
|
1076
|
+
closeTab: (tabIdToClose) => callExtension(
|
|
1077
|
+
"close_tab",
|
|
1078
|
+
{ type: "AISTUDIO_CLOSE_TAB", tabId: tabIdToClose },
|
|
1079
|
+
45000
|
|
1080
|
+
),
|
|
1081
|
+
cdpEvaluate: (tabId, expression) => callExtension(
|
|
1082
|
+
"cdp_evaluate",
|
|
1083
|
+
{ type: "AISTUDIO_EVALUATE", tabId, expression }
|
|
1084
|
+
),
|
|
1085
|
+
cdpCommand: (tabId, method, params) => callExtension(
|
|
1086
|
+
"cdp_command",
|
|
1087
|
+
{ type: "AISTUDIO_CDP_COMMAND", tabId, method, params }
|
|
1088
|
+
),
|
|
1089
|
+
searchDownloads: async (params) => {
|
|
1090
|
+
const result = await callExtension(
|
|
1091
|
+
"downloads_search",
|
|
1092
|
+
{ type: "DOWNLOADS_SEARCH", searchParams: params },
|
|
1093
|
+
10000
|
|
1094
|
+
);
|
|
1095
|
+
return result?.downloads || [];
|
|
1096
|
+
},
|
|
1097
|
+
log: (msg) => log(`[aistudio:build] ${msg}`)
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
return result;
|
|
1101
|
+
}).then((result) => {
|
|
1102
|
+
sendToolResponse(socket, originalId, { output: JSON.stringify(result) }, null);
|
|
1103
|
+
}).catch((err) => {
|
|
1104
|
+
sendToolResponse(socket, originalId, null, err.message);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
913
1109
|
|
|
914
1110
|
if (extensionMsg.type === "EXECUTE_KEY_REPEAT") {
|
|
915
1111
|
const { key, repeat, tabId: tid } = extensionMsg;
|
|
@@ -1204,15 +1400,15 @@ function processInput() {
|
|
|
1204
1400
|
} else if (autoScreenshot && tabId && !msg.error && !msg.base64) {
|
|
1205
1401
|
|
|
1206
1402
|
const screenshotId = ++requestCounter;
|
|
1207
|
-
const screenshotPath =
|
|
1403
|
+
const screenshotPath = path.join(SURF_TMP, `pi-auto-${Date.now()}.png`);
|
|
1208
1404
|
|
|
1209
|
-
const autoFiles = fs.readdirSync(
|
|
1405
|
+
const autoFiles = fs.readdirSync(SURF_TMP)
|
|
1210
1406
|
.filter(f => f.startsWith("pi-auto-") && f.endsWith(".png"))
|
|
1211
1407
|
.map(f => ({ name: f, time: parseInt(f.match(/pi-auto-(\d+)\.png/)?.[1] || "0", 10) }))
|
|
1212
1408
|
.sort((a, b) => b.time - a.time);
|
|
1213
1409
|
if (autoFiles.length >= 10) {
|
|
1214
1410
|
autoFiles.slice(9).forEach(f => {
|
|
1215
|
-
try { fs.unlinkSync(path.join(
|
|
1411
|
+
try { fs.unlinkSync(path.join(SURF_TMP, f.name)); } catch (e) {}
|
|
1216
1412
|
});
|
|
1217
1413
|
}
|
|
1218
1414
|
pendingToolRequests.set(screenshotId, {
|
|
@@ -1440,7 +1636,7 @@ const server = net.createServer((socket) => {
|
|
|
1440
1636
|
|
|
1441
1637
|
server.listen(SOCKET_PATH, () => {
|
|
1442
1638
|
log("Socket server listening on " + SOCKET_PATH);
|
|
1443
|
-
fs.chmodSync(SOCKET_PATH, 0o600);
|
|
1639
|
+
if (!IS_WIN) { try { fs.chmodSync(SOCKET_PATH, 0o600); } catch {} }
|
|
1444
1640
|
writeMessage({ type: "HOST_READY" });
|
|
1445
1641
|
log("Sent HOST_READY to extension");
|
|
1446
1642
|
});
|
|
@@ -1452,14 +1648,14 @@ server.on("error", (err) => {
|
|
|
1452
1648
|
process.on("SIGTERM", () => {
|
|
1453
1649
|
log("SIGTERM received");
|
|
1454
1650
|
server.close();
|
|
1455
|
-
try { fs.unlinkSync(SOCKET_PATH); } catch {}
|
|
1651
|
+
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1456
1652
|
process.exit(0);
|
|
1457
1653
|
});
|
|
1458
1654
|
|
|
1459
1655
|
process.on("SIGINT", () => {
|
|
1460
1656
|
log("SIGINT received");
|
|
1461
1657
|
server.close();
|
|
1462
|
-
try { fs.unlinkSync(SOCKET_PATH); } catch {}
|
|
1658
|
+
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1463
1659
|
process.exit(0);
|
|
1464
1660
|
});
|
|
1465
1661
|
|
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.6.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
|
],
|
|
@@ -55,7 +56,7 @@
|
|
|
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
|
+
"vite-plugin-node-polyfills": "^0.25.0",
|
|
59
60
|
"zod": "^4.3.5"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
@@ -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" ~/.pi/agent/skills/surf
|
|
12
|
+
|
|
13
|
+
# Option 2: Copy
|
|
14
|
+
cp -r skills/surf ~/.pi/agent/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.
|