@opentiny/webmcp-cli 0.1.1-alpha.1 → 0.1.1
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 +3 -0
- package/dist/bin.cjs +3678 -412
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3682 -418
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +38 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -16
- package/dist/index.js.map +1 -1
- package/dist/inject-bundle.js +3002 -3258
- package/dist/webmcp-tools/juejin.cn.js +151 -1802
- package/package.json +15 -5
- package/scripts/ensure-pty-helper.mjs +24 -0
- package/dist/bin.d.cts +0 -1
- package/dist/bin.d.ts +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,206 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
26
|
-
// src/bin.ts
|
|
27
|
-
var import_fs3 = __toESM(require("fs"), 1);
|
|
28
|
-
var import_path3 = __toESM(require("path"), 1);
|
|
29
|
-
var import_os2 = __toESM(require("os"), 1);
|
|
30
|
-
var import_commander = require("commander");
|
|
31
|
-
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
32
|
-
var import_fs4 = require("fs");
|
|
33
|
-
var import_path4 = require("path");
|
|
34
|
-
|
|
35
|
-
// src/expand-file-refs.ts
|
|
36
|
-
var import_fs = require("fs");
|
|
37
|
-
var import_path = require("path");
|
|
38
|
-
var import_url = require("url");
|
|
39
|
-
var JSON_STRING_LITERAL = /"(?:[^"\\]|\\.)*"/g;
|
|
40
|
-
var UNQUOTED_FILE_REF = /(?<![\\"])@(base64)?file:([^\s"',}\]]+)/g;
|
|
41
|
-
var FILE_REF_PREFIX = /^@(base64)?file:(.+)$/s;
|
|
42
|
-
function resolveFilePath(filePath, baseDir = process.cwd()) {
|
|
43
|
-
let normalizedPath = filePath.trim();
|
|
44
|
-
if (normalizedPath.startsWith('"') && normalizedPath.endsWith('"') || normalizedPath.startsWith("'") && normalizedPath.endsWith("'")) {
|
|
45
|
-
normalizedPath = normalizedPath.slice(1, -1).trim();
|
|
46
|
-
}
|
|
47
|
-
if (normalizedPath.startsWith("file://")) {
|
|
48
|
-
normalizedPath = (0, import_url.fileURLToPath)(normalizedPath);
|
|
49
|
-
}
|
|
50
|
-
if ((0, import_path.isAbsolute)(normalizedPath)) {
|
|
51
|
-
return (0, import_path.normalize)(normalizedPath);
|
|
52
|
-
}
|
|
53
|
-
return (0, import_path.resolve)(baseDir, normalizedPath);
|
|
54
|
-
}
|
|
55
|
-
function decodeFilePath(rawPath) {
|
|
56
|
-
return rawPath.replace(/\\\\/g, "\\");
|
|
57
|
-
}
|
|
58
|
-
function readFileRefContent(filePath, baseDir, base64) {
|
|
59
|
-
const absPath = resolveFilePath(filePath, baseDir);
|
|
60
|
-
let content;
|
|
61
|
-
try {
|
|
62
|
-
content = (0, import_fs.readFileSync)(absPath, "utf-8");
|
|
63
|
-
} catch (e) {
|
|
64
|
-
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}" (${absPath}): ${e.message}`);
|
|
65
|
-
}
|
|
66
|
-
return base64 ? Buffer.from(content, "utf-8").toString("base64") : content;
|
|
67
|
-
}
|
|
68
|
-
function expandFileRefValue(rawInner, baseDir) {
|
|
69
|
-
const match = rawInner.match(FILE_REF_PREFIX);
|
|
70
|
-
if (!match) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const [, base64Flag, rawPath] = match;
|
|
74
|
-
const filePath = decodeFilePath(rawPath);
|
|
75
|
-
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
76
|
-
return JSON.stringify(value);
|
|
77
|
-
}
|
|
78
|
-
function expandQuotedFileRefs(argsJson, baseDir) {
|
|
79
|
-
return argsJson.replace(JSON_STRING_LITERAL, (jsonStringLiteral) => {
|
|
80
|
-
const rawInner = jsonStringLiteral.slice(1, -1);
|
|
81
|
-
const replacement = expandFileRefValue(rawInner, baseDir);
|
|
82
|
-
return replacement ?? jsonStringLiteral;
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
function expandUnquotedFileRefs(argsJson, baseDir) {
|
|
86
|
-
return argsJson.replace(UNQUOTED_FILE_REF, (match, base64Flag, filePath) => {
|
|
87
|
-
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
88
|
-
return JSON.stringify(value);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
function expandFileRefs(argsJson, baseDir = process.cwd()) {
|
|
92
|
-
const withQuotedExpanded = expandQuotedFileRefs(argsJson, baseDir);
|
|
93
|
-
return expandUnquotedFileRefs(withQuotedExpanded, baseDir);
|
|
94
|
-
}
|
|
95
|
-
function getFileBaseDir(fileOption) {
|
|
96
|
-
if (!fileOption) {
|
|
97
|
-
return process.cwd();
|
|
98
|
-
}
|
|
99
|
-
return (0, import_path.dirname)((0, import_path.resolve)(process.cwd(), fileOption));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// src/parse-run-args.ts
|
|
103
|
-
var import_json5 = __toESM(require("json5"), 1);
|
|
104
|
-
function cleanJsonString(str) {
|
|
105
|
-
let cleaned = str.trim();
|
|
106
|
-
if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
|
|
107
|
-
cleaned = cleaned.slice(1, -1).trim();
|
|
108
|
-
}
|
|
109
|
-
return cleaned;
|
|
110
|
-
}
|
|
111
|
-
function looksLikeObjectBody(raw) {
|
|
112
|
-
return /^("?[\w-]+"?\s*:)/.test(raw);
|
|
113
|
-
}
|
|
114
|
-
function joinShellArgs(args) {
|
|
115
|
-
const cleaned = args.map(cleanJsonString);
|
|
116
|
-
const first = cleaned[0] ?? "";
|
|
117
|
-
if (first.startsWith("{")) {
|
|
118
|
-
return cleaned.join(" ");
|
|
119
|
-
}
|
|
120
|
-
if (looksLikeObjectBody(first)) {
|
|
121
|
-
let joined = cleaned.join(" ");
|
|
122
|
-
if (!joined.startsWith("{")) {
|
|
123
|
-
joined = `{${joined}}`;
|
|
124
|
-
}
|
|
125
|
-
if (!joined.endsWith("}")) {
|
|
126
|
-
joined = `${joined}}`;
|
|
127
|
-
}
|
|
128
|
-
return joined;
|
|
129
|
-
}
|
|
130
|
-
return cleaned.join(" ");
|
|
131
|
-
}
|
|
132
|
-
function insertMissingCommas(raw) {
|
|
133
|
-
return raw.replace(/"(\s+)("?[\w-]+"?\s*:)/g, '",$2');
|
|
134
|
-
}
|
|
135
|
-
function quoteUnquotedStringValues(raw) {
|
|
136
|
-
return raw.replace(
|
|
137
|
-
/([{,]\s*(?:"([^"]+)"|([\w-]+))\s*:\s*)(?!"@(?:base64)?file:|"|true|false|null|-?\d|\{|\[)([^",}\]]+?)(?=\s*[,}])/g,
|
|
138
|
-
(_match, prefix, _quotedKey, _unquotedKey, value) => {
|
|
139
|
-
const trimmed = value.trim();
|
|
140
|
-
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
|
|
141
|
-
return prefix + trimmed;
|
|
142
|
-
}
|
|
143
|
-
return prefix + JSON.stringify(trimmed);
|
|
144
|
-
}
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
function normalizeShellDamagedJson(raw) {
|
|
148
|
-
let normalized = raw.trim();
|
|
149
|
-
normalized = insertMissingCommas(normalized);
|
|
150
|
-
normalized = quoteUnquotedStringValues(normalized);
|
|
151
|
-
return normalized;
|
|
152
|
-
}
|
|
153
|
-
function buildArgsFromKeyValuePairs(args) {
|
|
154
|
-
const obj = {};
|
|
155
|
-
for (const pair of args) {
|
|
156
|
-
const index = pair.indexOf("=");
|
|
157
|
-
if (index === -1) {
|
|
158
|
-
throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u8BF7\u4F7F\u7528 key=value\uFF0C\u6216 JSON / -f \u6587\u4EF6\u3002`);
|
|
159
|
-
}
|
|
160
|
-
const key = pair.substring(0, index).trim();
|
|
161
|
-
let val = cleanJsonString(pair.substring(index + 1).trim());
|
|
162
|
-
try {
|
|
163
|
-
obj[key] = JSON.parse(val);
|
|
164
|
-
} catch {
|
|
165
|
-
obj[key] = val;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return JSON.stringify(obj);
|
|
169
|
-
}
|
|
170
|
-
function parseArgsJson(raw) {
|
|
171
|
-
const trimmed = cleanJsonString(raw);
|
|
172
|
-
try {
|
|
173
|
-
return JSON.stringify(import_json5.default.parse(trimmed));
|
|
174
|
-
} catch (firstError) {
|
|
175
|
-
const repaired = normalizeShellDamagedJson(trimmed);
|
|
176
|
-
try {
|
|
177
|
-
return JSON.stringify(import_json5.default.parse(repaired));
|
|
178
|
-
} catch {
|
|
179
|
-
const hint = `\u63D0\u793A\uFF1Abash \u4E2D JSON \u5FC5\u987B\u7528\u5355\u5F15\u53F7\u5305\u88F9\uFF0C\u4F8B\u5982 '{"title":"\u6807\u9898","content":"@base64file:./article.md"}'\uFF1B\u6216\u4F7F\u7528 key=value\uFF1Atitle=\u6807\u9898 content=@base64file:./article.md\uFF1B\u6216\u4F7F\u7528 -f args.json\u3002`;
|
|
180
|
-
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
181
|
-
throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${message}
|
|
182
|
-
${hint}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function prepareRunArgsJson(args, fileContent, baseDir) {
|
|
187
|
-
if (fileContent !== void 0) {
|
|
188
|
-
const trimmed = fileContent.trim();
|
|
189
|
-
if (!trimmed) {
|
|
190
|
-
throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
|
|
191
|
-
}
|
|
192
|
-
const expanded2 = expandFileRefs(trimmed, baseDir);
|
|
193
|
-
return parseArgsJson(expanded2);
|
|
194
|
-
}
|
|
195
|
-
if (args.length === 0) {
|
|
196
|
-
return "{}";
|
|
197
|
-
}
|
|
198
|
-
const joined = joinShellArgs(args);
|
|
199
|
-
let raw = "";
|
|
200
|
-
if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
|
|
201
|
-
raw = joined;
|
|
202
|
-
} else if (args.every((arg) => arg.includes("="))) {
|
|
203
|
-
raw = buildArgsFromKeyValuePairs(args);
|
|
204
|
-
} else {
|
|
205
|
-
raw = joined;
|
|
206
|
-
}
|
|
207
|
-
if (!raw.trim()) {
|
|
208
|
-
throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
|
|
209
|
-
}
|
|
210
|
-
const expanded = expandFileRefs(raw, baseDir);
|
|
211
|
-
return parseArgsJson(expanded);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
33
|
// src/browser.ts
|
|
215
|
-
var import_puppeteer_core = __toESM(require("puppeteer-core"), 1);
|
|
216
|
-
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
217
|
-
var import_fs2 = __toESM(require("fs"), 1);
|
|
218
|
-
var import_os = __toESM(require("os"), 1);
|
|
219
|
-
var import_path2 = __toESM(require("path"), 1);
|
|
220
|
-
var import_http = __toESM(require("http"), 1);
|
|
221
|
-
var import_child_process = require("child_process");
|
|
222
|
-
var import_url2 = require("url");
|
|
223
|
-
var import_meta = {};
|
|
224
|
-
var __filename = (0, import_url2.fileURLToPath)(import_meta.url);
|
|
225
|
-
var __dirname = import_path2.default.dirname(__filename);
|
|
226
34
|
function getCdpPort() {
|
|
227
35
|
const raw = process.env.WEBMCP_CDP_PORT;
|
|
228
36
|
if (raw) {
|
|
@@ -238,6 +46,38 @@ function shouldLaunchHeadless() {
|
|
|
238
46
|
if (process.env.WEBMCP_HEADLESS === "0") return false;
|
|
239
47
|
return process.env.WEBMCP_HEADLESS === "1" || process.env.CI === "true" || process.env.CI === "1";
|
|
240
48
|
}
|
|
49
|
+
function shouldIgnoreCertificateErrors() {
|
|
50
|
+
return process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS === "1";
|
|
51
|
+
}
|
|
52
|
+
function buildChromeLaunchArgs(input) {
|
|
53
|
+
const launchArgs = [
|
|
54
|
+
`--remote-debugging-port=${input.cdpPort}`,
|
|
55
|
+
`--user-data-dir=${input.userDataDir}`,
|
|
56
|
+
"--no-first-run",
|
|
57
|
+
"--no-default-browser-check"
|
|
58
|
+
];
|
|
59
|
+
if (input.headless) {
|
|
60
|
+
launchArgs.push(
|
|
61
|
+
"--headless=new",
|
|
62
|
+
"--no-sandbox",
|
|
63
|
+
"--disable-setuid-sandbox",
|
|
64
|
+
"--disable-gpu",
|
|
65
|
+
"--disable-dev-shm-usage"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const ignoreCertificateErrors = input.ignoreCertificateErrors ?? shouldIgnoreCertificateErrors();
|
|
69
|
+
if (ignoreCertificateErrors) {
|
|
70
|
+
launchArgs.push("--ignore-certificate-errors");
|
|
71
|
+
}
|
|
72
|
+
return launchArgs;
|
|
73
|
+
}
|
|
74
|
+
function buildPuppeteerConnectOptions(browserBaseUrl) {
|
|
75
|
+
return {
|
|
76
|
+
browserURL: browserBaseUrl,
|
|
77
|
+
defaultViewport: null,
|
|
78
|
+
...shouldIgnoreCertificateErrors() ? { ignoreHTTPSErrors: true } : {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
241
81
|
function readActivePortFromProfile(userDataDir) {
|
|
242
82
|
try {
|
|
243
83
|
const content = import_fs2.default.readFileSync(import_path2.default.join(userDataDir, "DevToolsActivePort"), "utf-8");
|
|
@@ -332,7 +172,7 @@ async function findAvailableCdpUrl(addresses, retries = 2) {
|
|
|
332
172
|
async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
|
|
333
173
|
try {
|
|
334
174
|
return await promiseWithTimeout(
|
|
335
|
-
import_puppeteer_core.default.connect(
|
|
175
|
+
import_puppeteer_core.default.connect(buildPuppeteerConnectOptions(browserBaseUrl)),
|
|
336
176
|
1e4,
|
|
337
177
|
`puppeteer.connect to ${browserBaseUrl} timed out`
|
|
338
178
|
);
|
|
@@ -407,21 +247,11 @@ async function startBrowserInBackground() {
|
|
|
407
247
|
import_fs2.default.unlinkSync(import_path2.default.join(userDataDir, "DevToolsActivePort"));
|
|
408
248
|
} catch {
|
|
409
249
|
}
|
|
410
|
-
const launchArgs =
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
];
|
|
416
|
-
if (shouldLaunchHeadless()) {
|
|
417
|
-
launchArgs.push(
|
|
418
|
-
"--headless=new",
|
|
419
|
-
"--no-sandbox",
|
|
420
|
-
"--disable-setuid-sandbox",
|
|
421
|
-
"--disable-gpu",
|
|
422
|
-
"--disable-dev-shm-usage"
|
|
423
|
-
);
|
|
424
|
-
}
|
|
250
|
+
const launchArgs = buildChromeLaunchArgs({
|
|
251
|
+
cdpPort,
|
|
252
|
+
userDataDir,
|
|
253
|
+
headless: shouldLaunchHeadless()
|
|
254
|
+
});
|
|
425
255
|
const child = (0, import_child_process.spawn)(
|
|
426
256
|
browserInfo.path,
|
|
427
257
|
launchArgs,
|
|
@@ -546,156 +376,3459 @@ async function activateTabById(browser, tabid) {
|
|
|
546
376
|
});
|
|
547
377
|
}
|
|
548
378
|
}
|
|
549
|
-
async function getTargetPage(browser, tabid) {
|
|
550
|
-
const pageTargets = getPageTargets(browser);
|
|
551
|
-
if (pageTargets.length === 0) {
|
|
552
|
-
const newPage = await browser.newPage();
|
|
553
|
-
await injectWebMCPPolyfillAndTools(newPage);
|
|
554
|
-
return newPage;
|
|
379
|
+
async function getTargetPage(browser, tabid) {
|
|
380
|
+
const pageTargets = getPageTargets(browser);
|
|
381
|
+
if (pageTargets.length === 0) {
|
|
382
|
+
const newPage = await browser.newPage();
|
|
383
|
+
await injectWebMCPPolyfillAndTools(newPage);
|
|
384
|
+
return newPage;
|
|
385
|
+
}
|
|
386
|
+
let targetPage = null;
|
|
387
|
+
if (tabid !== void 0) {
|
|
388
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
389
|
+
if (!target) {
|
|
390
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
391
|
+
}
|
|
392
|
+
targetPage = await target.page();
|
|
393
|
+
} else {
|
|
394
|
+
const lastTabId = getLastActiveTabId();
|
|
395
|
+
if (lastTabId) {
|
|
396
|
+
const lastTarget = findPageTargetByTabId(browser, lastTabId);
|
|
397
|
+
if (lastTarget) {
|
|
398
|
+
targetPage = await lastTarget.page();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (!targetPage) {
|
|
402
|
+
try {
|
|
403
|
+
const workspaceDir = getWorkspaceDir();
|
|
404
|
+
const cdpPort = getCdpPort();
|
|
405
|
+
const recordedPort = readActivePortFromProfile(workspaceDir);
|
|
406
|
+
const ports = recordedPort && recordedPort !== cdpPort ? [recordedPort, cdpPort] : [cdpPort];
|
|
407
|
+
const urls = ports.flatMap((port) => candidateCdpBaseUrls(port).map((u) => `${u}/json/list`));
|
|
408
|
+
let activeTargetId = null;
|
|
409
|
+
for (const url of urls) {
|
|
410
|
+
try {
|
|
411
|
+
const body = await httpGet(url, 1e3);
|
|
412
|
+
const targetsData = JSON.parse(body);
|
|
413
|
+
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
|
414
|
+
if (active) {
|
|
415
|
+
activeTargetId = active.id;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (activeTargetId) {
|
|
422
|
+
for (const target of pageTargets) {
|
|
423
|
+
if (getTargetIdFromTarget(target) === activeTargetId) {
|
|
424
|
+
targetPage = await target.page();
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (!targetPage) {
|
|
433
|
+
const lastTarget = pageTargets[pageTargets.length - 1];
|
|
434
|
+
targetPage = await lastTarget.page();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!targetPage) {
|
|
438
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u76EE\u6807\u9875\u9762");
|
|
439
|
+
}
|
|
440
|
+
await injectWebMCPPolyfillAndTools(targetPage);
|
|
441
|
+
return targetPage;
|
|
442
|
+
}
|
|
443
|
+
async function injectIntoPage(page) {
|
|
444
|
+
await injectWebMCPPolyfillAndTools(page);
|
|
445
|
+
}
|
|
446
|
+
async function injectWebMCPPolyfillAndTools(page) {
|
|
447
|
+
const polyfillReady = await page.evaluate(() => {
|
|
448
|
+
return !!window.__webmcpcli_init;
|
|
449
|
+
}).catch(() => false);
|
|
450
|
+
if (!polyfillReady) {
|
|
451
|
+
console.log(import_picocolors.default.cyan("\u5F53\u524D\u9875\u9762\u5C1A\u672A\u6CE8\u5165 WebMCP \u73AF\u5883\uFF0C\u6B63\u5728\u6267\u884C\u81EA\u52A8\u6CE8\u5165..."));
|
|
452
|
+
const injectScriptPath = import_path2.default.resolve(__dirname, "inject-bundle.js");
|
|
453
|
+
if (!import_fs2.default.existsSync(injectScriptPath)) {
|
|
454
|
+
throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`);
|
|
455
|
+
}
|
|
456
|
+
const scriptContent = import_fs2.default.readFileSync(injectScriptPath, "utf-8");
|
|
457
|
+
try {
|
|
458
|
+
await page.evaluate(scriptContent);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
461
|
+
throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
|
|
462
|
+
}
|
|
463
|
+
await page.waitForFunction(() => {
|
|
464
|
+
const mcp = navigator.modelContext;
|
|
465
|
+
return mcp && typeof mcp.registerTool === "function";
|
|
466
|
+
}, { timeout: 1e4 }).catch(() => {
|
|
467
|
+
console.warn("\u7B49\u5F85 WebMCP registerTool \u63A5\u53E3\u5C31\u7EEA\u8D85\u65F6\uFF0C\u6B63\u5728\u7EE7\u7EED\u6CE8\u5165...");
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
await injectDomainTools(page);
|
|
471
|
+
}
|
|
472
|
+
function getToolsBundleName(hostname) {
|
|
473
|
+
const normalized = hostname.split(":")[0].toLowerCase();
|
|
474
|
+
const toolsDir = import_path2.default.resolve(__dirname, "webmcp-tools");
|
|
475
|
+
if (!import_fs2.default.existsSync(toolsDir)) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
let files = [];
|
|
479
|
+
try {
|
|
480
|
+
files = import_fs2.default.readdirSync(toolsDir);
|
|
481
|
+
} catch {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const supportedDomains = files.filter((f) => f.endsWith(".js")).map((f) => f.slice(0, -3));
|
|
485
|
+
if (supportedDomains.includes(normalized)) {
|
|
486
|
+
return `webmcp-tools/${normalized}.js`;
|
|
487
|
+
}
|
|
488
|
+
for (const domain of supportedDomains) {
|
|
489
|
+
if (normalized.endsWith("." + domain)) {
|
|
490
|
+
return `webmcp-tools/${domain}.js`;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
async function injectDomainTools(page) {
|
|
496
|
+
let hostname;
|
|
497
|
+
try {
|
|
498
|
+
const url = new URL(page.url());
|
|
499
|
+
hostname = url.hostname;
|
|
500
|
+
} catch {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const bundleName = getToolsBundleName(hostname);
|
|
504
|
+
if (!bundleName) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const toolBundlePath = import_path2.default.resolve(__dirname, bundleName);
|
|
508
|
+
if (!import_fs2.default.existsSync(toolBundlePath)) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
|
|
512
|
+
const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
513
|
+
const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
|
|
514
|
+
if (alreadyInjected) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
console.log(import_picocolors.default.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
|
|
518
|
+
const toolScript = import_fs2.default.readFileSync(toolBundlePath, "utf-8");
|
|
519
|
+
try {
|
|
520
|
+
await page.evaluate(toolScript);
|
|
521
|
+
console.log(import_picocolors.default.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
524
|
+
console.warn(import_picocolors.default.yellow(`\u57DF\u540D\u5DE5\u5177\u6CE8\u5165\u5931\u8D25 (${hostname}): ${msg}`));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
var import_puppeteer_core, import_picocolors, import_fs2, import_os, import_path2, import_http, import_child_process, import_url2, import_meta, __filename, __dirname;
|
|
528
|
+
var init_browser = __esm({
|
|
529
|
+
"src/browser.ts"() {
|
|
530
|
+
"use strict";
|
|
531
|
+
import_puppeteer_core = __toESM(require("puppeteer-core"), 1);
|
|
532
|
+
import_picocolors = __toESM(require("picocolors"), 1);
|
|
533
|
+
import_fs2 = __toESM(require("fs"), 1);
|
|
534
|
+
import_os = __toESM(require("os"), 1);
|
|
535
|
+
import_path2 = __toESM(require("path"), 1);
|
|
536
|
+
import_http = __toESM(require("http"), 1);
|
|
537
|
+
import_child_process = require("child_process");
|
|
538
|
+
import_url2 = require("url");
|
|
539
|
+
import_meta = {};
|
|
540
|
+
__filename = (0, import_url2.fileURLToPath)(import_meta.url);
|
|
541
|
+
__dirname = import_path2.default.dirname(__filename);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// package.json
|
|
546
|
+
var package_default;
|
|
547
|
+
var init_package = __esm({
|
|
548
|
+
"package.json"() {
|
|
549
|
+
package_default = {
|
|
550
|
+
name: "@opentiny/webmcp-cli",
|
|
551
|
+
version: "0.1.1",
|
|
552
|
+
type: "module",
|
|
553
|
+
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
554
|
+
repository: {
|
|
555
|
+
type: "git",
|
|
556
|
+
url: "git+https://github.com/opentiny/webmcp-sdk.git",
|
|
557
|
+
directory: "packages/webmcp-cli"
|
|
558
|
+
},
|
|
559
|
+
bugs: {
|
|
560
|
+
url: "https://github.com/opentiny/webmcp-sdk/issues"
|
|
561
|
+
},
|
|
562
|
+
main: "dist/index.js",
|
|
563
|
+
types: "dist/index.d.ts",
|
|
564
|
+
bin: {
|
|
565
|
+
"webmcp-cli": "./dist/bin.js"
|
|
566
|
+
},
|
|
567
|
+
files: [
|
|
568
|
+
"dist",
|
|
569
|
+
"README.md",
|
|
570
|
+
"scripts/ensure-pty-helper.mjs"
|
|
571
|
+
],
|
|
572
|
+
scripts: {
|
|
573
|
+
dev: "tsup --watch",
|
|
574
|
+
"build:inject": "node scripts/build-inject.mjs",
|
|
575
|
+
build: "tsup && pnpm run build:inject",
|
|
576
|
+
prepublishOnly: "pnpm run build",
|
|
577
|
+
"link:global": "npm install -g .",
|
|
578
|
+
start: "node dist/bin.js",
|
|
579
|
+
postinstall: "node scripts/ensure-pty-helper.mjs",
|
|
580
|
+
test: "vitest run",
|
|
581
|
+
"test:watch": "vitest",
|
|
582
|
+
"test:browser": "vitest run --config vitest.e2e.config.ts"
|
|
583
|
+
},
|
|
584
|
+
keywords: [
|
|
585
|
+
"chrome",
|
|
586
|
+
"puppeteer",
|
|
587
|
+
"cli"
|
|
588
|
+
],
|
|
589
|
+
license: "MIT",
|
|
590
|
+
author: "OpenTiny Team",
|
|
591
|
+
engines: {
|
|
592
|
+
node: ">=18.19.0"
|
|
593
|
+
},
|
|
594
|
+
publishConfig: {
|
|
595
|
+
access: "public"
|
|
596
|
+
},
|
|
597
|
+
dependencies: {
|
|
598
|
+
"@mcp-b/webmcp-polyfill": "catalog:",
|
|
599
|
+
"@modelcontextprotocol/sdk": "catalog:",
|
|
600
|
+
"@opentiny/next-sdk": "workspace:^",
|
|
601
|
+
"@page-agent/page-controller": "catalog:",
|
|
602
|
+
clipboardy: "^5.3.1",
|
|
603
|
+
commander: "^11.1.0",
|
|
604
|
+
execa: "^9.6.1",
|
|
605
|
+
json5: "^2.2.3",
|
|
606
|
+
marked: "^15.0.7",
|
|
607
|
+
picocolors: "^1.0.0",
|
|
608
|
+
"puppeteer-core": "^22.0.0",
|
|
609
|
+
ws: "^8.18.0",
|
|
610
|
+
zod: "^3.23.0"
|
|
611
|
+
},
|
|
612
|
+
optionalDependencies: {
|
|
613
|
+
"node-pty": "^1.1.0"
|
|
614
|
+
},
|
|
615
|
+
devDependencies: {
|
|
616
|
+
"@types/node": "^20.0.0",
|
|
617
|
+
"@types/ws": "^8.5.10",
|
|
618
|
+
esbuild: "^0.25.0",
|
|
619
|
+
tsup: "^8.0.2",
|
|
620
|
+
tsx: "^4.0.0",
|
|
621
|
+
typescript: "^5.3.3",
|
|
622
|
+
vitest: "^4.1.10"
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// src/mcp/tools.ts
|
|
629
|
+
var import_zod, BrowserStateInput, BrowserToolCallInput, BrowserTabsOpenInput, BrowserTabsCloseInput, BrowserTabsSwitchInput, BrowserTabsNavInput, BrowserSubAgentRunInput, BrowserSkillsListInput, BrowserSkillsReadInput;
|
|
630
|
+
var init_tools = __esm({
|
|
631
|
+
"src/mcp/tools.ts"() {
|
|
632
|
+
"use strict";
|
|
633
|
+
import_zod = require("zod");
|
|
634
|
+
BrowserStateInput = import_zod.z.object({
|
|
635
|
+
tabId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional().describe(
|
|
636
|
+
"\u76EE\u6807 tab ID\u3002CDP \u6A21\u5F0F\u4E3A target UUID \u5B57\u7B26\u4E32\uFF1BWXT \u6A21\u5F0F\u4E3A Chrome tab ID \u6570\u503C\u3002\u4E0D\u4F20\u65F6\u4F7F\u7528\u5F53\u524D\u6D3B\u8DC3 tab\u3002"
|
|
637
|
+
)
|
|
638
|
+
});
|
|
639
|
+
BrowserToolCallInput = import_zod.z.object({
|
|
640
|
+
toolName: import_zod.z.string().describe("\u9875\u9762 WebMCP \u5DE5\u5177\u540D\u79F0\uFF08\u53EF\u4ECE browser_state \u8FD4\u56DE\u7684 webmcpTools \u5217\u8868\u4E2D\u83B7\u53D6\uFF09"),
|
|
641
|
+
toolArgs: import_zod.z.record(import_zod.z.unknown()).optional().describe("\u5DE5\u5177\u53C2\u6570\uFF0C\u4E0E\u5DE5\u5177\u7684 inputSchema \u5BF9\u9F50"),
|
|
642
|
+
tabId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional().describe("\u76EE\u6807 tab ID\uFF0C\u4E0D\u4F20\u65F6\u4F7F\u7528\u5F53\u524D\u6D3B\u8DC3 tab")
|
|
643
|
+
});
|
|
644
|
+
BrowserTabsOpenInput = import_zod.z.object({
|
|
645
|
+
url: import_zod.z.string().url().refine((val) => /^https?:\/\//i.test(val), { message: "\u53EA\u5141\u8BB8\u6253\u5F00 http \u6216 https \u534F\u8BAE\u7684 URL" }).describe("\u8981\u6253\u5F00\u7684 URL\uFF08\u4EC5\u652F\u6301 http/https \u534F\u8BAE\uFF09")
|
|
646
|
+
});
|
|
647
|
+
BrowserTabsCloseInput = import_zod.z.object({
|
|
648
|
+
tabId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional().describe(
|
|
649
|
+
"\u8981\u5173\u95ED\u7684 tab ID\uFF0C\u4E0D\u4F20\u65F6\u5173\u95ED\u5F53\u524D\u6D3B\u8DC3 tab"
|
|
650
|
+
)
|
|
651
|
+
});
|
|
652
|
+
BrowserTabsSwitchInput = import_zod.z.object({
|
|
653
|
+
tabId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).describe(
|
|
654
|
+
"\u8981\u5207\u6362\u5230\u7684 tab ID\uFF08\u53EF\u4ECE browser_state \u8FD4\u56DE\u7684 tabs \u5217\u8868\u4E2D\u83B7\u53D6\uFF09"
|
|
655
|
+
),
|
|
656
|
+
activateVisible: import_zod.z.boolean().optional().describe("\u662F\u5426\u5728\u524D\u53F0\u663E\u5F0F\u5207\u6362 Chrome \u53EF\u89C1\u6D3B\u52A8\u6807\u7B7E\uFF08\u4EC5 WXT \u6A21\u5F0F\u6709\u6548\uFF0C\u9ED8\u8BA4 false\uFF09")
|
|
657
|
+
});
|
|
658
|
+
BrowserTabsNavInput = import_zod.z.object({
|
|
659
|
+
tabId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional().describe("\u76EE\u6807 tab ID\uFF0C\u4E0D\u4F20\u65F6\u4F7F\u7528\u5F53\u524D\u6D3B\u8DC3 tab")
|
|
660
|
+
});
|
|
661
|
+
BrowserSubAgentRunInput = import_zod.z.object({
|
|
662
|
+
instruction: import_zod.z.string().describe("\u8981\u59D4\u6258\u7ED9 Tiny Robot \u5B50\u4EE3\u7406\u6267\u884C\u7684\u9AD8\u5C42\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\u6307\u4EE4"),
|
|
663
|
+
tabId: import_zod.z.number().optional().describe("\u76EE\u6807 tab ID\uFF0C\u4E0D\u4F20\u65F6\u4F7F\u7528\u5F53\u524D\u6D3B\u8DC3 tab"),
|
|
664
|
+
maxSteps: import_zod.z.number().min(1).max(20).optional().describe("\u6700\u5927\u81EA\u4E3B\u89C4\u5212\u6267\u884C\u6B65\u6570\uFF08\u9ED8\u8BA4 8\uFF0C\u6700\u5927 20\uFF09")
|
|
665
|
+
});
|
|
666
|
+
BrowserSkillsListInput = import_zod.z.object({}).describe("\u83B7\u53D6\u5F53\u524D\u6D4F\u89C8\u5668\u6269\u5C55\u4E2D\u5DF2\u6CE8\u518C\u7684\u6240\u6709 Skills \u6280\u80FD\u5143\u6570\u636E\u5217\u8868");
|
|
667
|
+
BrowserSkillsReadInput = import_zod.z.object({
|
|
668
|
+
name: import_zod.z.string().describe("\u8981\u8BFB\u53D6\u7684\u6280\u80FD\u540D\u79F0\uFF08\u53EF\u4ECE browser_skills_list \u8FD4\u56DE\u7684\u5217\u8868\u4E2D\u83B7\u53D6\uFF09")
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// src/adapters/cdp-adapter.ts
|
|
674
|
+
var cdp_adapter_exports = {};
|
|
675
|
+
__export(cdp_adapter_exports, {
|
|
676
|
+
CdpBrowserAdapter: () => CdpBrowserAdapter
|
|
677
|
+
});
|
|
678
|
+
var CdpBrowserAdapter;
|
|
679
|
+
var init_cdp_adapter = __esm({
|
|
680
|
+
"src/adapters/cdp-adapter.ts"() {
|
|
681
|
+
"use strict";
|
|
682
|
+
init_browser();
|
|
683
|
+
CdpBrowserAdapter = class {
|
|
684
|
+
browser = null;
|
|
685
|
+
async getBrowser() {
|
|
686
|
+
if (!this.browser) {
|
|
687
|
+
this.browser = await connectBrowser();
|
|
688
|
+
}
|
|
689
|
+
return this.browser;
|
|
690
|
+
}
|
|
691
|
+
async getState(tabId) {
|
|
692
|
+
const browser = await this.getBrowser();
|
|
693
|
+
const tabIdStr = tabId != null ? String(tabId) : void 0;
|
|
694
|
+
const page = await getTargetPage(browser, tabIdStr);
|
|
695
|
+
await injectIntoPage(page);
|
|
696
|
+
const state = await page.evaluate(async () => {
|
|
697
|
+
const url = document.URL;
|
|
698
|
+
const title = document.title;
|
|
699
|
+
const mcp = document.modelContext || navigator.modelContext;
|
|
700
|
+
let webmcpTools = [];
|
|
701
|
+
if (mcp && typeof mcp.getTools === "function") {
|
|
702
|
+
try {
|
|
703
|
+
const toolsResult = await mcp.getTools();
|
|
704
|
+
const list = toolsResult?.tools || toolsResult || [];
|
|
705
|
+
webmcpTools = list.filter((t) => t && typeof t.name === "string").map((t) => ({
|
|
706
|
+
name: t.name,
|
|
707
|
+
description: typeof t.description === "string" ? t.description.slice(0, 200) : void 0
|
|
708
|
+
}));
|
|
709
|
+
} catch {
|
|
710
|
+
webmcpTools = [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return { url, title, webmcpTools };
|
|
714
|
+
});
|
|
715
|
+
const activeTabId = await getPageTargetId(page).catch(() => null);
|
|
716
|
+
const pages = await browser.pages();
|
|
717
|
+
const tabs2 = await Promise.all(
|
|
718
|
+
pages.map(async (p) => {
|
|
719
|
+
const pUrl = p.url();
|
|
720
|
+
if (pUrl.startsWith("devtools://")) return null;
|
|
721
|
+
const pTitle = await Promise.race([
|
|
722
|
+
p.title().catch(() => "Unknown"),
|
|
723
|
+
new Promise((resolve3) => setTimeout(() => resolve3("Unknown"), 500))
|
|
724
|
+
]);
|
|
725
|
+
return {
|
|
726
|
+
tabId: await getPageTargetId(p).catch(() => pUrl),
|
|
727
|
+
title: pTitle,
|
|
728
|
+
url: pUrl
|
|
729
|
+
};
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
return {
|
|
733
|
+
...state,
|
|
734
|
+
activeTabId: activeTabId ?? void 0,
|
|
735
|
+
tabs: tabs2.filter(Boolean)
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async callTool(toolName, args, tabId) {
|
|
739
|
+
const browser = await this.getBrowser();
|
|
740
|
+
const tabIdStr = tabId != null ? String(tabId) : void 0;
|
|
741
|
+
const page = await getTargetPage(browser, tabIdStr);
|
|
742
|
+
const urlBefore = page.url();
|
|
743
|
+
await injectIntoPage(page);
|
|
744
|
+
const argsJson = JSON.stringify(args);
|
|
745
|
+
try {
|
|
746
|
+
const result = await page.evaluate(
|
|
747
|
+
async (name, inputString) => {
|
|
748
|
+
const mcp = document.modelContext || navigator.modelContext;
|
|
749
|
+
if (!mcp || typeof mcp.executeTool !== "function") {
|
|
750
|
+
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
|
|
751
|
+
}
|
|
752
|
+
const tools = await mcp.getTools();
|
|
753
|
+
const toolObj = tools.find((t) => t.name === name);
|
|
754
|
+
if (!toolObj) throw new Error(`Tool ${name} not found`);
|
|
755
|
+
let res = await mcp.executeTool(toolObj, inputString);
|
|
756
|
+
if (typeof res === "string") {
|
|
757
|
+
try {
|
|
758
|
+
res = JSON.parse(res);
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (res === void 0 || res === null) {
|
|
763
|
+
throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C");
|
|
764
|
+
}
|
|
765
|
+
if (typeof res === "object" && res.success === false) {
|
|
766
|
+
const failed = res;
|
|
767
|
+
throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
|
|
768
|
+
}
|
|
769
|
+
return res;
|
|
770
|
+
},
|
|
771
|
+
toolName,
|
|
772
|
+
argsJson
|
|
773
|
+
);
|
|
774
|
+
return result;
|
|
775
|
+
} catch (evalError) {
|
|
776
|
+
const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
|
|
777
|
+
const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
|
|
778
|
+
if (isContextDestroyed) {
|
|
779
|
+
await new Promise((resolve3) => setTimeout(resolve3, 800));
|
|
780
|
+
const newUrl = page.url();
|
|
781
|
+
if (newUrl && newUrl !== urlBefore) {
|
|
782
|
+
return { success: true, message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`, navigatedTo: newUrl };
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
throw evalError;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async tabs(action, options) {
|
|
789
|
+
const browser = await this.getBrowser();
|
|
790
|
+
const tabIdStr = options.tabId != null ? String(options.tabId) : void 0;
|
|
791
|
+
switch (action) {
|
|
792
|
+
case "open": {
|
|
793
|
+
if (!options.url) throw new Error("tabs open \u9700\u8981 url \u53C2\u6570");
|
|
794
|
+
const page = await browser.newPage();
|
|
795
|
+
await page.goto(options.url);
|
|
796
|
+
await injectIntoPage(page);
|
|
797
|
+
const tabId = await getPageTargetId(page);
|
|
798
|
+
setLastActiveTabId(tabId);
|
|
799
|
+
return { success: true, tabId, url: options.url };
|
|
800
|
+
}
|
|
801
|
+
case "close": {
|
|
802
|
+
const page = await getTargetPage(browser, tabIdStr);
|
|
803
|
+
await page.close();
|
|
804
|
+
return { success: true };
|
|
805
|
+
}
|
|
806
|
+
case "switch": {
|
|
807
|
+
if (!tabIdStr) throw new Error("tabs switch \u9700\u8981 tabId \u53C2\u6570");
|
|
808
|
+
await activateTabById(browser, tabIdStr);
|
|
809
|
+
setLastActiveTabId(tabIdStr);
|
|
810
|
+
return { success: true, tabId: tabIdStr };
|
|
811
|
+
}
|
|
812
|
+
case "back": {
|
|
813
|
+
const page = await getTargetPage(browser, tabIdStr);
|
|
814
|
+
await page.goBack();
|
|
815
|
+
return { success: true };
|
|
816
|
+
}
|
|
817
|
+
case "forward": {
|
|
818
|
+
const page = await getTargetPage(browser, tabIdStr);
|
|
819
|
+
await page.goForward();
|
|
820
|
+
return { success: true };
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
async dispose() {
|
|
825
|
+
if (this.browser) {
|
|
826
|
+
await this.browser.disconnect().catch(() => {
|
|
827
|
+
});
|
|
828
|
+
this.browser = null;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// src/bridge/protocol.ts
|
|
836
|
+
function isTerminalMethod(method) {
|
|
837
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
838
|
+
}
|
|
839
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
840
|
+
var init_protocol = __esm({
|
|
841
|
+
"src/bridge/protocol.ts"() {
|
|
842
|
+
"use strict";
|
|
843
|
+
DEFAULT_WS_PORT = 18999;
|
|
844
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
845
|
+
WS_CLOSE_CODE = {
|
|
846
|
+
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
847
|
+
REPLACED: 4e3,
|
|
848
|
+
/** 认证未通过(未提供 Token、Token 错误或 HMAC 签名校验失败) */
|
|
849
|
+
UNAUTHORIZED: 4001,
|
|
850
|
+
/** 协议主版本号不匹配 */
|
|
851
|
+
PROTOCOL_MISMATCH: 4002
|
|
852
|
+
};
|
|
853
|
+
BRIDGE_ERROR_CODE = {
|
|
854
|
+
/** 请求执行超时 */
|
|
855
|
+
REQUEST_TIMEOUT: -32e3,
|
|
856
|
+
/** 命令执行超时 */
|
|
857
|
+
EXECUTION_TIMEOUT: -32001,
|
|
858
|
+
/** 命令或系统操作执行失败 */
|
|
859
|
+
EXECUTION_FAILED: -32002,
|
|
860
|
+
/** 无效参数(如 command 为空) */
|
|
861
|
+
INVALID_PARAMS: -32602,
|
|
862
|
+
/** 不支持的方法 */
|
|
863
|
+
METHOD_NOT_FOUND: -32601,
|
|
864
|
+
/** 扩展尚未连接至桥接服务 */
|
|
865
|
+
EXTENSION_NOT_CONNECTED: -32051,
|
|
866
|
+
/** 侧边栏尚未就绪或已关闭 */
|
|
867
|
+
SIDEPANEL_NOT_READY: -32050
|
|
868
|
+
};
|
|
869
|
+
BRIDGE_METHODS = {
|
|
870
|
+
TOOL_CALL: "tool/call",
|
|
871
|
+
SUB_AGENT_RUN: "sub_agent/run",
|
|
872
|
+
BROWSER_TABS: "browser/tabs",
|
|
873
|
+
BROWSER_STATE: "browser/state",
|
|
874
|
+
SKILLS_LIST: "skills/list",
|
|
875
|
+
SKILLS_GET: "skills/get",
|
|
876
|
+
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
877
|
+
SYSTEM_BASH: "system/bash",
|
|
878
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
879
|
+
TERMINAL_OPEN: "terminal/open",
|
|
880
|
+
TERMINAL_INPUT: "terminal/input",
|
|
881
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
882
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
883
|
+
};
|
|
884
|
+
TERMINAL_EVENTS = {
|
|
885
|
+
OUTPUT: "notification/terminal_output",
|
|
886
|
+
EXIT: "notification/terminal_exit"
|
|
887
|
+
};
|
|
888
|
+
BASH_EVENTS = {
|
|
889
|
+
OUTPUT: "notification/bash_output"
|
|
890
|
+
};
|
|
891
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
892
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
893
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
894
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
895
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
896
|
+
]);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
// src/bridge/workspace-manager.ts
|
|
901
|
+
function resolveBaseWorkspace(params) {
|
|
902
|
+
if (params.workspacePath && typeof params.workspacePath === "string" && params.workspacePath.trim()) {
|
|
903
|
+
const trimmedWs = params.workspacePath.trim();
|
|
904
|
+
return import_node_path.default.isAbsolute(trimmedWs) ? trimmedWs : import_node_path.default.resolve(import_node_os.default.homedir(), trimmedWs);
|
|
905
|
+
}
|
|
906
|
+
if (params.conversationId && typeof params.conversationId === "string" && params.conversationId.trim()) {
|
|
907
|
+
const safeId = params.conversationId.trim().replace(/[/\\?%*:|"<>.]/g, "_");
|
|
908
|
+
const sessionDir = import_node_path.default.resolve(WORKSPACES_ROOT, safeId || "default");
|
|
909
|
+
const relative = import_node_path.default.relative(WORKSPACES_ROOT, sessionDir);
|
|
910
|
+
if (!relative || relative.startsWith("..") || import_node_path.default.isAbsolute(relative)) {
|
|
911
|
+
return import_node_path.default.join(WORKSPACES_ROOT, "default");
|
|
912
|
+
}
|
|
913
|
+
return sessionDir;
|
|
914
|
+
}
|
|
915
|
+
return import_node_path.default.join(WORKSPACES_ROOT, "default");
|
|
916
|
+
}
|
|
917
|
+
function resolveWorkspaceDir(params = {}) {
|
|
918
|
+
const baseDir = resolveBaseWorkspace(params);
|
|
919
|
+
if (!import_node_fs.default.existsSync(baseDir)) {
|
|
920
|
+
try {
|
|
921
|
+
import_node_fs.default.mkdirSync(baseDir, { recursive: true, mode: 493 });
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (!params.cwd || typeof params.cwd !== "string" || !params.cwd.trim()) {
|
|
926
|
+
return baseDir;
|
|
927
|
+
}
|
|
928
|
+
const trimmedCwd = params.cwd.trim();
|
|
929
|
+
const targetDir = import_node_path.default.isAbsolute(trimmedCwd) ? import_node_path.default.resolve(trimmedCwd) : import_node_path.default.resolve(baseDir, trimmedCwd);
|
|
930
|
+
const relative = import_node_path.default.relative(baseDir, targetDir);
|
|
931
|
+
if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative)) {
|
|
932
|
+
throw new BashInvalidParamsError(`\u76EE\u6807\u6267\u884C\u8DEF\u5F84\u8D85\u51FA\u5DF2\u6388\u6743\u5DE5\u4F5C\u7A7A\u95F4\u5B89\u5168\u8FB9\u754C: ${trimmedCwd}`);
|
|
933
|
+
}
|
|
934
|
+
return targetDir;
|
|
935
|
+
}
|
|
936
|
+
var import_node_path, import_node_os, import_node_fs, WORKSPACES_ROOT, BashInvalidParamsError;
|
|
937
|
+
var init_workspace_manager = __esm({
|
|
938
|
+
"src/bridge/workspace-manager.ts"() {
|
|
939
|
+
"use strict";
|
|
940
|
+
import_node_path = __toESM(require("path"), 1);
|
|
941
|
+
import_node_os = __toESM(require("os"), 1);
|
|
942
|
+
import_node_fs = __toESM(require("fs"), 1);
|
|
943
|
+
WORKSPACES_ROOT = import_node_path.default.join(import_node_os.default.homedir(), ".robot-wxt", "workspaces");
|
|
944
|
+
BashInvalidParamsError = class extends Error {
|
|
945
|
+
code = "INVALID_PARAMS";
|
|
946
|
+
constructor(message = "\u53C2\u6570\u9519\u8BEF") {
|
|
947
|
+
super(message);
|
|
948
|
+
this.name = "BashInvalidParamsError";
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
// src/bridge/bash-executor.ts
|
|
955
|
+
function findWindowsBash() {
|
|
956
|
+
const pathDirs = (process.env.PATH || "").split(import_node_path2.default.delimiter);
|
|
957
|
+
for (const dir of pathDirs) {
|
|
958
|
+
if (!dir) continue;
|
|
959
|
+
const candidate = import_node_path2.default.join(dir, "bash.exe");
|
|
960
|
+
try {
|
|
961
|
+
if (import_node_fs2.default.existsSync(candidate)) return candidate;
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
966
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
967
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
968
|
+
const programW6432 = process.env.ProgramW6432 || programFiles;
|
|
969
|
+
const commonGitBashPaths = [
|
|
970
|
+
import_node_path2.default.join(programFiles, "Git", "bin", "bash.exe"),
|
|
971
|
+
import_node_path2.default.join(programFiles, "Git", "usr", "bin", "bash.exe"),
|
|
972
|
+
import_node_path2.default.join(programW6432, "Git", "bin", "bash.exe"),
|
|
973
|
+
import_node_path2.default.join(programFilesX86, "Git", "bin", "bash.exe"),
|
|
974
|
+
import_node_path2.default.join(localAppData, "Programs", "Git", "bin", "bash.exe"),
|
|
975
|
+
import_node_path2.default.join(localAppData, "Programs", "Git", "usr", "bin", "bash.exe"),
|
|
976
|
+
import_node_path2.default.join(process.env.SystemRoot || "C:\\Windows", "System32", "bash.exe")
|
|
977
|
+
];
|
|
978
|
+
for (const candidate of commonGitBashPaths) {
|
|
979
|
+
try {
|
|
980
|
+
if (import_node_fs2.default.existsSync(candidate)) return candidate;
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
function resolveShell(forcePlatform) {
|
|
987
|
+
const currentPlatform = forcePlatform || process.platform;
|
|
988
|
+
if (!forcePlatform && cachedShell) {
|
|
989
|
+
return cachedShell;
|
|
990
|
+
}
|
|
991
|
+
if (currentPlatform !== "win32") {
|
|
992
|
+
const posixShell = process.env.SHELL || "/bin/bash";
|
|
993
|
+
if (!forcePlatform) cachedShell = posixShell;
|
|
994
|
+
return posixShell;
|
|
995
|
+
}
|
|
996
|
+
const winBash = findWindowsBash();
|
|
997
|
+
if (winBash) {
|
|
998
|
+
if (!forcePlatform) cachedShell = winBash;
|
|
999
|
+
return winBash;
|
|
1000
|
+
}
|
|
1001
|
+
const fallback = "powershell.exe";
|
|
1002
|
+
if (!forcePlatform) cachedShell = fallback;
|
|
1003
|
+
return fallback;
|
|
1004
|
+
}
|
|
1005
|
+
function stripAnsi(text) {
|
|
1006
|
+
return text.replace(ANSI_PATTERN, "");
|
|
1007
|
+
}
|
|
1008
|
+
function createAnsiStripper() {
|
|
1009
|
+
let pending = "";
|
|
1010
|
+
const take = (final) => {
|
|
1011
|
+
let out = "";
|
|
1012
|
+
let index = 0;
|
|
1013
|
+
while (index < pending.length) {
|
|
1014
|
+
if (pending[index] !== "\x1B") {
|
|
1015
|
+
out += pending[index];
|
|
1016
|
+
index += 1;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const rest = pending.slice(index);
|
|
1020
|
+
ANSI_PATTERN.lastIndex = 0;
|
|
1021
|
+
const match = ANSI_PATTERN.exec(rest);
|
|
1022
|
+
if (match && match.index === 0) {
|
|
1023
|
+
index += match[0].length;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (!final && rest.length < 128 && isOpenAnsi(rest)) {
|
|
1027
|
+
pending = rest;
|
|
1028
|
+
return out;
|
|
1029
|
+
}
|
|
1030
|
+
out += pending[index];
|
|
1031
|
+
index += 1;
|
|
1032
|
+
}
|
|
1033
|
+
pending = "";
|
|
1034
|
+
return out;
|
|
1035
|
+
};
|
|
1036
|
+
return {
|
|
1037
|
+
push(text) {
|
|
1038
|
+
pending += text;
|
|
1039
|
+
return take(false);
|
|
1040
|
+
},
|
|
1041
|
+
end() {
|
|
1042
|
+
return take(true);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function isOpenAnsi(text) {
|
|
1047
|
+
if (!text.startsWith("\x1B")) return false;
|
|
1048
|
+
if (text.length === 1) return true;
|
|
1049
|
+
if (text[1] === "[") {
|
|
1050
|
+
let index = 2;
|
|
1051
|
+
while (index < text.length && /[0-?]/.test(text[index])) index += 1;
|
|
1052
|
+
while (index < text.length && /[ -/]/.test(text[index])) index += 1;
|
|
1053
|
+
return index >= text.length;
|
|
1054
|
+
}
|
|
1055
|
+
if (text[1] === "]") return !text.includes("\x07") && !text.includes("\x1B\\");
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
function withoutCarriageReturn(buf) {
|
|
1059
|
+
if (!buf.includes(13)) return buf;
|
|
1060
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
1061
|
+
let size = 0;
|
|
1062
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
1063
|
+
const byte = buf[index];
|
|
1064
|
+
if (byte === 13) continue;
|
|
1065
|
+
out[size] = byte;
|
|
1066
|
+
size += 1;
|
|
1067
|
+
}
|
|
1068
|
+
return out.subarray(0, size);
|
|
1069
|
+
}
|
|
1070
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false, ansi) {
|
|
1071
|
+
const state = { chunks: [], truncated: false };
|
|
1072
|
+
if (!stream) return state;
|
|
1073
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1074
|
+
let bytes = 0;
|
|
1075
|
+
const emitText = (text) => {
|
|
1076
|
+
const cleaned = ansi ? ansi.push(text) : text;
|
|
1077
|
+
if (cleaned) onText?.(cleaned);
|
|
1078
|
+
};
|
|
1079
|
+
const emit = (buf) => {
|
|
1080
|
+
emitText(decoder.write(buf));
|
|
1081
|
+
};
|
|
1082
|
+
stream.on("data", (chunk) => {
|
|
1083
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1084
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1085
|
+
if (buf.length === 0) return;
|
|
1086
|
+
emit(buf);
|
|
1087
|
+
if (bytes >= maxBuffer) {
|
|
1088
|
+
state.truncated = true;
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const remaining = maxBuffer - bytes;
|
|
1092
|
+
if (buf.length <= remaining) {
|
|
1093
|
+
state.chunks.push(buf);
|
|
1094
|
+
bytes += buf.length;
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
state.chunks.push(buf.subarray(0, remaining));
|
|
1098
|
+
bytes += remaining;
|
|
1099
|
+
state.truncated = true;
|
|
1100
|
+
});
|
|
1101
|
+
stream.on("end", () => {
|
|
1102
|
+
const rest = ansi ? `${ansi.push(decoder.end())}${ansi.end()}` : decoder.end();
|
|
1103
|
+
if (rest) onText?.(rest);
|
|
1104
|
+
});
|
|
1105
|
+
return state;
|
|
1106
|
+
}
|
|
1107
|
+
function decodeLimited(state, removeAnsi = false) {
|
|
1108
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1109
|
+
const text = decoder.write(Buffer.concat(state.chunks));
|
|
1110
|
+
const flushed = state.truncated ? "" : decoder.end();
|
|
1111
|
+
const decoded = text + flushed;
|
|
1112
|
+
return (removeAnsi ? stripAnsi(decoded) : decoded).trim();
|
|
1113
|
+
}
|
|
1114
|
+
function signalProcessTree(pid, signal) {
|
|
1115
|
+
if (!pid || pid <= 0) return;
|
|
1116
|
+
if (process.platform === "win32") {
|
|
1117
|
+
try {
|
|
1118
|
+
(0, import_node_child_process.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 5e3 });
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
process.kill(-pid, signal);
|
|
1125
|
+
} catch {
|
|
1126
|
+
try {
|
|
1127
|
+
process.kill(pid, signal);
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function ignoreStreamError(stream) {
|
|
1133
|
+
stream?.on("error", () => {
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
async function executeBash(params) {
|
|
1137
|
+
const trimmedCommand = params.command?.trim();
|
|
1138
|
+
if (!trimmedCommand) {
|
|
1139
|
+
throw new BashInvalidParamsError("\u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1140
|
+
}
|
|
1141
|
+
if (/rm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+|--recursive\s+--force\s+)\s*(\/|~|\$HOME|\/\*)\s*$/i.test(
|
|
1142
|
+
trimmedCommand
|
|
1143
|
+
) || /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/i.test(trimmedCommand)) {
|
|
1144
|
+
throw new BashInvalidParamsError("\u5B89\u5168\u62E6\u622A\uFF1A\u7981\u6B62\u5728\u5BBF\u4E3B\u673A\u6267\u884C\u9488\u5BF9\u7CFB\u7EDF\u5173\u952E\u6839\u8DEF\u5F84\u7684\u9AD8\u5371\u7834\u574F\u6027\u547D\u4EE4");
|
|
1145
|
+
}
|
|
1146
|
+
const cwd = resolveWorkspaceDir({
|
|
1147
|
+
cwd: params.cwd,
|
|
1148
|
+
workspacePath: params.workspacePath,
|
|
1149
|
+
conversationId: params.conversationId
|
|
1150
|
+
});
|
|
1151
|
+
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
1152
|
+
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
1153
|
+
const shell = resolveShell();
|
|
1154
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1155
|
+
const detached = process.platform !== "win32";
|
|
1156
|
+
const subprocess = useTty ? (0, import_execa.execa)("python3", ["-u", "-c", TTY_HELPER], {
|
|
1157
|
+
cwd,
|
|
1158
|
+
env: {
|
|
1159
|
+
...env,
|
|
1160
|
+
PAGER: "cat",
|
|
1161
|
+
GIT_PAGER: "cat",
|
|
1162
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1163
|
+
BASH_TTY_SHELL: shell
|
|
1164
|
+
},
|
|
1165
|
+
stdin: "ignore",
|
|
1166
|
+
buffer: false,
|
|
1167
|
+
encoding: "buffer",
|
|
1168
|
+
reject: false,
|
|
1169
|
+
windowsHide: true,
|
|
1170
|
+
stripFinalNewline: false,
|
|
1171
|
+
detached
|
|
1172
|
+
}) : (0, import_execa.execa)(trimmedCommand, {
|
|
1173
|
+
shell,
|
|
1174
|
+
cwd,
|
|
1175
|
+
env,
|
|
1176
|
+
stdin: "ignore",
|
|
1177
|
+
buffer: false,
|
|
1178
|
+
encoding: "buffer",
|
|
1179
|
+
reject: false,
|
|
1180
|
+
windowsHide: true,
|
|
1181
|
+
stripFinalNewline: false,
|
|
1182
|
+
detached
|
|
1183
|
+
});
|
|
1184
|
+
ignoreStreamError(subprocess.stdout);
|
|
1185
|
+
ignoreStreamError(subprocess.stderr);
|
|
1186
|
+
let timedOut = false;
|
|
1187
|
+
let forceTimer;
|
|
1188
|
+
const timer = setTimeout(() => {
|
|
1189
|
+
timedOut = true;
|
|
1190
|
+
signalProcessTree(subprocess.pid, "SIGTERM");
|
|
1191
|
+
forceTimer = setTimeout(() => {
|
|
1192
|
+
signalProcessTree(subprocess.pid, "SIGKILL");
|
|
1193
|
+
subprocess.stdout?.destroy();
|
|
1194
|
+
subprocess.stderr?.destroy();
|
|
1195
|
+
}, FORCE_KILL_AFTER_DELAY_MS);
|
|
1196
|
+
}, normalizedTimeoutMs);
|
|
1197
|
+
const emit = (stream) => (text) => {
|
|
1198
|
+
params.onOutput?.({ stream, text });
|
|
1199
|
+
};
|
|
1200
|
+
const ansi = useTty ? createAnsiStripper() : void 0;
|
|
1201
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty, ansi);
|
|
1202
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1203
|
+
let result;
|
|
1204
|
+
try {
|
|
1205
|
+
result = await subprocess;
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
if (timedOut) throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1208
|
+
throw err;
|
|
1209
|
+
} finally {
|
|
1210
|
+
clearTimeout(timer);
|
|
1211
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
1212
|
+
}
|
|
1213
|
+
if (timedOut) {
|
|
1214
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1215
|
+
}
|
|
1216
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1217
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1218
|
+
}
|
|
1219
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1220
|
+
return {
|
|
1221
|
+
stdout: decodeLimited(stdoutState, useTty),
|
|
1222
|
+
stderr: decodeLimited(stderrState),
|
|
1223
|
+
exitCode: result.exitCode ?? null,
|
|
1224
|
+
signal: result.signal ?? null,
|
|
1225
|
+
durationMs: result.durationMs ?? 0,
|
|
1226
|
+
...truncated ? { truncated: true } : {}
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
var import_node_child_process, import_node_fs2, import_node_path2, import_node_string_decoder, import_execa, BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell, FORCE_KILL_AFTER_DELAY_MS, TTY_HELPER, ANSI_PATTERN;
|
|
1230
|
+
var init_bash_executor = __esm({
|
|
1231
|
+
"src/bridge/bash-executor.ts"() {
|
|
1232
|
+
"use strict";
|
|
1233
|
+
import_node_child_process = require("child_process");
|
|
1234
|
+
import_node_fs2 = __toESM(require("fs"), 1);
|
|
1235
|
+
import_node_path2 = __toESM(require("path"), 1);
|
|
1236
|
+
import_node_string_decoder = require("string_decoder");
|
|
1237
|
+
import_execa = require("execa");
|
|
1238
|
+
init_workspace_manager();
|
|
1239
|
+
BashTimeoutError = class extends Error {
|
|
1240
|
+
code = "TIMEOUT";
|
|
1241
|
+
timeoutMs;
|
|
1242
|
+
constructor(timeoutMs) {
|
|
1243
|
+
super(`\u547D\u4EE4\u6267\u884C\u8D85\u65F6 (${timeoutMs / 1e3}s)`);
|
|
1244
|
+
this.name = "BashTimeoutError";
|
|
1245
|
+
this.timeoutMs = timeoutMs;
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
DEFAULT_TIMEOUT_MS = 3e4;
|
|
1249
|
+
MAX_TIMEOUT_MS = 6e5;
|
|
1250
|
+
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1251
|
+
cachedShell = null;
|
|
1252
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1253
|
+
TTY_HELPER = `
|
|
1254
|
+
import errno, os, sys
|
|
1255
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1256
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1257
|
+
if not cmd:
|
|
1258
|
+
sys.exit(127)
|
|
1259
|
+
import pty
|
|
1260
|
+
pid, fd = pty.fork()
|
|
1261
|
+
if pid == 0:
|
|
1262
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1263
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1264
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1265
|
+
try:
|
|
1266
|
+
# stdout/stderr \u4ECD\u662F\u7EC8\u7AEF\uFF1Bstdin \u6362\u6210 /dev/null\uFF0C\u8BFB\u53D6\u8F93\u5165\u7684\u547D\u4EE4\u4F1A\u7ACB\u523B\u9047\u5230 EOF
|
|
1267
|
+
null_fd = os.open(os.devnull, os.O_RDONLY)
|
|
1268
|
+
os.dup2(null_fd, 0)
|
|
1269
|
+
os.close(null_fd)
|
|
1270
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1271
|
+
except OSError:
|
|
1272
|
+
os._exit(127)
|
|
1273
|
+
while True:
|
|
1274
|
+
try:
|
|
1275
|
+
data = os.read(fd, 65536)
|
|
1276
|
+
except OSError as exc:
|
|
1277
|
+
if exc.errno == errno.EINTR:
|
|
1278
|
+
continue
|
|
1279
|
+
break
|
|
1280
|
+
if not data:
|
|
1281
|
+
break
|
|
1282
|
+
try:
|
|
1283
|
+
os.write(1, data)
|
|
1284
|
+
except OSError:
|
|
1285
|
+
break
|
|
1286
|
+
try:
|
|
1287
|
+
_, status = os.waitpid(pid, 0)
|
|
1288
|
+
except OSError:
|
|
1289
|
+
sys.exit(1)
|
|
1290
|
+
if os.WIFEXITED(status):
|
|
1291
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1292
|
+
if os.WIFSIGNALED(status):
|
|
1293
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1294
|
+
sys.exit(1)
|
|
1295
|
+
`;
|
|
1296
|
+
ANSI_PATTERN = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001B\\)|[@-Z\\-_])/g;
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
// src/bridge/directory-picker.ts
|
|
1301
|
+
async function pickDirectory(optionsOrTitle) {
|
|
1302
|
+
const options = typeof optionsOrTitle === "string" ? { title: optionsOrTitle } : optionsOrTitle || {};
|
|
1303
|
+
const title = options.title || "\u8BF7\u9009\u62E9\u5DE5\u4F5C\u7A7A\u95F4\u6587\u4EF6\u5939";
|
|
1304
|
+
const defaultPath = options.defaultPath && typeof options.defaultPath === "string" ? options.defaultPath.trim() : void 0;
|
|
1305
|
+
const platform = process.platform;
|
|
1306
|
+
if (platform === "darwin") {
|
|
1307
|
+
return pickDirectoryMac(title, defaultPath);
|
|
1308
|
+
}
|
|
1309
|
+
if (platform === "win32") {
|
|
1310
|
+
return pickDirectoryWindows(title, defaultPath);
|
|
1311
|
+
}
|
|
1312
|
+
return pickDirectoryLinux(title, defaultPath);
|
|
1313
|
+
}
|
|
1314
|
+
function pickDirectoryMac(title, defaultPath) {
|
|
1315
|
+
const safeTitle = title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1316
|
+
const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
|
|
1317
|
+
const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
|
|
1318
|
+
return new Promise((resolve3, reject) => {
|
|
1319
|
+
const child = (0, import_node_child_process2.spawn)("osascript", ["-e", appleScript], {
|
|
1320
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1321
|
+
});
|
|
1322
|
+
let stdout = "";
|
|
1323
|
+
let stderr = "";
|
|
1324
|
+
child.stdout?.on("data", (d) => {
|
|
1325
|
+
stdout += d.toString();
|
|
1326
|
+
});
|
|
1327
|
+
child.stderr?.on("data", (d) => {
|
|
1328
|
+
stderr += d.toString();
|
|
1329
|
+
});
|
|
1330
|
+
child.on("close", (code) => {
|
|
1331
|
+
if (code === 0) {
|
|
1332
|
+
const selected = stdout.trim();
|
|
1333
|
+
resolve3(selected || null);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (code === 1 && (stderr.includes("User canceled") || stderr.includes("-128") || !stderr.trim())) {
|
|
1337
|
+
resolve3(null);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
reject(new Error(`\u5524\u8D77\u7CFB\u7EDF\u6587\u4EF6\u5939\u9009\u62E9\u5668\u5931\u8D25: ${stderr.trim() || `\u9000\u51FA\u7801 ${code}`}`));
|
|
1341
|
+
});
|
|
1342
|
+
child.on("error", (err) => {
|
|
1343
|
+
reject(new Error(`\u5524\u8D77\u7CFB\u7EDF\u6587\u4EF6\u5939\u9009\u62E9\u5668\u5931\u8D25: ${err.message}`));
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
function escapePowerShellString(str) {
|
|
1348
|
+
return str.replace(/`/g, "``").replace(/"/g, '`"').replace(/\$/g, "`$");
|
|
1349
|
+
}
|
|
1350
|
+
function pickDirectoryWindows(title, defaultPath) {
|
|
1351
|
+
const safeTitle = escapePowerShellString(title);
|
|
1352
|
+
const safeDefaultPath = escapePowerShellString(defaultPath ?? "");
|
|
1353
|
+
const psScript = `
|
|
1354
|
+
$code = @'
|
|
1355
|
+
using System;
|
|
1356
|
+
using System.Runtime.InteropServices;
|
|
1357
|
+
|
|
1358
|
+
[ComImport, Guid("D57C7288-D4AD-4768-BE02-9D969532D960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
1359
|
+
interface IFileOpenDialog {
|
|
1360
|
+
[PreserveSig] int Show(IntPtr hwnd);
|
|
1361
|
+
void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
|
|
1362
|
+
void SetFileTypeIndex(uint iFileType);
|
|
1363
|
+
void GetFileTypeIndex(out uint piFileType);
|
|
1364
|
+
void Advise(IntPtr pfde, out uint pdwCookie);
|
|
1365
|
+
void Unadvise(uint dwCookie);
|
|
1366
|
+
void SetOptions(uint fos);
|
|
1367
|
+
void GetOptions(out uint pfos);
|
|
1368
|
+
void SetDefaultFolder(IntPtr psi);
|
|
1369
|
+
void SetFolder(IntPtr psi);
|
|
1370
|
+
void GetFolder(out IntPtr ppsi);
|
|
1371
|
+
void GetCurrentSelection(out IntPtr ppsi);
|
|
1372
|
+
void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
1373
|
+
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
|
|
1374
|
+
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
|
|
1375
|
+
void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
|
|
1376
|
+
void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
|
|
1377
|
+
void GetResult(out IntPtr ppsi);
|
|
1378
|
+
void AddPlace(IntPtr psi, int fdap);
|
|
1379
|
+
void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
|
|
1380
|
+
void Close(int hr);
|
|
1381
|
+
void SetClientGuid([In] ref Guid guid);
|
|
1382
|
+
void ClearClientData();
|
|
1383
|
+
void SetFilter(IntPtr pFilter);
|
|
1384
|
+
void GetResults(out IntPtr ppenum);
|
|
1385
|
+
void GetSelectedItems(out IntPtr ppsai);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
1389
|
+
interface IShellItem {
|
|
1390
|
+
void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);
|
|
1391
|
+
void GetParent(out IShellItem ppsi);
|
|
1392
|
+
void GetDisplayName(uint sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
|
1393
|
+
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
|
1394
|
+
void Compare(IShellItem psi, uint hint, out int piOrder);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
public static class FolderPicker {
|
|
1398
|
+
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
|
1399
|
+
static extern int SHCreateItemFromParsingName(string pszPath, IntPtr pbc, [In] ref Guid riid, out IntPtr ppv);
|
|
1400
|
+
|
|
1401
|
+
// FOS_PICKFOLDERS = 0x20, FOS_FORCEFILESYSTEM = 0x40
|
|
1402
|
+
const uint FOS_PICKFOLDERS = 0x00000020;
|
|
1403
|
+
const uint FOS_FORCEFILESYSTEM = 0x00000040;
|
|
1404
|
+
|
|
1405
|
+
public static string Pick(string title, string defaultPath) {
|
|
1406
|
+
var clsid = new Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7");
|
|
1407
|
+
var riid = new Guid("D57C7288-D4AD-4768-BE02-9D969532D960");
|
|
1408
|
+
var obj = Activator.CreateInstance(Type.GetTypeFromCLSID(clsid));
|
|
1409
|
+
var dialog = (IFileOpenDialog)obj;
|
|
1410
|
+
dialog.SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
|
|
1411
|
+
dialog.SetTitle(title);
|
|
1412
|
+
if (!string.IsNullOrEmpty(defaultPath)) {
|
|
1413
|
+
var riidShellItem = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE");
|
|
1414
|
+
IntPtr psi;
|
|
1415
|
+
if (SHCreateItemFromParsingName(defaultPath, IntPtr.Zero, ref riidShellItem, out psi) == 0) {
|
|
1416
|
+
dialog.SetFolder(psi);
|
|
1417
|
+
Marshal.Release(psi);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
int hr = dialog.Show(IntPtr.Zero);
|
|
1421
|
+
if (hr != 0) return null; // \u7528\u6237\u53D6\u6D88 (HRESULT_FROM_WIN32(ERROR_CANCELLED) = 0x800704C7)
|
|
1422
|
+
IntPtr ppsi;
|
|
1423
|
+
dialog.GetResult(out ppsi);
|
|
1424
|
+
var shellItem = (IShellItem)Marshal.GetTypedObjectForIUnknown(ppsi, typeof(IShellItem));
|
|
1425
|
+
string path;
|
|
1426
|
+
shellItem.GetDisplayName(0x80058000, out path); // SIGDN_FILESYSPATH
|
|
1427
|
+
Marshal.Release(ppsi);
|
|
1428
|
+
return path;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
'@
|
|
1432
|
+
Add-Type -TypeDefinition $code
|
|
1433
|
+
$result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
|
|
1434
|
+
if ($result) { Write-Output $result }
|
|
1435
|
+
`;
|
|
1436
|
+
return new Promise((resolve3, reject) => {
|
|
1437
|
+
const child = (0, import_node_child_process2.spawn)("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
|
|
1438
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1439
|
+
windowsHide: true
|
|
1440
|
+
});
|
|
1441
|
+
let stdout = "";
|
|
1442
|
+
let stderr = "";
|
|
1443
|
+
child.stdout?.on("data", (d) => {
|
|
1444
|
+
stdout += d.toString();
|
|
1445
|
+
});
|
|
1446
|
+
child.stderr?.on("data", (d) => {
|
|
1447
|
+
stderr += d.toString();
|
|
1448
|
+
});
|
|
1449
|
+
child.on("close", (code) => {
|
|
1450
|
+
if (code === 0) {
|
|
1451
|
+
const selected = stdout.trim();
|
|
1452
|
+
resolve3(selected || null);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
reject(new Error(`PowerShell \u6587\u4EF6\u5939\u9009\u62E9\u5668\u6267\u884C\u5931\u8D25: ${stderr.trim() || `\u9000\u51FA\u7801 ${code}`}`));
|
|
1456
|
+
});
|
|
1457
|
+
child.on("error", (err) => {
|
|
1458
|
+
reject(new Error(`\u5524\u8D77 PowerShell \u6587\u4EF6\u5939\u9009\u62E9\u5668\u5931\u8D25: ${err.message}`));
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
function pickDirectoryLinux(title, defaultPath) {
|
|
1463
|
+
const args = ["--file-selection", "--directory", `--title=${title}`];
|
|
1464
|
+
if (defaultPath) {
|
|
1465
|
+
args.push(`--filename=${defaultPath}`);
|
|
1466
|
+
}
|
|
1467
|
+
return new Promise((resolve3, reject) => {
|
|
1468
|
+
const child = (0, import_node_child_process2.spawn)("zenity", args, {
|
|
1469
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1470
|
+
});
|
|
1471
|
+
let stdout = "";
|
|
1472
|
+
let stderr = "";
|
|
1473
|
+
child.stdout?.on("data", (d) => {
|
|
1474
|
+
stdout += d.toString();
|
|
1475
|
+
});
|
|
1476
|
+
child.stderr?.on("data", (d) => {
|
|
1477
|
+
stderr += d.toString();
|
|
1478
|
+
});
|
|
1479
|
+
child.on("close", (code) => {
|
|
1480
|
+
if (code === 0) {
|
|
1481
|
+
const selected = stdout.trim();
|
|
1482
|
+
resolve3(selected || null);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
if (code === 1) {
|
|
1486
|
+
resolve3(null);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
reject(new Error(`zenity \u76EE\u5F55\u9009\u62E9\u5668\u5F02\u5E38\u9000\u51FA (\u9000\u51FA\u7801 ${code}): ${stderr.trim()}`));
|
|
1490
|
+
});
|
|
1491
|
+
child.on("error", (err) => {
|
|
1492
|
+
if (err?.code === "ENOENT") {
|
|
1493
|
+
reject(new Error("\u672A\u68C0\u6D4B\u5230 zenity \u76EE\u5F55\u9009\u62E9\u5668\uFF0C\u8BF7\u5148\u5728\u7CFB\u7EDF\u4E2D\u5B89\u88C5 zenity (\u4F8B\u5982: sudo apt install zenity)"));
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
reject(new Error(`\u5524\u8D77 zenity \u76EE\u5F55\u9009\u62E9\u5668\u5931\u8D25: ${err.message}`));
|
|
1497
|
+
});
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
var import_node_child_process2;
|
|
1501
|
+
var init_directory_picker = __esm({
|
|
1502
|
+
"src/bridge/directory-picker.ts"() {
|
|
1503
|
+
"use strict";
|
|
1504
|
+
import_node_child_process2 = require("child_process");
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
// src/bridge/pty-backend.ts
|
|
1509
|
+
function interactiveArgs(shell) {
|
|
1510
|
+
if (process.platform !== "win32") return ["-i"];
|
|
1511
|
+
const base = import_node_path3.default.basename(shell).toLowerCase();
|
|
1512
|
+
if (base === "powershell.exe" || base === "pwsh.exe") return ["-NoLogo"];
|
|
1513
|
+
if (base === "cmd.exe") return [];
|
|
1514
|
+
return ["-i"];
|
|
1515
|
+
}
|
|
1516
|
+
function ptyEnvironment() {
|
|
1517
|
+
const env = {};
|
|
1518
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1519
|
+
if (value !== void 0) env[key] = value;
|
|
1520
|
+
}
|
|
1521
|
+
env.TERM = "xterm-256color";
|
|
1522
|
+
return env;
|
|
1523
|
+
}
|
|
1524
|
+
function resolveSpawnFile(shell, pathEnv = process.env.PATH || "") {
|
|
1525
|
+
if (!shell || import_node_path3.default.isAbsolute(shell)) return shell;
|
|
1526
|
+
const names = process.platform === "win32" && !shell.toLowerCase().endsWith(".exe") ? [shell, `${shell}.exe`] : [shell];
|
|
1527
|
+
for (const dir of pathEnv.split(import_node_path3.default.delimiter)) {
|
|
1528
|
+
if (!dir) continue;
|
|
1529
|
+
for (const name of names) {
|
|
1530
|
+
const candidate = import_node_path3.default.join(dir, name);
|
|
1531
|
+
try {
|
|
1532
|
+
if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (process.platform === "win32") {
|
|
1538
|
+
const systemRoot = process.env.SystemRoot || "C:\\Windows";
|
|
1539
|
+
const fallbacks = [
|
|
1540
|
+
import_node_path3.default.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
1541
|
+
import_node_path3.default.join(systemRoot, "Sysnative", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
1542
|
+
];
|
|
1543
|
+
for (const candidate of fallbacks) {
|
|
1544
|
+
try {
|
|
1545
|
+
if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return shell;
|
|
1551
|
+
}
|
|
1552
|
+
function describeSpawnHelperChmodError(helper, code) {
|
|
1553
|
+
if (code !== "EPERM" && code !== "EACCES") return null;
|
|
1554
|
+
return `spawn-helper \u65E0\u6CD5\u8BBE\u7F6E\u4E3A\u53EF\u6267\u884C\uFF08${helper}\uFF09\u3002\u5F53\u524D\u7528\u6237\u6CA1\u6709\u6743\u9650\u4FEE\u6539\u8BE5\u6587\u4EF6\u3002\u8BF7\u7528\u5B89\u88C5\u65F6\u7684\u7528\u6237\u91CD\u65B0\u5B89\u88C5 @opentiny/webmcp-cli\uFF0C\u6216\u5BF9\u8BE5\u6587\u4EF6\u6267\u884C chmod 755\u3002`;
|
|
1555
|
+
}
|
|
1556
|
+
function loadNodePty() {
|
|
1557
|
+
try {
|
|
1558
|
+
return (0, import_node_module.createRequire)(import_meta2.url)("node-pty");
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1561
|
+
throw new Error(`\u4EA4\u4E92\u7EC8\u7AEF\u4E0D\u53EF\u7528\uFF1A\u672A\u80FD\u52A0\u8F7D node-pty\uFF08${detail}\uFF09\u3002\u5176\u4F59\u6865\u63A5\u547D\u4EE4\u4ECD\u53EF\u4F7F\u7528\u3002`);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function spawnHelperPath() {
|
|
1565
|
+
try {
|
|
1566
|
+
const pkgJson = (0, import_node_module.createRequire)(import_meta2.url).resolve("node-pty/package.json");
|
|
1567
|
+
return import_node_path3.default.join(import_node_path3.default.dirname(pkgJson), "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
|
|
1568
|
+
} catch {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function ensureSpawnHelper() {
|
|
1573
|
+
if (process.platform === "win32") return null;
|
|
1574
|
+
const helper = spawnHelperPath();
|
|
1575
|
+
if (!helper || !(0, import_node_fs3.existsSync)(helper)) return null;
|
|
1576
|
+
try {
|
|
1577
|
+
(0, import_node_fs3.chmodSync)(helper, 493);
|
|
1578
|
+
return null;
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
1581
|
+
return describeSpawnHelperChmodError(helper, code);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function signalName(code) {
|
|
1585
|
+
if (!code) return null;
|
|
1586
|
+
for (const [name, value] of Object.entries(import_node_os2.constants.signals)) {
|
|
1587
|
+
if (value === code) return name;
|
|
1588
|
+
}
|
|
1589
|
+
return String(code);
|
|
1590
|
+
}
|
|
1591
|
+
function descendantPids(rootPid) {
|
|
1592
|
+
const result = (0, import_node_child_process3.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf8", timeout: 3e3 });
|
|
1593
|
+
if (result.status !== 0 || !result.stdout) return [];
|
|
1594
|
+
const children = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const line of result.stdout.split("\n")) {
|
|
1596
|
+
const [pidText, ppidText] = line.trim().split(/\s+/);
|
|
1597
|
+
const pid = Number(pidText);
|
|
1598
|
+
const ppid = Number(ppidText);
|
|
1599
|
+
if (!pid || !ppid) continue;
|
|
1600
|
+
const list = children.get(ppid);
|
|
1601
|
+
if (list) list.push(pid);
|
|
1602
|
+
else children.set(ppid, [pid]);
|
|
1603
|
+
}
|
|
1604
|
+
const out = [];
|
|
1605
|
+
const walk = (pid) => {
|
|
1606
|
+
for (const child of children.get(pid) || []) {
|
|
1607
|
+
walk(child);
|
|
1608
|
+
out.push(child);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
walk(rootPid);
|
|
1612
|
+
return out;
|
|
1613
|
+
}
|
|
1614
|
+
function killPtySession(term) {
|
|
1615
|
+
const pid = term.pid;
|
|
1616
|
+
if (pid && pid > 0) {
|
|
1617
|
+
if (process.platform === "win32") {
|
|
1618
|
+
try {
|
|
1619
|
+
(0, import_node_child_process3.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 3e3 });
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
} else {
|
|
1623
|
+
for (const victim of [...descendantPids(pid), pid]) {
|
|
1624
|
+
try {
|
|
1625
|
+
process.kill(victim, "SIGKILL");
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
try {
|
|
1632
|
+
term.kill("SIGHUP");
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function spawnHostPty(options) {
|
|
1637
|
+
const pty = loadNodePty();
|
|
1638
|
+
const helperError = ensureSpawnHelper();
|
|
1639
|
+
const shell = resolveSpawnFile(options.shell || resolveShell());
|
|
1640
|
+
const pending = [];
|
|
1641
|
+
let dataCb = null;
|
|
1642
|
+
let exitCb = null;
|
|
1643
|
+
let pendingExit = null;
|
|
1644
|
+
let settled = false;
|
|
1645
|
+
let term = null;
|
|
1646
|
+
const emitData = (data) => {
|
|
1647
|
+
if (!data) return;
|
|
1648
|
+
if (dataCb) dataCb(data);
|
|
1649
|
+
else pending.push(data);
|
|
1650
|
+
};
|
|
1651
|
+
const finish = (info) => {
|
|
1652
|
+
if (settled) return;
|
|
1653
|
+
settled = true;
|
|
1654
|
+
if (exitCb) exitCb(info);
|
|
1655
|
+
else pendingExit = info;
|
|
1656
|
+
};
|
|
1657
|
+
try {
|
|
1658
|
+
term = pty.spawn(shell, interactiveArgs(shell), {
|
|
1659
|
+
name: "xterm-256color",
|
|
1660
|
+
cols: options.cols,
|
|
1661
|
+
rows: options.rows,
|
|
1662
|
+
cwd: options.cwd,
|
|
1663
|
+
env: ptyEnvironment()
|
|
1664
|
+
});
|
|
1665
|
+
term.onData((data) => emitData(data));
|
|
1666
|
+
term.onExit((event) => {
|
|
1667
|
+
finish({ exitCode: event.exitCode, signal: signalName(event.signal) });
|
|
1668
|
+
});
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1671
|
+
const detail = helperError ? `${helperError} ${message}` : message;
|
|
1672
|
+
setImmediate(() => {
|
|
1673
|
+
emitData(`\r
|
|
1674
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${detail}\r
|
|
1675
|
+
`);
|
|
1676
|
+
finish({ exitCode: null, signal: null });
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
write(data) {
|
|
1681
|
+
try {
|
|
1682
|
+
term?.write(data);
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
},
|
|
1686
|
+
resize(cols, rows) {
|
|
1687
|
+
try {
|
|
1688
|
+
term?.resize(cols, rows);
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
kill() {
|
|
1693
|
+
if (term) killPtySession(term);
|
|
1694
|
+
},
|
|
1695
|
+
pause() {
|
|
1696
|
+
try {
|
|
1697
|
+
term?.pause();
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
resume() {
|
|
1702
|
+
try {
|
|
1703
|
+
term?.resume();
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1707
|
+
onData(cb) {
|
|
1708
|
+
dataCb = cb;
|
|
1709
|
+
for (const chunk of pending) cb(chunk);
|
|
1710
|
+
pending.length = 0;
|
|
1711
|
+
},
|
|
1712
|
+
onExit(cb) {
|
|
1713
|
+
exitCb = cb;
|
|
1714
|
+
if (pendingExit) cb(pendingExit);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
var import_node_child_process3, import_node_fs3, import_node_module, import_node_path3, import_node_os2, import_meta2;
|
|
1719
|
+
var init_pty_backend = __esm({
|
|
1720
|
+
"src/bridge/pty-backend.ts"() {
|
|
1721
|
+
"use strict";
|
|
1722
|
+
import_node_child_process3 = require("child_process");
|
|
1723
|
+
import_node_fs3 = require("fs");
|
|
1724
|
+
import_node_module = require("module");
|
|
1725
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
1726
|
+
import_node_os2 = require("os");
|
|
1727
|
+
init_bash_executor();
|
|
1728
|
+
import_meta2 = {};
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
// src/bridge/terminal-session.ts
|
|
1733
|
+
function applyTerminalBackpressure(bufferedAmount, flow) {
|
|
1734
|
+
if (bufferedAmount > TERMINAL_OUTPUT_HIGH_WATER) flow.pauseOutput();
|
|
1735
|
+
else if (bufferedAmount <= TERMINAL_OUTPUT_HIGH_WATER / 2) flow.resumeOutput();
|
|
1736
|
+
}
|
|
1737
|
+
function clampDimension(value, fallback) {
|
|
1738
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1739
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1740
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1741
|
+
}
|
|
1742
|
+
var import_node_crypto, OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TERMINAL_OUTPUT_HIGH_WATER, TerminalSessionManager;
|
|
1743
|
+
var init_terminal_session = __esm({
|
|
1744
|
+
"src/bridge/terminal-session.ts"() {
|
|
1745
|
+
"use strict";
|
|
1746
|
+
import_node_crypto = require("crypto");
|
|
1747
|
+
init_workspace_manager();
|
|
1748
|
+
init_pty_backend();
|
|
1749
|
+
init_protocol();
|
|
1750
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1751
|
+
MAX_INPUT = 64 * 1024;
|
|
1752
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1753
|
+
TERMINAL_OUTPUT_HIGH_WATER = 1024 * 1024;
|
|
1754
|
+
TerminalSessionManager = class {
|
|
1755
|
+
session = null;
|
|
1756
|
+
buffer = "";
|
|
1757
|
+
timer = null;
|
|
1758
|
+
send;
|
|
1759
|
+
spawnPty;
|
|
1760
|
+
coalesceMs;
|
|
1761
|
+
constructor(options) {
|
|
1762
|
+
this.send = options.send;
|
|
1763
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1764
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1765
|
+
}
|
|
1766
|
+
open(params = {}) {
|
|
1767
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1768
|
+
const cols = clampDimension(params.cols, 80);
|
|
1769
|
+
const rows = clampDimension(params.rows, 24);
|
|
1770
|
+
this.closeActive();
|
|
1771
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
1772
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1773
|
+
this.session = { id, pty, cwd };
|
|
1774
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1775
|
+
pty.onExit((info) => {
|
|
1776
|
+
if (this.session?.id !== id) return;
|
|
1777
|
+
this.flush(id);
|
|
1778
|
+
this.session = null;
|
|
1779
|
+
this.send({
|
|
1780
|
+
jsonrpc: "2.0",
|
|
1781
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1782
|
+
params: {
|
|
1783
|
+
sessionId: id,
|
|
1784
|
+
exitCode: info.exitCode,
|
|
1785
|
+
signal: info.signal
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
return { sessionId: id, cwd };
|
|
1790
|
+
}
|
|
1791
|
+
input(params) {
|
|
1792
|
+
const session = this.requireSession(params.sessionId);
|
|
1793
|
+
if (typeof params.data !== "string") {
|
|
1794
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1795
|
+
}
|
|
1796
|
+
if (params.data.length > MAX_INPUT) {
|
|
1797
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1798
|
+
}
|
|
1799
|
+
session.pty.write(params.data);
|
|
1800
|
+
return { ok: true };
|
|
1801
|
+
}
|
|
1802
|
+
resize(params) {
|
|
1803
|
+
const session = this.requireSession(params.sessionId);
|
|
1804
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1805
|
+
return { ok: true };
|
|
1806
|
+
}
|
|
1807
|
+
close(params = {}) {
|
|
1808
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1809
|
+
return { ok: true };
|
|
1810
|
+
}
|
|
1811
|
+
this.closeActive();
|
|
1812
|
+
return { ok: true };
|
|
1813
|
+
}
|
|
1814
|
+
disposeAll() {
|
|
1815
|
+
this.closeActive();
|
|
1816
|
+
}
|
|
1817
|
+
pauseOutput() {
|
|
1818
|
+
try {
|
|
1819
|
+
this.session?.pty.pause();
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
resumeOutput() {
|
|
1824
|
+
try {
|
|
1825
|
+
this.session?.pty.resume();
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
requireSession(sessionId) {
|
|
1830
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1831
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1832
|
+
}
|
|
1833
|
+
return this.session;
|
|
1834
|
+
}
|
|
1835
|
+
closeActive() {
|
|
1836
|
+
const current = this.session;
|
|
1837
|
+
this.session = null;
|
|
1838
|
+
this.buffer = "";
|
|
1839
|
+
if (this.timer) {
|
|
1840
|
+
clearTimeout(this.timer);
|
|
1841
|
+
this.timer = null;
|
|
1842
|
+
}
|
|
1843
|
+
current?.pty.kill();
|
|
1844
|
+
}
|
|
1845
|
+
pushOutput(sessionId, chunk) {
|
|
1846
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1847
|
+
this.buffer += chunk;
|
|
1848
|
+
this.flushFullChunks(sessionId);
|
|
1849
|
+
if (!this.buffer) return;
|
|
1850
|
+
if (this.coalesceMs <= 0) {
|
|
1851
|
+
this.flush(sessionId);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (!this.timer) {
|
|
1855
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
flushFullChunks(sessionId) {
|
|
1859
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1860
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1861
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1862
|
+
this.emitOutput(sessionId, data);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
flush(sessionId) {
|
|
1866
|
+
if (this.timer) {
|
|
1867
|
+
clearTimeout(this.timer);
|
|
1868
|
+
this.timer = null;
|
|
1869
|
+
}
|
|
1870
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1871
|
+
this.buffer = "";
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const data = this.buffer;
|
|
1875
|
+
this.buffer = "";
|
|
1876
|
+
this.emitOutput(sessionId, data);
|
|
1877
|
+
}
|
|
1878
|
+
emitOutput(sessionId, data) {
|
|
1879
|
+
this.send({
|
|
1880
|
+
jsonrpc: "2.0",
|
|
1881
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1882
|
+
params: { sessionId, data }
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
// src/bridge/bridge-client.ts
|
|
1890
|
+
var bridge_client_exports = {};
|
|
1891
|
+
__export(bridge_client_exports, {
|
|
1892
|
+
BRIDGE_ERROR_CODE: () => BRIDGE_ERROR_CODE,
|
|
1893
|
+
ExtensionBridgeClient: () => ExtensionBridgeClient,
|
|
1894
|
+
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
1895
|
+
computeHmac: () => computeHmac,
|
|
1896
|
+
deriveSharedSecret: () => deriveSharedSecret,
|
|
1897
|
+
getKillPortHint: () => getKillPortHint,
|
|
1898
|
+
resolveOrCreateAuthToken: () => resolveOrCreateAuthToken,
|
|
1899
|
+
verifyHmacConstantTime: () => verifyHmacConstantTime
|
|
1900
|
+
});
|
|
1901
|
+
function isJsonRpcId(id) {
|
|
1902
|
+
if (id === null || typeof id === "string") return true;
|
|
1903
|
+
return typeof id === "number" && Number.isFinite(id);
|
|
1904
|
+
}
|
|
1905
|
+
function resolveOrCreateAuthToken(explicitToken) {
|
|
1906
|
+
if (explicitToken && explicitToken.trim()) {
|
|
1907
|
+
const token = explicitToken.trim();
|
|
1908
|
+
try {
|
|
1909
|
+
if (!import_node_fs4.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1910
|
+
import_node_fs4.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
1911
|
+
}
|
|
1912
|
+
import_node_fs4.default.writeFileSync(TOKEN_FILE_PATH, token, { mode: 384, encoding: "utf8" });
|
|
1913
|
+
} catch {
|
|
1914
|
+
}
|
|
1915
|
+
return token;
|
|
1916
|
+
}
|
|
1917
|
+
if (process.env.ROBOT_WXT_BRIDGE_TOKEN) {
|
|
1918
|
+
return process.env.ROBOT_WXT_BRIDGE_TOKEN.trim();
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
if (import_node_fs4.default.existsSync(TOKEN_FILE_PATH)) {
|
|
1922
|
+
const existing = import_node_fs4.default.readFileSync(TOKEN_FILE_PATH, "utf8").trim();
|
|
1923
|
+
if (existing) return existing;
|
|
1924
|
+
}
|
|
1925
|
+
} catch {
|
|
1926
|
+
}
|
|
1927
|
+
const newToken = (0, import_node_crypto2.randomBytes)(32).toString("hex");
|
|
1928
|
+
try {
|
|
1929
|
+
if (!import_node_fs4.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1930
|
+
import_node_fs4.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
1931
|
+
}
|
|
1932
|
+
import_node_fs4.default.writeFileSync(TOKEN_FILE_PATH, newToken, { mode: 384, encoding: "utf8" });
|
|
1933
|
+
} catch {
|
|
1934
|
+
}
|
|
1935
|
+
return newToken;
|
|
1936
|
+
}
|
|
1937
|
+
function parseMajorVersion(v) {
|
|
1938
|
+
return parseInt(v.split(".")[0] ?? "0", 10);
|
|
1939
|
+
}
|
|
1940
|
+
function deriveSharedSecret(tokenOrExtensionId, hmacSalt) {
|
|
1941
|
+
return (0, import_node_crypto2.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
|
|
1942
|
+
}
|
|
1943
|
+
function computeHmac(secret, challenge) {
|
|
1944
|
+
return (0, import_node_crypto2.createHmac)("sha256", secret).update(challenge).digest("hex");
|
|
1945
|
+
}
|
|
1946
|
+
function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
1947
|
+
if (!actualHex || typeof actualHex !== "string") return false;
|
|
1948
|
+
try {
|
|
1949
|
+
const actualBuf = Buffer.from(actualHex, "hex");
|
|
1950
|
+
const expectedBuf = Buffer.from(expectedHex, "hex");
|
|
1951
|
+
if (actualBuf.length !== expectedBuf.length) return false;
|
|
1952
|
+
return (0, import_node_crypto2.timingSafeEqual)(actualBuf, expectedBuf);
|
|
1953
|
+
} catch {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function getKillPortHint(port) {
|
|
1958
|
+
if (process.platform === "win32") {
|
|
1959
|
+
return `netstat -ano | findstr :${port} \uFF08\u627E\u5230 PID \u540E\u6267\u884C: taskkill /pid <PID> /T /F\uFF09`;
|
|
1960
|
+
}
|
|
1961
|
+
return `lsof -ti:${port} | xargs kill -9`;
|
|
1962
|
+
}
|
|
1963
|
+
var import_ws, import_node_events, import_node_crypto2, import_node_fs4, import_node_path4, import_node_os3, WS_PORT, REQUEST_TIMEOUT_MS, HANDSHAKE_TIMEOUT_MS, WS_OPEN, TOKEN_FILE_DIR, TOKEN_FILE_PATH, ExtensionBridgeClient;
|
|
1964
|
+
var init_bridge_client = __esm({
|
|
1965
|
+
"src/bridge/bridge-client.ts"() {
|
|
1966
|
+
"use strict";
|
|
1967
|
+
import_ws = require("ws");
|
|
1968
|
+
import_node_events = require("events");
|
|
1969
|
+
import_node_crypto2 = require("crypto");
|
|
1970
|
+
import_node_fs4 = __toESM(require("fs"), 1);
|
|
1971
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
1972
|
+
import_node_os3 = __toESM(require("os"), 1);
|
|
1973
|
+
init_protocol();
|
|
1974
|
+
init_bash_executor();
|
|
1975
|
+
init_directory_picker();
|
|
1976
|
+
init_terminal_session();
|
|
1977
|
+
WS_PORT = DEFAULT_WS_PORT;
|
|
1978
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
1979
|
+
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
1980
|
+
WS_OPEN = 1;
|
|
1981
|
+
TOKEN_FILE_DIR = import_node_path4.default.join(import_node_os3.default.homedir(), ".robot-wxt");
|
|
1982
|
+
TOKEN_FILE_PATH = import_node_path4.default.join(TOKEN_FILE_DIR, "bridge-token");
|
|
1983
|
+
ExtensionBridgeClient = class _ExtensionBridgeClient extends import_node_events.EventEmitter {
|
|
1984
|
+
constructor(port = WS_PORT, authToken) {
|
|
1985
|
+
super();
|
|
1986
|
+
this.port = port;
|
|
1987
|
+
this.explicitAuthToken = authToken;
|
|
1988
|
+
this.authToken = authToken ?? resolveOrCreateAuthToken();
|
|
1989
|
+
}
|
|
1990
|
+
port;
|
|
1991
|
+
wss = null;
|
|
1992
|
+
extensionSocket = null;
|
|
1993
|
+
clientSocket = null;
|
|
1994
|
+
forwardedRequests = /* @__PURE__ */ new Map();
|
|
1995
|
+
pending = /* @__PURE__ */ new Map();
|
|
1996
|
+
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1997
|
+
destroyed = false;
|
|
1998
|
+
terminalSessions = null;
|
|
1999
|
+
idCounter = 0;
|
|
2000
|
+
isClientMode = false;
|
|
2001
|
+
explicitAuthToken;
|
|
2002
|
+
authToken;
|
|
2003
|
+
/** 获取当前服务端使用的认证 Token */
|
|
2004
|
+
get currentAuthToken() {
|
|
2005
|
+
return this.authToken;
|
|
2006
|
+
}
|
|
2007
|
+
/** 获取当前激活的 Socket */
|
|
2008
|
+
_getActiveSocket() {
|
|
2009
|
+
if (this.isClientMode && this.clientSocket) {
|
|
2010
|
+
return this.clientSocket.readyState === WS_OPEN ? this.clientSocket : null;
|
|
2011
|
+
}
|
|
2012
|
+
if (this.extensionSocket && this.extensionSocket.readyState === WS_OPEN) {
|
|
2013
|
+
return this.extensionSocket;
|
|
2014
|
+
}
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
/** 是否已有就绪的扩展连接(或作为 Client 已连接到 Server) */
|
|
2018
|
+
get isConnected() {
|
|
2019
|
+
return this._getActiveSocket() != null;
|
|
2020
|
+
}
|
|
2021
|
+
/** 等待扩展连接或作为客户端连入服务就绪 */
|
|
2022
|
+
async waitForReady(timeoutMs = 5e3) {
|
|
2023
|
+
if (this.isConnected) return true;
|
|
2024
|
+
return new Promise((resolve3) => {
|
|
2025
|
+
const timer = setTimeout(() => {
|
|
2026
|
+
cleanup();
|
|
2027
|
+
resolve3(this.isConnected);
|
|
2028
|
+
}, timeoutMs);
|
|
2029
|
+
const cleanup = () => {
|
|
2030
|
+
clearTimeout(timer);
|
|
2031
|
+
this.off("connected", onConnected);
|
|
2032
|
+
};
|
|
2033
|
+
const onConnected = () => {
|
|
2034
|
+
cleanup();
|
|
2035
|
+
resolve3(true);
|
|
2036
|
+
};
|
|
2037
|
+
this.on("connected", onConnected);
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* 静态探测目标端口的桥接服务状态
|
|
2042
|
+
*/
|
|
2043
|
+
static async probe(port = WS_PORT, authToken, timeoutMs = 800) {
|
|
2044
|
+
const client = new _ExtensionBridgeClient(port, authToken);
|
|
2045
|
+
try {
|
|
2046
|
+
const res = await client._probeExistingServer(timeoutMs);
|
|
2047
|
+
if (client.clientSocket) {
|
|
2048
|
+
try {
|
|
2049
|
+
client.clientSocket.close(1e3, "Probe finished");
|
|
2050
|
+
} catch {
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
if (res.success) {
|
|
2054
|
+
return { running: true, authed: true };
|
|
2055
|
+
}
|
|
2056
|
+
if (res.error) {
|
|
2057
|
+
const isAuthError = res.error.message.includes("HMAC") || res.error.message.includes("\u8BA4\u8BC1\u5931\u8D25") || res.error.message.includes("Unauthorized");
|
|
2058
|
+
if (isAuthError) {
|
|
2059
|
+
return { running: true, authed: false, error: res.error.message };
|
|
2060
|
+
}
|
|
2061
|
+
return { running: false, authed: false, error: res.error.message };
|
|
2062
|
+
}
|
|
2063
|
+
return { running: false, authed: false };
|
|
2064
|
+
} finally {
|
|
2065
|
+
client.destroy();
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* 自动探测模式:
|
|
2070
|
+
* 优先尝试连接已存在的 18999 服务;连接不上时退回启动本机的 WebSocketServer。
|
|
2071
|
+
*/
|
|
2072
|
+
async startAuto(probeTimeoutMs = 800) {
|
|
2073
|
+
if (this.destroyed) return;
|
|
2074
|
+
const probe = await this._probeExistingServer(probeTimeoutMs);
|
|
2075
|
+
if (probe.success) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (probe.error) {
|
|
2079
|
+
throw probe.error;
|
|
2080
|
+
}
|
|
2081
|
+
this.connect();
|
|
2082
|
+
}
|
|
2083
|
+
async _probeExistingServer(timeoutMs) {
|
|
2084
|
+
return new Promise((resolve3) => {
|
|
2085
|
+
let settled = false;
|
|
2086
|
+
let connectionEstablished = false;
|
|
2087
|
+
let authFailedReason = null;
|
|
2088
|
+
const ws = new import_ws.WebSocket(`ws://127.0.0.1:${this.port}`);
|
|
2089
|
+
const timer = setTimeout(() => {
|
|
2090
|
+
if (!settled) {
|
|
2091
|
+
settled = true;
|
|
2092
|
+
try {
|
|
2093
|
+
ws.terminate();
|
|
2094
|
+
} catch {
|
|
2095
|
+
}
|
|
2096
|
+
if (authFailedReason) {
|
|
2097
|
+
resolve3({
|
|
2098
|
+
success: false,
|
|
2099
|
+
error: new Error(
|
|
2100
|
+
`\u672C\u5730\u6865\u63A5\u670D\u52A1\uFF08127.0.0.1:${this.port}\uFF09\u6B63\u5728\u8FD0\u884C\uFF0C\u4F46 HMAC \u8BA4\u8BC1\u5931\u8D25\uFF1A${authFailedReason}\u3002
|
|
2101
|
+
\u8BF7\u786E\u8BA4\u5F53\u524D CLI \u4F7F\u7528\u7684 Token \u4E0E\u8BE5\u670D\u52A1\u4E00\u81F4\uFF0C\u6216\u7EC8\u6B62\u5360\u7528\u8FDB\u7A0B\uFF08\u53EF\u6267\u884C: ${getKillPortHint(this.port)}\uFF09\u3002`
|
|
2102
|
+
)
|
|
2103
|
+
});
|
|
2104
|
+
} else if (connectionEstablished) {
|
|
2105
|
+
resolve3({
|
|
2106
|
+
success: false,
|
|
2107
|
+
error: new Error(
|
|
2108
|
+
`\u672C\u5730 127.0.0.1:${this.port} \u5DF2\u88AB\u5360\u7528\u4E14\u65E0\u54CD\u5E94\uFF08\u53EF\u80FD\u4E0D\u662F\u6709\u6548\u6865\u63A5\u670D\u52A1\uFF09\uFF0C\u8BF7\u5148\u7EC8\u6B62\u5360\u7528\u8FDB\u7A0B\uFF08\u53EF\u6267\u884C: ${getKillPortHint(this.port)}\uFF09\u3002`
|
|
2109
|
+
)
|
|
2110
|
+
});
|
|
2111
|
+
} else {
|
|
2112
|
+
resolve3({ success: false });
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}, timeoutMs);
|
|
2116
|
+
ws.on("open", () => {
|
|
2117
|
+
connectionEstablished = true;
|
|
2118
|
+
});
|
|
2119
|
+
ws.on("close", (code, reason) => {
|
|
2120
|
+
const reasonStr = reason ? reason.toString() : "";
|
|
2121
|
+
if (code === WS_CLOSE_CODE.UNAUTHORIZED || reasonStr.includes("Unauthorized") || reasonStr.includes("HMAC")) {
|
|
2122
|
+
authFailedReason = reasonStr || "Token \u8BA4\u8BC1\u88AB\u62D2\u7EDD (HMAC verification failed)";
|
|
2123
|
+
}
|
|
2124
|
+
if (!settled && authFailedReason) {
|
|
2125
|
+
settled = true;
|
|
2126
|
+
clearTimeout(timer);
|
|
2127
|
+
resolve3({
|
|
2128
|
+
success: false,
|
|
2129
|
+
error: new Error(
|
|
2130
|
+
`\u672C\u5730\u6865\u63A5\u670D\u52A1\uFF08127.0.0.1:${this.port}\uFF09\u6B63\u5728\u8FD0\u884C\uFF0C\u4F46 HMAC \u8BA4\u8BC1\u5931\u8D25\uFF1A${authFailedReason}\u3002
|
|
2131
|
+
\u8BF7\u786E\u8BA4\u5F53\u524D CLI \u4F7F\u7528\u7684 Token \u4E0E\u8BE5\u670D\u52A1\u4E00\u81F4\uFF0C\u6216\u7EC8\u6B62\u5360\u7528\u8FDB\u7A0B\uFF08\u53EF\u6267\u884C: ${getKillPortHint(this.port)}\uFF09\u3002`
|
|
2132
|
+
)
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
ws.on("message", (data) => {
|
|
2137
|
+
try {
|
|
2138
|
+
const msg = JSON.parse(data.toString());
|
|
2139
|
+
if (msg.type === "BRIDGE_CHALLENGE" && msg.challenge && msg.hmacSalt) {
|
|
2140
|
+
connectionEstablished = true;
|
|
2141
|
+
const secret = deriveSharedSecret(this.authToken, msg.hmacSalt);
|
|
2142
|
+
const hmac = computeHmac(secret, msg.challenge);
|
|
2143
|
+
ws.send(
|
|
2144
|
+
JSON.stringify({
|
|
2145
|
+
type: "BRIDGE_AUTH",
|
|
2146
|
+
clientType: "cli-client",
|
|
2147
|
+
challenge: msg.challenge,
|
|
2148
|
+
hmac
|
|
2149
|
+
})
|
|
2150
|
+
);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (msg.type === "BRIDGE_AUTH_OK" && !settled) {
|
|
2154
|
+
settled = true;
|
|
2155
|
+
clearTimeout(timer);
|
|
2156
|
+
this.isClientMode = true;
|
|
2157
|
+
this.clientSocket = ws;
|
|
2158
|
+
this._bindClientSocket(ws);
|
|
2159
|
+
this.emit("connected");
|
|
2160
|
+
resolve3({ success: true });
|
|
2161
|
+
}
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
ws.once("error", (err) => {
|
|
2166
|
+
if (!settled) {
|
|
2167
|
+
settled = true;
|
|
2168
|
+
clearTimeout(timer);
|
|
2169
|
+
try {
|
|
2170
|
+
ws.terminate();
|
|
2171
|
+
} catch {
|
|
2172
|
+
}
|
|
2173
|
+
if (err?.code === "ECONNREFUSED" || err?.message?.includes("ECONNREFUSED")) {
|
|
2174
|
+
resolve3({ success: false });
|
|
2175
|
+
} else {
|
|
2176
|
+
resolve3({
|
|
2177
|
+
success: false,
|
|
2178
|
+
error: new Error(`\u8FDE\u63A5\u672C\u5730 127.0.0.1:${this.port} \u5931\u8D25: ${err?.message || err}`)
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
_bindClientSocket(ws) {
|
|
2186
|
+
ws.on("message", (data) => {
|
|
2187
|
+
let msg;
|
|
2188
|
+
try {
|
|
2189
|
+
msg = JSON.parse(data.toString());
|
|
2190
|
+
} catch {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (msg.type === "BRIDGE_CHALLENGE" || msg.type === "BRIDGE_AUTH_OK") {
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (msg.method === "notification/sub_agent_progress") {
|
|
2197
|
+
this.emit("progress", msg.params);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const pending = this.pending.get(String(msg.id));
|
|
2201
|
+
if (!pending) return;
|
|
2202
|
+
this.pending.delete(String(msg.id));
|
|
2203
|
+
clearTimeout(pending.timer);
|
|
2204
|
+
if (msg.error) {
|
|
2205
|
+
pending.reject(new Error(`[${msg.error.code}] ${msg.error.message}`));
|
|
2206
|
+
} else {
|
|
2207
|
+
pending.resolve(msg.result);
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
ws.on("close", () => {
|
|
2211
|
+
this.clientSocket = null;
|
|
2212
|
+
this.emit("disconnected");
|
|
2213
|
+
this._rejectAllPending("\u4E0E\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
/** 启动 WebSocket 服务端,等待 Chrome 扩展(Offscreen Document)连入 */
|
|
2217
|
+
connect() {
|
|
2218
|
+
if (this.destroyed) return;
|
|
2219
|
+
if (this.wss) return;
|
|
2220
|
+
try {
|
|
2221
|
+
this.wss = new import_ws.WebSocketServer({
|
|
2222
|
+
port: this.port,
|
|
2223
|
+
host: "127.0.0.1",
|
|
2224
|
+
verifyClient: (info, callback) => {
|
|
2225
|
+
const origin = info.origin || info.req.headers.origin;
|
|
2226
|
+
if (origin) {
|
|
2227
|
+
const isAllowedExtension = origin.startsWith("chrome-extension://") || origin.startsWith("moz-extension://") || origin.startsWith("extension://");
|
|
2228
|
+
if (!isAllowedExtension) {
|
|
2229
|
+
callback(false, 403, "Forbidden origin: only browser extensions are allowed");
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
callback(true);
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
this.emit("error", err);
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
this.wss.on("listening", () => {
|
|
2241
|
+
this.emit("listening");
|
|
2242
|
+
});
|
|
2243
|
+
this.wss.on("error", (err) => {
|
|
2244
|
+
if (err?.code === "EADDRINUSE") {
|
|
2245
|
+
process.stderr.write(
|
|
2246
|
+
`[webmcp-cli] \u26A0\uFE0F \u7AEF\u53E3 ${this.port} \u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u5148\u7EC8\u6B62\u5360\u7528\u8BE5\u7AEF\u53E3\u7684\u65E7\u8FDB\u7A0B\uFF08\u53EF\u6267\u884C: ${getKillPortHint(this.port)}\uFF09
|
|
2247
|
+
`
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
this.emit("error", err);
|
|
2251
|
+
});
|
|
2252
|
+
this.wss.on("connection", (socket) => {
|
|
2253
|
+
const challenge = (0, import_node_crypto2.randomUUID)();
|
|
2254
|
+
const hmacSalt = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
2255
|
+
const authState = {
|
|
2256
|
+
authenticated: false,
|
|
2257
|
+
challenge,
|
|
2258
|
+
hmacSalt,
|
|
2259
|
+
timer: setTimeout(() => {
|
|
2260
|
+
if (!authState.authenticated) {
|
|
2261
|
+
try {
|
|
2262
|
+
socket.close(WS_CLOSE_CODE.UNAUTHORIZED, "Handshake timeout");
|
|
2263
|
+
} catch {
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}, HANDSHAKE_TIMEOUT_MS)
|
|
2267
|
+
};
|
|
2268
|
+
this.socketAuthStates.set(socket, authState);
|
|
2269
|
+
try {
|
|
2270
|
+
socket.send(
|
|
2271
|
+
JSON.stringify({
|
|
2272
|
+
type: "BRIDGE_CHALLENGE",
|
|
2273
|
+
challenge,
|
|
2274
|
+
hmacSalt,
|
|
2275
|
+
protocolVersion: PROTOCOL_VERSION
|
|
2276
|
+
})
|
|
2277
|
+
);
|
|
2278
|
+
} catch {
|
|
2279
|
+
}
|
|
2280
|
+
socket.on("message", async (data) => {
|
|
2281
|
+
await this.handleSocketMessage(socket, data);
|
|
2282
|
+
});
|
|
2283
|
+
socket.on("close", () => {
|
|
2284
|
+
const auth = this.socketAuthStates.get(socket);
|
|
2285
|
+
if (auth?.timer) clearTimeout(auth.timer);
|
|
2286
|
+
this.socketAuthStates.delete(socket);
|
|
2287
|
+
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
2288
|
+
if (fwdInfo.socket === socket) {
|
|
2289
|
+
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
2290
|
+
this.forwardedRequests.delete(reqId);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (socket === this.extensionSocket) {
|
|
2294
|
+
this.extensionSocket = null;
|
|
2295
|
+
this.terminalSessions?.disposeAll();
|
|
2296
|
+
this.emit("disconnected");
|
|
2297
|
+
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
2298
|
+
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
2299
|
+
try {
|
|
2300
|
+
if (fwdInfo.socket.readyState === WS_OPEN) {
|
|
2301
|
+
fwdInfo.socket.send(
|
|
2302
|
+
JSON.stringify({
|
|
2303
|
+
jsonrpc: "2.0",
|
|
2304
|
+
id: fwdInfo.originalId,
|
|
2305
|
+
error: {
|
|
2306
|
+
code: BRIDGE_ERROR_CODE.EXTENSION_NOT_CONNECTED,
|
|
2307
|
+
message: "Chrome \u6269\u5C55\u8FDE\u63A5\u5DF2\u65AD\u5F00"
|
|
2308
|
+
}
|
|
2309
|
+
})
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
} catch {
|
|
2313
|
+
}
|
|
2314
|
+
this.forwardedRequests.delete(reqId);
|
|
2315
|
+
}
|
|
2316
|
+
this._rejectAllPending("WebSocket \u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
socket.on("error", () => {
|
|
2320
|
+
});
|
|
2321
|
+
});
|
|
2322
|
+
this.wss.on("error", (err) => {
|
|
2323
|
+
this.emit("error", err);
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
async handleSocketMessage(socket, data) {
|
|
2327
|
+
let msg;
|
|
2328
|
+
try {
|
|
2329
|
+
msg = JSON.parse(data.toString());
|
|
2330
|
+
} catch {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
const currentAuth = this.socketAuthStates.get(socket);
|
|
2334
|
+
if (!currentAuth || !currentAuth.authenticated) {
|
|
2335
|
+
await this.handleHandshake(socket, currentAuth, msg);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (msg.type === "KEEP_ALIVE" || msg.type === "WS_BRIDGE_KEEP_ALIVE") {
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
if (msg.method && msg.id) {
|
|
2342
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2343
|
+
this.sendJsonRpcResponse(socket, {
|
|
2344
|
+
id: msg.id,
|
|
2345
|
+
error: {
|
|
2346
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2347
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (socket === this.extensionSocket) {
|
|
2353
|
+
await this.handleExtensionSystemRpc(socket, msg);
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
await this.handleForwardRpc(socket, msg);
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
if (msg.method && !msg.id) {
|
|
2360
|
+
this.handleProgressNotification(msg);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
if (msg.id) {
|
|
2364
|
+
this.handleIncomingResponse(msg);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
verifyHandshakeHmac(currentAuth, hmac) {
|
|
2368
|
+
if (!currentAuth.hmacSalt || !hmac) return false;
|
|
2369
|
+
const secret = deriveSharedSecret(this.authToken, currentAuth.hmacSalt);
|
|
2370
|
+
const expectedHmac = computeHmac(secret, currentAuth.challenge);
|
|
2371
|
+
if (verifyHmacConstantTime(hmac, expectedHmac)) {
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
if (!this.explicitAuthToken) {
|
|
2375
|
+
const diskToken = resolveOrCreateAuthToken();
|
|
2376
|
+
if (diskToken && diskToken !== this.authToken) {
|
|
2377
|
+
const diskSecret = deriveSharedSecret(diskToken, currentAuth.hmacSalt);
|
|
2378
|
+
const diskExpectedHmac = computeHmac(diskSecret, currentAuth.challenge);
|
|
2379
|
+
if (verifyHmacConstantTime(hmac, diskExpectedHmac)) {
|
|
2380
|
+
this.authToken = diskToken;
|
|
2381
|
+
return true;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
async handleHandshake(socket, currentAuth, msg) {
|
|
2388
|
+
if (!currentAuth || msg.type !== "BRIDGE_AUTH" || msg.challenge !== currentAuth.challenge) {
|
|
2389
|
+
try {
|
|
2390
|
+
socket.close(WS_CLOSE_CODE.UNAUTHORIZED, "Unauthorized: Handshake failed");
|
|
2391
|
+
} catch {
|
|
2392
|
+
}
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
const clientVersion = typeof msg.protocolVersion === "string" ? msg.protocolVersion : PROTOCOL_VERSION;
|
|
2396
|
+
if (parseMajorVersion(clientVersion) !== parseMajorVersion(PROTOCOL_VERSION)) {
|
|
2397
|
+
try {
|
|
2398
|
+
socket.close(
|
|
2399
|
+
WS_CLOSE_CODE.PROTOCOL_MISMATCH,
|
|
2400
|
+
`Protocol version mismatch: server=${PROTOCOL_VERSION}, client=${clientVersion}`
|
|
2401
|
+
);
|
|
2402
|
+
} catch {
|
|
2403
|
+
}
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const verified = this.verifyHandshakeHmac(currentAuth, msg.hmac);
|
|
2407
|
+
if (!verified) {
|
|
2408
|
+
const tokenFingerprint = this.authToken ? `${this.authToken.slice(0, 6)}...` : "none";
|
|
2409
|
+
process.stderr.write(
|
|
2410
|
+
`[webmcp-cli] HMAC \u9A8C\u8BC1\u5931\u8D25\uFF0C\u62D2\u7EDD\u8FDE\u63A5. client extensionId=${msg.extensionId}, server authTokenFingerprint=${tokenFingerprint}
|
|
2411
|
+
`
|
|
2412
|
+
);
|
|
2413
|
+
try {
|
|
2414
|
+
socket.close(WS_CLOSE_CODE.UNAUTHORIZED, "Unauthorized: HMAC verification failed");
|
|
2415
|
+
} catch {
|
|
2416
|
+
}
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
clearTimeout(currentAuth.timer);
|
|
2420
|
+
currentAuth.authenticated = true;
|
|
2421
|
+
currentAuth.clientType = msg.clientType;
|
|
2422
|
+
if (msg.clientType === "extension") {
|
|
2423
|
+
if (this.extensionSocket && this.extensionSocket !== socket) {
|
|
2424
|
+
this.terminalSessions?.disposeAll();
|
|
2425
|
+
try {
|
|
2426
|
+
this.extensionSocket.close(WS_CLOSE_CODE.REPLACED, "Replaced by new connection");
|
|
2427
|
+
} catch {
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
this.extensionSocket = socket;
|
|
2431
|
+
this.emit("connected");
|
|
2432
|
+
}
|
|
2433
|
+
try {
|
|
2434
|
+
socket.send(
|
|
2435
|
+
JSON.stringify({
|
|
2436
|
+
type: "BRIDGE_AUTH_OK",
|
|
2437
|
+
clientType: msg.clientType
|
|
2438
|
+
})
|
|
2439
|
+
);
|
|
2440
|
+
} catch {
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
async handleForwardRpc(socket, msg) {
|
|
2444
|
+
let targetSocket = this._getActiveSocket();
|
|
2445
|
+
if (!targetSocket) {
|
|
2446
|
+
await new Promise((resolve3) => {
|
|
2447
|
+
const timer2 = setTimeout(resolve3, 6e3);
|
|
2448
|
+
this.once("connected", () => {
|
|
2449
|
+
clearTimeout(timer2);
|
|
2450
|
+
resolve3();
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
targetSocket = this._getActiveSocket();
|
|
2454
|
+
}
|
|
2455
|
+
if (!targetSocket) {
|
|
2456
|
+
socket.send(
|
|
2457
|
+
JSON.stringify({
|
|
2458
|
+
jsonrpc: "2.0",
|
|
2459
|
+
id: msg.id,
|
|
2460
|
+
error: {
|
|
2461
|
+
code: BRIDGE_ERROR_CODE.EXTENSION_NOT_CONNECTED,
|
|
2462
|
+
message: "Chrome \u6269\u5C55\u5C1A\u672A\u8FDE\u63A5\u5230\u672C\u5730\u6865\u63A5\u670D\u52A1\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u4E14\u5728\u4FA7\u8FB9\u680F\u4E2D\u5F00\u542F\u4E86\u300C\u672C\u5730 WebSocket \u6865\u63A5\u300D\u5F00\u5173\u3002"
|
|
2463
|
+
}
|
|
2464
|
+
})
|
|
2465
|
+
);
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const internalId = `fwd_${++this.idCounter}_${Date.now()}`;
|
|
2469
|
+
const timeoutMs = msg.method === "sub_agent/run" ? 18e4 : REQUEST_TIMEOUT_MS;
|
|
2470
|
+
const timer = setTimeout(() => {
|
|
2471
|
+
const fwd = this.forwardedRequests.get(internalId);
|
|
2472
|
+
if (!fwd) return;
|
|
2473
|
+
this.forwardedRequests.delete(internalId);
|
|
2474
|
+
try {
|
|
2475
|
+
if (fwd.socket.readyState === WS_OPEN) {
|
|
2476
|
+
fwd.socket.send(
|
|
2477
|
+
JSON.stringify({
|
|
2478
|
+
jsonrpc: "2.0",
|
|
2479
|
+
id: fwd.originalId,
|
|
2480
|
+
error: {
|
|
2481
|
+
code: BRIDGE_ERROR_CODE.REQUEST_TIMEOUT,
|
|
2482
|
+
message: `\u8BF7\u6C42\u8F6C\u53D1\u81F3 Chrome \u6269\u5C55\u8D85\u65F6 (${timeoutMs / 1e3}s)`
|
|
2483
|
+
}
|
|
2484
|
+
})
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
} catch {
|
|
2488
|
+
}
|
|
2489
|
+
}, timeoutMs);
|
|
2490
|
+
this.forwardedRequests.set(internalId, {
|
|
2491
|
+
socket,
|
|
2492
|
+
originalId: msg.id,
|
|
2493
|
+
progressToken: msg.params?.progressToken,
|
|
2494
|
+
timer
|
|
2495
|
+
});
|
|
2496
|
+
const outgoingMsg = { ...msg, id: internalId };
|
|
2497
|
+
targetSocket.send(JSON.stringify(outgoingMsg));
|
|
2498
|
+
}
|
|
2499
|
+
sendJsonRpcResponse(socket, response) {
|
|
2500
|
+
if (socket.readyState === WS_OPEN) {
|
|
2501
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", ...response }));
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
formatSystemRpcError(err) {
|
|
2505
|
+
if (err instanceof BashInvalidParamsError || err?.code === "INVALID_PARAMS" || err?.message === "\u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A") {
|
|
2506
|
+
return { code: BRIDGE_ERROR_CODE.INVALID_PARAMS, message: err?.message || "\u53C2\u6570\u9519\u8BEF" };
|
|
2507
|
+
}
|
|
2508
|
+
if (err instanceof BashTimeoutError || err?.code === "TIMEOUT" || typeof err?.message === "string" && err.message.includes("\u8D85\u65F6")) {
|
|
2509
|
+
return { code: BRIDGE_ERROR_CODE.EXECUTION_TIMEOUT, message: err?.message || "\u547D\u4EE4\u6267\u884C\u8D85\u65F6" };
|
|
2510
|
+
}
|
|
2511
|
+
return { code: BRIDGE_ERROR_CODE.EXECUTION_FAILED, message: err?.message || "\u6267\u884C\u5931\u8D25" };
|
|
2512
|
+
}
|
|
2513
|
+
systemRpcHandlers = Object.assign(
|
|
2514
|
+
/* @__PURE__ */ Object.create(null),
|
|
2515
|
+
{
|
|
2516
|
+
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
2517
|
+
const selectedPath = await pickDirectory({
|
|
2518
|
+
title: params?.title || void 0,
|
|
2519
|
+
defaultPath: params?.defaultPath || void 0
|
|
2520
|
+
});
|
|
2521
|
+
return {
|
|
2522
|
+
path: selectedPath,
|
|
2523
|
+
selectedPath,
|
|
2524
|
+
canceled: selectedPath === null
|
|
2525
|
+
};
|
|
2526
|
+
},
|
|
2527
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2528
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2529
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2530
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
2531
|
+
}
|
|
2532
|
+
);
|
|
2533
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2534
|
+
if (socket.readyState !== WS_OPEN) return;
|
|
2535
|
+
let payload;
|
|
2536
|
+
try {
|
|
2537
|
+
payload = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
2538
|
+
} catch {
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
try {
|
|
2542
|
+
socket.send(payload);
|
|
2543
|
+
} catch {
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
async executeHostBash(socket, id, params) {
|
|
2547
|
+
try {
|
|
2548
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2549
|
+
const result = await executeBash({
|
|
2550
|
+
...requestParams,
|
|
2551
|
+
onOutput: (chunk) => {
|
|
2552
|
+
if (!chunk.text) return;
|
|
2553
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2554
|
+
id,
|
|
2555
|
+
stream: chunk.stream,
|
|
2556
|
+
text: chunk.text
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
async handleExtensionSystemRpc(socket, msg) {
|
|
2566
|
+
const { id, method, params } = msg;
|
|
2567
|
+
if (!isJsonRpcId(id)) {
|
|
2568
|
+
this.sendJsonRpcResponse(socket, {
|
|
2569
|
+
id: null,
|
|
2570
|
+
error: {
|
|
2571
|
+
code: BRIDGE_ERROR_CODE.INVALID_PARAMS,
|
|
2572
|
+
message: "\u65E0\u6548\u7684\u8BF7\u6C42 id"
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2578
|
+
await this.executeHostBash(socket, id, params);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
2582
|
+
if (!handler || typeof handler !== "function") {
|
|
2583
|
+
this.sendJsonRpcResponse(socket, {
|
|
2584
|
+
id,
|
|
2585
|
+
error: {
|
|
2586
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2587
|
+
message: `\u4E0D\u652F\u6301\u7684\u65B9\u6CD5: ${method}`
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
try {
|
|
2593
|
+
const result = await handler(params);
|
|
2594
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2595
|
+
} catch (err) {
|
|
2596
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
handleProgressNotification(msg) {
|
|
2600
|
+
const token = msg.params?.progressToken;
|
|
2601
|
+
if (token != null) {
|
|
2602
|
+
for (const [, reqInfo] of this.forwardedRequests) {
|
|
2603
|
+
if (reqInfo.progressToken === token) {
|
|
2604
|
+
try {
|
|
2605
|
+
if (reqInfo.socket.readyState === WS_OPEN) {
|
|
2606
|
+
reqInfo.socket.send(JSON.stringify(msg));
|
|
2607
|
+
}
|
|
2608
|
+
} catch {
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
this.emit("progress", msg.params);
|
|
2614
|
+
}
|
|
2615
|
+
handleIncomingResponse(msg) {
|
|
2616
|
+
const fwdInfo = this.forwardedRequests.get(String(msg.id));
|
|
2617
|
+
if (fwdInfo) {
|
|
2618
|
+
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
2619
|
+
this.forwardedRequests.delete(String(msg.id));
|
|
2620
|
+
try {
|
|
2621
|
+
if (fwdInfo.socket.readyState === WS_OPEN) {
|
|
2622
|
+
fwdInfo.socket.send(
|
|
2623
|
+
JSON.stringify({
|
|
2624
|
+
...msg,
|
|
2625
|
+
id: fwdInfo.originalId
|
|
2626
|
+
})
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
} catch {
|
|
2630
|
+
}
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
const pending = this.pending.get(String(msg.id));
|
|
2634
|
+
if (!pending) return;
|
|
2635
|
+
this.pending.delete(String(msg.id));
|
|
2636
|
+
clearTimeout(pending.timer);
|
|
2637
|
+
if (msg.error) {
|
|
2638
|
+
pending.reject(new Error(`[${msg.error.code}] ${msg.error.message}`));
|
|
2639
|
+
} else {
|
|
2640
|
+
pending.resolve(msg.result);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
/** 发送 JSON-RPC 2.0 请求,返回 result */
|
|
2644
|
+
async call(method, params = {}, timeoutMs, progressToken, autoWaitMs = 0) {
|
|
2645
|
+
if (autoWaitMs > 0 && !this.isConnected) {
|
|
2646
|
+
await this.waitForReady(autoWaitMs);
|
|
2647
|
+
}
|
|
2648
|
+
const targetSocket = this._getActiveSocket();
|
|
2649
|
+
if (!targetSocket) {
|
|
2650
|
+
throw new Error(
|
|
2651
|
+
"\u4E0E Chrome \u6269\u5C55\u7684\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u786E\u4FDD\uFF1A\n 1. Chrome \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\n 2. Tiny Robot \u6269\u5C55\u5DF2\u5B89\u88C5\u5E76\u5F00\u542F\n 3. \u6269\u5C55\u4FA7\u8FB9\u680F\u5DF2\u6253\u5F00"
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
const effectiveTimeoutMs = timeoutMs ?? (method === "sub_agent/run" ? 18e4 : REQUEST_TIMEOUT_MS);
|
|
2655
|
+
const id = String(++this.idCounter);
|
|
2656
|
+
const requestParams = { ...params };
|
|
2657
|
+
if (progressToken != null) {
|
|
2658
|
+
requestParams.progressToken = progressToken;
|
|
2659
|
+
}
|
|
2660
|
+
const request = { jsonrpc: "2.0", id, method, params: requestParams };
|
|
2661
|
+
return new Promise((resolve3, reject) => {
|
|
2662
|
+
const timer = setTimeout(() => {
|
|
2663
|
+
this.pending.delete(id);
|
|
2664
|
+
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${effectiveTimeoutMs / 1e3}s\uFF09\uFF1A${method}`));
|
|
2665
|
+
}, effectiveTimeoutMs);
|
|
2666
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
2667
|
+
try {
|
|
2668
|
+
targetSocket.send(JSON.stringify(request));
|
|
2669
|
+
} catch (err) {
|
|
2670
|
+
this.pending.delete(id);
|
|
2671
|
+
clearTimeout(timer);
|
|
2672
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
getTerminalSessions() {
|
|
2677
|
+
if (!this.terminalSessions) {
|
|
2678
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2679
|
+
send: (frame) => {
|
|
2680
|
+
const socket = this.extensionSocket;
|
|
2681
|
+
if (!socket || socket.readyState !== WS_OPEN) return;
|
|
2682
|
+
const sessions = this.terminalSessions;
|
|
2683
|
+
if (sessions) applyTerminalBackpressure(socket.bufferedAmount ?? 0, sessions);
|
|
2684
|
+
let payload;
|
|
2685
|
+
try {
|
|
2686
|
+
payload = JSON.stringify(frame);
|
|
2687
|
+
} catch {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
try {
|
|
2691
|
+
socket.send(payload, () => {
|
|
2692
|
+
if (this.terminalSessions && socket.readyState === WS_OPEN) {
|
|
2693
|
+
applyTerminalBackpressure(socket.bufferedAmount ?? 0, this.terminalSessions);
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
} catch {
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
return this.terminalSessions;
|
|
2702
|
+
}
|
|
2703
|
+
/** 销毁服务,断开所有连接 */
|
|
2704
|
+
destroy() {
|
|
2705
|
+
this.destroyed = true;
|
|
2706
|
+
this.terminalSessions?.disposeAll();
|
|
2707
|
+
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
2708
|
+
this.forwardedRequests.clear();
|
|
2709
|
+
if (this.clientSocket) {
|
|
2710
|
+
try {
|
|
2711
|
+
this.clientSocket.close();
|
|
2712
|
+
} catch {
|
|
2713
|
+
}
|
|
2714
|
+
this.clientSocket = null;
|
|
2715
|
+
}
|
|
2716
|
+
if (this.extensionSocket) {
|
|
2717
|
+
try {
|
|
2718
|
+
this.extensionSocket.close();
|
|
2719
|
+
} catch {
|
|
2720
|
+
}
|
|
2721
|
+
this.extensionSocket = null;
|
|
2722
|
+
}
|
|
2723
|
+
if (this.wss) {
|
|
2724
|
+
try {
|
|
2725
|
+
this.wss.close();
|
|
2726
|
+
} catch {
|
|
2727
|
+
}
|
|
2728
|
+
this.wss = null;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
_rejectAllPending(reason) {
|
|
2732
|
+
for (const [, pending] of this.pending) {
|
|
2733
|
+
clearTimeout(pending.timer);
|
|
2734
|
+
pending.reject(new Error(reason));
|
|
2735
|
+
}
|
|
2736
|
+
this.pending.clear();
|
|
2737
|
+
for (const [, fwdInfo] of this.forwardedRequests) {
|
|
2738
|
+
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
2739
|
+
}
|
|
2740
|
+
this.forwardedRequests.clear();
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
});
|
|
2745
|
+
|
|
2746
|
+
// src/adapters/wxt-adapter.ts
|
|
2747
|
+
var wxt_adapter_exports = {};
|
|
2748
|
+
__export(wxt_adapter_exports, {
|
|
2749
|
+
WxtBrowserAdapter: () => WxtBrowserAdapter
|
|
2750
|
+
});
|
|
2751
|
+
var AUTO_WAIT_MS, WxtBrowserAdapter;
|
|
2752
|
+
var init_wxt_adapter = __esm({
|
|
2753
|
+
"src/adapters/wxt-adapter.ts"() {
|
|
2754
|
+
"use strict";
|
|
2755
|
+
init_bridge_client();
|
|
2756
|
+
init_protocol();
|
|
2757
|
+
AUTO_WAIT_MS = 6e3;
|
|
2758
|
+
WxtBrowserAdapter = class {
|
|
2759
|
+
client;
|
|
2760
|
+
constructor(token, port) {
|
|
2761
|
+
this.client = new ExtensionBridgeClient(port, token);
|
|
2762
|
+
this.client.on("error", () => {
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
/** 确保 WS 桥接服务已启动并连接 */
|
|
2766
|
+
async ensureConnected() {
|
|
2767
|
+
await this.client.startAuto();
|
|
2768
|
+
}
|
|
2769
|
+
async getState(tabId) {
|
|
2770
|
+
const numTabId = typeof tabId === "string" ? parseInt(tabId, 10) || void 0 : tabId;
|
|
2771
|
+
const result = await this.client.call(
|
|
2772
|
+
BRIDGE_METHODS.BROWSER_STATE,
|
|
2773
|
+
{ tabId: numTabId },
|
|
2774
|
+
void 0,
|
|
2775
|
+
void 0,
|
|
2776
|
+
AUTO_WAIT_MS
|
|
2777
|
+
);
|
|
2778
|
+
return result;
|
|
2779
|
+
}
|
|
2780
|
+
async callTool(toolName, args, tabId) {
|
|
2781
|
+
const numTabId = typeof tabId === "string" ? parseInt(tabId, 10) || void 0 : tabId;
|
|
2782
|
+
return this.client.call(
|
|
2783
|
+
BRIDGE_METHODS.TOOL_CALL,
|
|
2784
|
+
{ toolName, toolArgs: args, tabId: numTabId },
|
|
2785
|
+
void 0,
|
|
2786
|
+
void 0,
|
|
2787
|
+
AUTO_WAIT_MS
|
|
2788
|
+
);
|
|
2789
|
+
}
|
|
2790
|
+
async tabs(action, options) {
|
|
2791
|
+
const numTabId = typeof options.tabId === "string" ? parseInt(options.tabId, 10) || void 0 : options.tabId;
|
|
2792
|
+
return this.client.call(
|
|
2793
|
+
BRIDGE_METHODS.BROWSER_TABS,
|
|
2794
|
+
{ tabAction: action, ...options, tabId: numTabId },
|
|
2795
|
+
void 0,
|
|
2796
|
+
void 0,
|
|
2797
|
+
AUTO_WAIT_MS
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
/** 委托扩展内 Tiny Robot 子代理闭环执行高层任务 */
|
|
2801
|
+
async runSubAgent(instruction, options = {}, timeoutMs) {
|
|
2802
|
+
const params = { instruction, tabId: options.tabId, maxSteps: options.maxSteps };
|
|
2803
|
+
return this.client.call(
|
|
2804
|
+
BRIDGE_METHODS.SUB_AGENT_RUN,
|
|
2805
|
+
params,
|
|
2806
|
+
timeoutMs,
|
|
2807
|
+
options.progressToken,
|
|
2808
|
+
AUTO_WAIT_MS
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
/** 获取已注册的 Skills 列表(L1 索引) */
|
|
2812
|
+
async listSkills() {
|
|
2813
|
+
return this.client.call(BRIDGE_METHODS.SKILLS_LIST, {}, void 0, void 0, AUTO_WAIT_MS);
|
|
2814
|
+
}
|
|
2815
|
+
/** 读取指定 Skill 的详细指令(L2 详情) */
|
|
2816
|
+
async getSkill(name) {
|
|
2817
|
+
return this.client.call(BRIDGE_METHODS.SKILLS_GET, { name }, void 0, void 0, AUTO_WAIT_MS);
|
|
2818
|
+
}
|
|
2819
|
+
/** 读取 Skill 关联资源(L3 资源) */
|
|
2820
|
+
async readSkillResource(name, resourcePath) {
|
|
2821
|
+
return this.client.call(
|
|
2822
|
+
BRIDGE_METHODS.SKILLS_READ_RESOURCE,
|
|
2823
|
+
{ name, resourcePath },
|
|
2824
|
+
void 0,
|
|
2825
|
+
void 0,
|
|
2826
|
+
AUTO_WAIT_MS
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
/** 获取底层 ExtensionBridgeClient(供 MCP Server 绑定事件) */
|
|
2830
|
+
get bridgeClient() {
|
|
2831
|
+
return this.client;
|
|
2832
|
+
}
|
|
2833
|
+
async dispose() {
|
|
2834
|
+
this.client.destroy();
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
});
|
|
2839
|
+
|
|
2840
|
+
// src/bridge/daemon.ts
|
|
2841
|
+
var daemon_exports = {};
|
|
2842
|
+
__export(daemon_exports, {
|
|
2843
|
+
PID_FILE: () => PID_FILE,
|
|
2844
|
+
ensureBridgeDaemon: () => ensureBridgeDaemon,
|
|
2845
|
+
findPidByPort: () => findPidByPort,
|
|
2846
|
+
getDaemonPidFile: () => getDaemonPidFile,
|
|
2847
|
+
isBridgeDaemonProcess: () => isBridgeDaemonProcess,
|
|
2848
|
+
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2849
|
+
isProcessAlive: () => isProcessAlive,
|
|
2850
|
+
killProcessOnPort: () => killProcessOnPort,
|
|
2851
|
+
killProcessTree: () => killProcessTree,
|
|
2852
|
+
probeServerReady: () => probeServerReady,
|
|
2853
|
+
runDaemonProcess: () => runDaemonProcess,
|
|
2854
|
+
stopBridgeDaemon: () => stopBridgeDaemon
|
|
2855
|
+
});
|
|
2856
|
+
function getDaemonPidFile(port = WS_PORT2) {
|
|
2857
|
+
if (port === WS_PORT2) {
|
|
2858
|
+
return import_node_path5.default.join(DAEMON_DIR, "daemon.pid");
|
|
2859
|
+
}
|
|
2860
|
+
return import_node_path5.default.join(DAEMON_DIR, `daemon-${port}.pid`);
|
|
2861
|
+
}
|
|
2862
|
+
function isProcessAlive(pid) {
|
|
2863
|
+
if (!pid || pid <= 0) return false;
|
|
2864
|
+
try {
|
|
2865
|
+
process.kill(pid, 0);
|
|
2866
|
+
return true;
|
|
2867
|
+
} catch {
|
|
2868
|
+
return false;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
async function findPidByPort(port, excludePid) {
|
|
2872
|
+
if (process.platform === "win32") {
|
|
2873
|
+
try {
|
|
2874
|
+
const { stdout } = await execAsync("netstat -ano -p tcp");
|
|
2875
|
+
const lines = stdout.split(/\r?\n/);
|
|
2876
|
+
for (const rawLine of lines) {
|
|
2877
|
+
const line = rawLine.trim();
|
|
2878
|
+
if (!line.toUpperCase().startsWith("TCP")) continue;
|
|
2879
|
+
const parts = line.split(/\s+/);
|
|
2880
|
+
if (parts.length < 5) continue;
|
|
2881
|
+
const localAddr = parts[1];
|
|
2882
|
+
const state = parts[3]?.toUpperCase() || "";
|
|
2883
|
+
const pid = parseInt(parts[parts.length - 1], 10);
|
|
2884
|
+
if (!pid || isNaN(pid) || excludePid && pid === excludePid) continue;
|
|
2885
|
+
if (localAddr.endsWith(`:${port}`)) {
|
|
2886
|
+
if (state.includes("LISTEN") || state.includes("\u4FA6\u542C")) {
|
|
2887
|
+
return pid;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
return null;
|
|
2892
|
+
} catch {
|
|
2893
|
+
return null;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
try {
|
|
2897
|
+
const { stdout } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`);
|
|
2898
|
+
const pids = stdout.trim().split("\n").map((p) => parseInt(p.trim(), 10)).filter((p) => Boolean(p && !isNaN(p) && (!excludePid || p !== excludePid)));
|
|
2899
|
+
return pids[0] ?? null;
|
|
2900
|
+
} catch {
|
|
2901
|
+
return null;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
async function killProcessTree(pid) {
|
|
2905
|
+
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2906
|
+
if (process.platform === "win32") {
|
|
2907
|
+
try {
|
|
2908
|
+
await execAsync(`taskkill /pid ${pid} /T /F`);
|
|
2909
|
+
} catch {
|
|
2910
|
+
try {
|
|
2911
|
+
process.kill(pid, "SIGKILL");
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
for (let i = 0; i < 15; i++) {
|
|
2916
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2917
|
+
if (!isProcessAlive(pid)) return true;
|
|
2918
|
+
}
|
|
2919
|
+
return !isProcessAlive(pid);
|
|
2920
|
+
}
|
|
2921
|
+
try {
|
|
2922
|
+
process.kill(pid, "SIGTERM");
|
|
2923
|
+
} catch {
|
|
2924
|
+
}
|
|
2925
|
+
for (let i = 0; i < 10; i++) {
|
|
2926
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2927
|
+
if (!isProcessAlive(pid)) return true;
|
|
2928
|
+
}
|
|
2929
|
+
try {
|
|
2930
|
+
process.kill(pid, "SIGKILL");
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
for (let i = 0; i < 5; i++) {
|
|
2934
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2935
|
+
if (!isProcessAlive(pid)) return true;
|
|
2936
|
+
}
|
|
2937
|
+
return !isProcessAlive(pid);
|
|
2938
|
+
}
|
|
2939
|
+
async function killProcessOnPort(port) {
|
|
2940
|
+
const pid = await findPidByPort(port, process.pid);
|
|
2941
|
+
if (pid) {
|
|
2942
|
+
await killProcessTree(pid);
|
|
2943
|
+
for (let i = 0; i < 20; i++) {
|
|
2944
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2945
|
+
const remainingPid = await findPidByPort(port, process.pid);
|
|
2946
|
+
if (!remainingPid) return true;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
async function probeServerReady(port = WS_PORT2, token, timeoutMs = 1e3) {
|
|
2952
|
+
return ExtensionBridgeClient.probe(port, token ?? resolveOrCreateAuthToken(), timeoutMs);
|
|
2953
|
+
}
|
|
2954
|
+
function isDaemonProcessAlive(port = WS_PORT2) {
|
|
2955
|
+
const pidFile = getDaemonPidFile(port);
|
|
2956
|
+
if (!import_node_fs5.default.existsSync(pidFile)) return false;
|
|
2957
|
+
try {
|
|
2958
|
+
const pid = parseInt(import_node_fs5.default.readFileSync(pidFile, "utf8").trim(), 10);
|
|
2959
|
+
return Boolean(pid && isProcessAlive(pid));
|
|
2960
|
+
} catch {
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
async function ensureBridgeDaemon(options = {}) {
|
|
2965
|
+
const port = options.port ?? WS_PORT2;
|
|
2966
|
+
const token = options.token ?? resolveOrCreateAuthToken();
|
|
2967
|
+
const timeoutMs = options.timeoutMs ?? 6e3;
|
|
2968
|
+
const probe = await probeServerReady(port, token, 800);
|
|
2969
|
+
if (probe.running && probe.authed) {
|
|
2970
|
+
return { newlyStarted: false };
|
|
2971
|
+
}
|
|
2972
|
+
if (probe.running && !probe.authed) {
|
|
2973
|
+
if (!options.silent) {
|
|
2974
|
+
console.log(
|
|
2975
|
+
`\x1B[33m[webmcp-cli]\x1B[0m \u{1F504} \u68C0\u6D4B\u5230\u540E\u53F0\u6865\u63A5\u670D\u52A1 Token \u4E0D\u5339\u914D\uFF0C\u6B63\u5728\u9A8C\u8BC1\u65E7\u5B88\u62A4\u8FDB\u7A0B\u5F52\u5C5E\u5E76\u5C1D\u8BD5\u81EA\u52A8\u91CD\u542F...`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
const stopped = await stopBridgeDaemon(port, { onlyVerified: true });
|
|
2979
|
+
if (!stopped) {
|
|
2980
|
+
const killTip = getKillPortHint(port);
|
|
2981
|
+
throw new Error(
|
|
2982
|
+
`\u672C\u5730 127.0.0.1:${port} \u6B63\u5728\u8FD0\u884C\u670D\u52A1\uFF0C\u4F46\u8BA4\u8BC1\u672A\u901A\u8FC7\uFF08${probe.error || "Token \u4E0D\u5339\u914D"}\uFF09\u3002
|
|
2983
|
+
\u672A\u80FD\u786E\u8BA4\u8BE5\u8FDB\u7A0B\u5F52\u5C5E\u4E8E\u53D7\u7BA1\u7684\u6865\u63A5\u5B88\u62A4\u8FDB\u7A0B\uFF0C\u4E3A\u5B89\u5168\u8D77\u89C1\u5DF2\u963B\u6B62\u81EA\u52A8\u5F3A\u6740\u3002\u8BF7\u68C0\u67E5\u7AEF\u53E3 ${port} \u662F\u5426\u88AB\u5176\u4ED6\u5E94\u7528\u5360\u7528\uFF0C\u6216\u624B\u52A8\u7EC8\u6B62\uFF08\u53EF\u6267\u884C: ${killTip}\uFF09\u3002`
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
2987
|
+
}
|
|
2988
|
+
if (!options.silent) {
|
|
2989
|
+
console.log(`\x1B[36m[webmcp-cli]\x1B[0m \u{1F680} \u672C\u5730\u6865\u63A5\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u81EA\u52A8\u62C9\u8D77\u5E38\u9A7B\u670D\u52A1 (127.0.0.1:${port})...`);
|
|
2990
|
+
}
|
|
2991
|
+
startDaemonProcessBackground(token, port);
|
|
2992
|
+
const startTime = Date.now();
|
|
2993
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
2994
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2995
|
+
const check = await probeServerReady(port, token, 400);
|
|
2996
|
+
if (check.running && check.authed) {
|
|
2997
|
+
if (!options.silent) {
|
|
2998
|
+
console.log(`\x1B[32m[webmcp-cli]\x1B[0m \u2705 \u6865\u63A5\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u7B49\u5F85 Chrome \u6269\u5C55\u8FDE\u63A5...`);
|
|
2999
|
+
}
|
|
3000
|
+
return { newlyStarted: true };
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
throw new Error(`\u542F\u52A8\u540E\u53F0 WebSocket \u6865\u63A5\u670D\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u7AEF\u53E3 ${port} \u662F\u5426\u53EF\u7528\u3002`);
|
|
3004
|
+
}
|
|
3005
|
+
function resolveCliEntry() {
|
|
3006
|
+
const currentDir = import_node_path5.default.dirname((0, import_node_url.fileURLToPath)(import_meta3.url));
|
|
3007
|
+
const distCandidate = import_node_path5.default.join(currentDir, "bin.js");
|
|
3008
|
+
if (import_node_fs5.default.existsSync(distCandidate)) return distCandidate;
|
|
3009
|
+
const projectDistCandidate = import_node_path5.default.resolve(currentDir, "../dist/bin.js");
|
|
3010
|
+
if (import_node_fs5.default.existsSync(projectDistCandidate)) return projectDistCandidate;
|
|
3011
|
+
return distCandidate;
|
|
3012
|
+
}
|
|
3013
|
+
function startDaemonProcessBackground(token, port) {
|
|
3014
|
+
const cliEntry = resolveCliEntry();
|
|
3015
|
+
if (!import_node_fs5.default.existsSync(DAEMON_DIR)) {
|
|
3016
|
+
import_node_fs5.default.mkdirSync(DAEMON_DIR, { mode: 448, recursive: true });
|
|
3017
|
+
}
|
|
3018
|
+
const child = (0, import_node_child_process4.spawn)(process.execPath, [cliEntry, "--mode", "wxt", "daemon", "--port", String(port)], {
|
|
3019
|
+
detached: true,
|
|
3020
|
+
stdio: "ignore",
|
|
3021
|
+
windowsHide: true,
|
|
3022
|
+
env: {
|
|
3023
|
+
...process.env,
|
|
3024
|
+
ROBOT_WXT_BRIDGE_TOKEN: token
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
child.unref();
|
|
3028
|
+
}
|
|
3029
|
+
async function runDaemonProcess(args) {
|
|
3030
|
+
let port = WS_PORT2;
|
|
3031
|
+
let token;
|
|
3032
|
+
const portIdx = args.indexOf("--port");
|
|
3033
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
3034
|
+
port = parseInt(args[portIdx + 1], 10);
|
|
3035
|
+
}
|
|
3036
|
+
const tokenIdx = args.indexOf("--token");
|
|
3037
|
+
if (tokenIdx !== -1 && args[tokenIdx + 1]) {
|
|
3038
|
+
token = args[tokenIdx + 1];
|
|
3039
|
+
} else if (process.env.ROBOT_WXT_BRIDGE_TOKEN) {
|
|
3040
|
+
token = process.env.ROBOT_WXT_BRIDGE_TOKEN;
|
|
3041
|
+
}
|
|
3042
|
+
const client = new ExtensionBridgeClient(port, token);
|
|
3043
|
+
client.on("error", (err) => {
|
|
3044
|
+
if (err && (err.code === "EADDRINUSE" || err.message?.includes("EADDRINUSE"))) {
|
|
3045
|
+
process.exit(1);
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
3048
|
+
const pidFile = getDaemonPidFile(port);
|
|
3049
|
+
client.on("listening", () => {
|
|
3050
|
+
try {
|
|
3051
|
+
if (!import_node_fs5.default.existsSync(DAEMON_DIR)) {
|
|
3052
|
+
import_node_fs5.default.mkdirSync(DAEMON_DIR, { mode: 448, recursive: true });
|
|
3053
|
+
}
|
|
3054
|
+
import_node_fs5.default.writeFileSync(pidFile, String(process.pid), { mode: 384, encoding: "utf8" });
|
|
3055
|
+
} catch {
|
|
3056
|
+
}
|
|
3057
|
+
});
|
|
3058
|
+
client.connect();
|
|
3059
|
+
const shutdown = () => {
|
|
3060
|
+
try {
|
|
3061
|
+
if (import_node_fs5.default.existsSync(pidFile)) {
|
|
3062
|
+
const recordedPid = import_node_fs5.default.readFileSync(pidFile, "utf8").trim();
|
|
3063
|
+
if (recordedPid === String(process.pid)) {
|
|
3064
|
+
import_node_fs5.default.unlinkSync(pidFile);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
} catch {
|
|
3068
|
+
}
|
|
3069
|
+
client.destroy();
|
|
3070
|
+
process.exit(0);
|
|
3071
|
+
};
|
|
3072
|
+
process.on("SIGINT", shutdown);
|
|
3073
|
+
process.on("SIGTERM", shutdown);
|
|
3074
|
+
process.on("exit", shutdown);
|
|
3075
|
+
setInterval(() => {
|
|
3076
|
+
}, 6e4);
|
|
3077
|
+
}
|
|
3078
|
+
async function isBridgeDaemonProcess(pid) {
|
|
3079
|
+
if (!pid || pid <= 0 || !isProcessAlive(pid)) return false;
|
|
3080
|
+
try {
|
|
3081
|
+
let cmd = "";
|
|
3082
|
+
if (process.platform === "win32") {
|
|
3083
|
+
const { stdout } = await execAsync(
|
|
3084
|
+
`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId = ${pid}\\").CommandLine"`
|
|
3085
|
+
);
|
|
3086
|
+
cmd = stdout.trim();
|
|
3087
|
+
} else {
|
|
3088
|
+
const { stdout } = await execAsync(`ps -p ${pid} -o command=`);
|
|
3089
|
+
cmd = stdout.trim();
|
|
3090
|
+
}
|
|
3091
|
+
if (!cmd) return false;
|
|
3092
|
+
return (cmd.includes("webmcp-cli") || cmd.includes("bin.js") || cmd.includes("daemon.js")) && cmd.includes("daemon");
|
|
3093
|
+
} catch {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
3098
|
+
const onlyVerified = options.onlyVerified ?? false;
|
|
3099
|
+
let killedAny = false;
|
|
3100
|
+
const pidFile = getDaemonPidFile(port);
|
|
3101
|
+
let daemonPid = null;
|
|
3102
|
+
if (import_node_fs5.default.existsSync(pidFile)) {
|
|
3103
|
+
try {
|
|
3104
|
+
const pidStr = import_node_fs5.default.readFileSync(pidFile, "utf8").trim();
|
|
3105
|
+
const p = parseInt(pidStr, 10);
|
|
3106
|
+
if (p && isProcessAlive(p)) {
|
|
3107
|
+
daemonPid = p;
|
|
3108
|
+
}
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
if (onlyVerified) {
|
|
3113
|
+
if (!daemonPid) {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const isVerified = await isBridgeDaemonProcess(daemonPid);
|
|
3117
|
+
if (!isVerified) {
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
const portPid = await findPidByPort(port, process.pid);
|
|
3121
|
+
if (portPid && portPid !== daemonPid) {
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
const killed = await killProcessTree(daemonPid);
|
|
3125
|
+
if (killed) {
|
|
3126
|
+
killedAny = true;
|
|
3127
|
+
try {
|
|
3128
|
+
if (import_node_fs5.default.existsSync(pidFile)) import_node_fs5.default.unlinkSync(pidFile);
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
} else {
|
|
3133
|
+
if (daemonPid) {
|
|
3134
|
+
try {
|
|
3135
|
+
const killed = await killProcessTree(daemonPid);
|
|
3136
|
+
if (killed) {
|
|
3137
|
+
killedAny = true;
|
|
3138
|
+
try {
|
|
3139
|
+
if (import_node_fs5.default.existsSync(pidFile)) import_node_fs5.default.unlinkSync(pidFile);
|
|
3140
|
+
} catch {
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
} catch {
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
const portPid = await findPidByPort(port, process.pid);
|
|
3147
|
+
if (portPid) {
|
|
3148
|
+
const killedByPort = await killProcessOnPort(port);
|
|
3149
|
+
if (killedByPort) {
|
|
3150
|
+
killedAny = true;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
let portReleased = false;
|
|
3155
|
+
for (let i = 0; i < 10; i++) {
|
|
3156
|
+
const remainingPid = await findPidByPort(port, process.pid);
|
|
3157
|
+
if (!remainingPid) {
|
|
3158
|
+
portReleased = true;
|
|
3159
|
+
break;
|
|
3160
|
+
}
|
|
3161
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3162
|
+
}
|
|
3163
|
+
if (!portReleased) {
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
return killedAny;
|
|
3167
|
+
}
|
|
3168
|
+
var import_node_child_process4, import_node_util, import_node_fs5, import_node_path5, import_node_os4, import_node_url, import_meta3, execAsync, WS_PORT2, DAEMON_DIR, PID_FILE;
|
|
3169
|
+
var init_daemon = __esm({
|
|
3170
|
+
"src/bridge/daemon.ts"() {
|
|
3171
|
+
"use strict";
|
|
3172
|
+
import_node_child_process4 = require("child_process");
|
|
3173
|
+
import_node_util = require("util");
|
|
3174
|
+
import_node_fs5 = __toESM(require("fs"), 1);
|
|
3175
|
+
import_node_path5 = __toESM(require("path"), 1);
|
|
3176
|
+
import_node_os4 = __toESM(require("os"), 1);
|
|
3177
|
+
import_node_url = require("url");
|
|
3178
|
+
init_bridge_client();
|
|
3179
|
+
import_meta3 = {};
|
|
3180
|
+
execAsync = (0, import_node_util.promisify)(import_node_child_process4.exec);
|
|
3181
|
+
WS_PORT2 = 18999;
|
|
3182
|
+
DAEMON_DIR = import_node_path5.default.join(import_node_os4.default.homedir(), ".robot-wxt");
|
|
3183
|
+
PID_FILE = getDaemonPidFile(WS_PORT2);
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
|
|
3187
|
+
// src/mcp/server.ts
|
|
3188
|
+
var server_exports = {};
|
|
3189
|
+
__export(server_exports, {
|
|
3190
|
+
startMcpServer: () => startMcpServer
|
|
3191
|
+
});
|
|
3192
|
+
async function startMcpServer(options = {}) {
|
|
3193
|
+
const mode = options.mode ?? "cdp";
|
|
3194
|
+
const agentMode = options.agentMode ?? "tools";
|
|
3195
|
+
const isAgentMode = agentMode === "agent";
|
|
3196
|
+
const server = new import_mcp.McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
3197
|
+
if (mode === "cdp") {
|
|
3198
|
+
if (isAgentMode) {
|
|
3199
|
+
throw new Error("--agent \u5B50\u4EE3\u7406\u59D4\u6258\u6A21\u5F0F\u4EC5\u5728 WXT \u6A21\u5F0F\u53EF\u7528\uFF0C\u8BF7\u4F7F\u7528: webmcp-cli --mode wxt mcp --agent");
|
|
3200
|
+
}
|
|
3201
|
+
await startCdpMcpServer(server, isAgentMode);
|
|
3202
|
+
} else {
|
|
3203
|
+
await startWxtMcpServer(server, isAgentMode, options);
|
|
3204
|
+
}
|
|
3205
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
3206
|
+
await server.connect(transport);
|
|
3207
|
+
process.stderr.write(
|
|
3208
|
+
`[webmcp-cli] MCP Server \u5DF2\u542F\u52A8\uFF08${SERVER_NAME} v${SERVER_VERSION}\uFF0C\u6A21\u5F0F: ${mode}${isAgentMode ? "/agent" : "/tools"}\uFF09
|
|
3209
|
+
`
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
async function startCdpMcpServer(server, isAgentMode) {
|
|
3213
|
+
const { CdpBrowserAdapter: CdpBrowserAdapter2 } = await Promise.resolve().then(() => (init_cdp_adapter(), cdp_adapter_exports));
|
|
3214
|
+
const adapter = new CdpBrowserAdapter2();
|
|
3215
|
+
registerCommonTools(server, adapter, isAgentMode);
|
|
3216
|
+
process.on("SIGINT", async () => {
|
|
3217
|
+
await adapter.dispose();
|
|
3218
|
+
process.exit(0);
|
|
3219
|
+
});
|
|
3220
|
+
process.on("SIGTERM", async () => {
|
|
3221
|
+
await adapter.dispose();
|
|
3222
|
+
process.exit(0);
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
async function startWxtMcpServer(server, isAgentMode, options) {
|
|
3226
|
+
const { WxtBrowserAdapter: WxtBrowserAdapter2 } = await Promise.resolve().then(() => (init_wxt_adapter(), wxt_adapter_exports));
|
|
3227
|
+
const { ensureBridgeDaemon: ensureBridgeDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
3228
|
+
const daemonResult = await ensureBridgeDaemon2({
|
|
3229
|
+
token: options.token,
|
|
3230
|
+
port: options.wsPort,
|
|
3231
|
+
silent: true
|
|
3232
|
+
});
|
|
3233
|
+
const adapter = new WxtBrowserAdapter2(options.token, options.wsPort);
|
|
3234
|
+
await adapter.ensureConnected();
|
|
3235
|
+
if (daemonResult.newlyStarted) {
|
|
3236
|
+
process.stderr.write("[webmcp-cli] \u{1F680} \u672C\u5730\u6865\u63A5\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u7B49\u5F85 Chrome \u6269\u5C55\u8FDE\u63A5...\n");
|
|
3237
|
+
}
|
|
3238
|
+
const client = adapter.bridgeClient;
|
|
3239
|
+
registerCommonTools(server, adapter, isAgentMode);
|
|
3240
|
+
registerWxtTools(server, adapter, isAgentMode, server);
|
|
3241
|
+
registerSkillsResources(server, adapter);
|
|
3242
|
+
client.on("connected", () => {
|
|
3243
|
+
process.stderr.write("[webmcp-cli] \u5DF2\u8FDE\u63A5\u5230 Chrome \u6269\u5C55\n");
|
|
3244
|
+
try {
|
|
3245
|
+
server.server.notification({ method: "notifications/resources/list_changed" });
|
|
3246
|
+
} catch {
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
client.on("disconnected", () => {
|
|
3250
|
+
process.stderr.write("[webmcp-cli] \u4E0E Chrome \u6269\u5C55\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00\n");
|
|
3251
|
+
});
|
|
3252
|
+
process.on("SIGINT", async () => {
|
|
3253
|
+
await adapter.dispose();
|
|
3254
|
+
process.exit(0);
|
|
3255
|
+
});
|
|
3256
|
+
process.on("SIGTERM", async () => {
|
|
3257
|
+
await adapter.dispose();
|
|
3258
|
+
process.exit(0);
|
|
3259
|
+
});
|
|
3260
|
+
const tokenFingerprint = options.token ? `${options.token.slice(0, 6)}...` : "(auto)";
|
|
3261
|
+
process.stderr.write(`[webmcp-cli] \u{1F511} Token \u6307\u7EB9: ${tokenFingerprint} (\u5B8C\u6574\u51ED\u636E\u89C1 ~/.robot-wxt/bridge-token)
|
|
3262
|
+
`);
|
|
3263
|
+
}
|
|
3264
|
+
function registerCommonTools(server, adapter, isAgentMode) {
|
|
3265
|
+
server.registerTool(
|
|
3266
|
+
"browser_state",
|
|
3267
|
+
{
|
|
3268
|
+
description: "\u83B7\u53D6\u5F53\u524D\u6D4F\u89C8\u5668\u72B6\u6001\uFF0C\u5305\u62EC\uFF1A\u5F53\u524D tab \u7684 URL / \u6807\u9898\u3001\u6240\u6709\u5DF2\u6253\u5F00\u7684 tab \u5217\u8868\u3001\u5F53\u524D\u9875\u9762\u6CE8\u518C\u7684 WebMCP \u5DE5\u5177\u5217\u8868\u3002\u5728\u6267\u884C\u64CD\u4F5C\u524D\uFF0C\u5EFA\u8BAE\u5148\u8C03\u7528\u6B64\u5DE5\u5177\u4E86\u89E3\u5F53\u524D\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u3002",
|
|
3269
|
+
inputSchema: BrowserStateInput.shape
|
|
3270
|
+
},
|
|
3271
|
+
async (args) => {
|
|
3272
|
+
const result = await adapter.getState(args.tabId);
|
|
3273
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3274
|
+
}
|
|
3275
|
+
);
|
|
3276
|
+
if (!isAgentMode) {
|
|
3277
|
+
server.registerTool(
|
|
3278
|
+
"browser_tool_call",
|
|
3279
|
+
{
|
|
3280
|
+
description: "\u6267\u884C\u5F53\u524D\u9875\u9762\u6CE8\u518C\u7684 WebMCP \u5DE5\u5177\uFF08\u5982 click\u3001input\u3001scroll \u7B49\uFF09\u3002\u5DE5\u5177\u540D\u79F0\u548C\u53C2\u6570 schema \u901A\u8FC7 browser_state \u8FD4\u56DE\u7684 webmcpTools \u5B57\u6BB5\u83B7\u53D6\u3002",
|
|
3281
|
+
inputSchema: BrowserToolCallInput.shape
|
|
3282
|
+
},
|
|
3283
|
+
async (args) => {
|
|
3284
|
+
const result = await adapter.callTool(args.toolName, args.toolArgs ?? {}, args.tabId);
|
|
3285
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3286
|
+
}
|
|
3287
|
+
);
|
|
3288
|
+
server.registerTool(
|
|
3289
|
+
"browser_tabs_open",
|
|
3290
|
+
{ description: "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4E00\u4E2A\u65B0\u6807\u7B7E\u9875\u5E76\u5BFC\u822A\u5230\u6307\u5B9A URL\u3002", inputSchema: BrowserTabsOpenInput.shape },
|
|
3291
|
+
async (args) => {
|
|
3292
|
+
const result = await adapter.tabs("open", { url: args.url });
|
|
3293
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3294
|
+
}
|
|
3295
|
+
);
|
|
3296
|
+
server.registerTool(
|
|
3297
|
+
"browser_tabs_close",
|
|
3298
|
+
{ description: "\u5173\u95ED\u6307\u5B9A\u6807\u7B7E\u9875\uFF08\u4E0D\u6307\u5B9A tabId \u65F6\u5173\u95ED\u5F53\u524D\u6D3B\u8DC3\u6807\u7B7E\u9875\uFF09\u3002", inputSchema: BrowserTabsCloseInput.shape },
|
|
3299
|
+
async (args) => {
|
|
3300
|
+
const result = await adapter.tabs("close", { tabId: args.tabId });
|
|
3301
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3302
|
+
}
|
|
3303
|
+
);
|
|
3304
|
+
server.registerTool(
|
|
3305
|
+
"browser_tabs_switch",
|
|
3306
|
+
{
|
|
3307
|
+
description: "\u5C06\u7126\u70B9\u5207\u6362\u5230\u6307\u5B9A\u6807\u7B7E\u9875\uFF08tabId \u901A\u8FC7 browser_state \u8FD4\u56DE\u7684 tabs \u5217\u8868\u83B7\u53D6\uFF09\u3002",
|
|
3308
|
+
inputSchema: BrowserTabsSwitchInput.shape
|
|
3309
|
+
},
|
|
3310
|
+
async (args) => {
|
|
3311
|
+
const result = await adapter.tabs("switch", { tabId: args.tabId, activateVisible: args.activateVisible });
|
|
3312
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3313
|
+
}
|
|
3314
|
+
);
|
|
3315
|
+
server.registerTool(
|
|
3316
|
+
"browser_tabs_back",
|
|
3317
|
+
{ description: "\u5728\u6307\u5B9A\u6807\u7B7E\u9875\uFF08\u6216\u5F53\u524D\u6D3B\u8DC3\u6807\u7B7E\u9875\uFF09\u4E2D\u6267\u884C\u6D4F\u89C8\u5668\u540E\u9000\u64CD\u4F5C\u3002", inputSchema: BrowserTabsNavInput.shape },
|
|
3318
|
+
async (args) => {
|
|
3319
|
+
const result = await adapter.tabs("back", { tabId: args.tabId });
|
|
3320
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3321
|
+
}
|
|
3322
|
+
);
|
|
3323
|
+
server.registerTool(
|
|
3324
|
+
"browser_tabs_forward",
|
|
3325
|
+
{ description: "\u5728\u6307\u5B9A\u6807\u7B7E\u9875\uFF08\u6216\u5F53\u524D\u6D3B\u8DC3\u6807\u7B7E\u9875\uFF09\u4E2D\u6267\u884C\u6D4F\u89C8\u5668\u524D\u8FDB\u64CD\u4F5C\u3002", inputSchema: BrowserTabsNavInput.shape },
|
|
3326
|
+
async (args) => {
|
|
3327
|
+
const result = await adapter.tabs("forward", { tabId: args.tabId });
|
|
3328
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3329
|
+
}
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
function registerWxtTools(server, adapter, isAgentMode, mcpServer) {
|
|
3334
|
+
server.registerTool(
|
|
3335
|
+
"browser_sub_agent_run",
|
|
3336
|
+
{
|
|
3337
|
+
description: isAgentMode ? "\u300ATiny Robot \u7AEF\u4FA7\u81EA\u4E3B\u5B50\u4EE3\u7406\u300B\u5C06\u590D\u6742\u6216\u957F\u6D41\u7A0B\u7684\u6D4F\u89C8\u5668\u64CD\u4F5C\u4EFB\u52A1\u59D4\u6258\u7ED9\u6269\u5C55\u7AEF\u4FA7\u7684 Tiny Robot \u5B50\u4EE3\u7406\u95ED\u73AF\u6267\u884C\u3002\u5B50\u4EE3\u7406\u5C06\u5728\u7AEF\u4FA7\u81EA\u4E3B\u89C2\u5BDF\u9875\u9762\u3001\u89C4\u5212\u6B65\u9AA4\u5E76\u6267\u884C\u539F\u5B50\u64CD\u4F5C\uFF0C\u6267\u884C\u5B8C\u6BD5\u540E\u8FD4\u56DE\u6700\u7EC8\u7ED3\u679C\uFF0C\u6781\u5927\u5730\u8282\u7701\u4E3B Agent \u4E0A\u4E0B\u6587 Token\u3002" : "\u300A\u9AD8\u5C42\u4EFB\u52A1\u59D4\u6258\u300B\u5C06\u590D\u6742\u3001\u957F\u6D41\u7A0B\u6216\u9700\u53CD\u590D\u8BD5\u9519\u7684\u6D4F\u89C8\u5668\u64CD\u4F5C\u4EFB\u52A1\u59D4\u6258\u7ED9 Chrome \u6269\u5C55\u5185\u90E8\u7684 Tiny Robot \u5B50\u4EE3\u7406\uFF08Sub-Agent\uFF09\u81EA\u4E3B\u95ED\u73AF\u6267\u884C\u3002\u5B50\u4EE3\u7406\u5C06\u5728\u7AEF\u4FA7\u81EA\u4E3B\u8FDB\u884C\u9875\u9762\u63A2\u7D22\u3001\u89C4\u5212\u548C\u539F\u5B50\u64CD\u4F5C\uFF0C\u6267\u884C\u5B8C\u6BD5\u540E\u8FD4\u56DE\u6700\u7EC8\u7ED3\u679C\uFF0C\u6781\u5927\u5730\u8282\u7701\u5916\u90E8\u4E3B Agent \u7684\u4E0A\u4E0B\u6587 Token\u3002",
|
|
3338
|
+
inputSchema: BrowserSubAgentRunInput.shape
|
|
3339
|
+
},
|
|
3340
|
+
async (args, extra) => {
|
|
3341
|
+
const progressToken = extra?._meta?.progressToken;
|
|
3342
|
+
const onProgress = progressToken != null ? (params) => {
|
|
3343
|
+
if (params?.progressToken && params.progressToken !== progressToken) return;
|
|
3344
|
+
try {
|
|
3345
|
+
mcpServer.server.notification({
|
|
3346
|
+
method: "notifications/progress",
|
|
3347
|
+
params: { progressToken, progress: params?.step ?? 0, total: params?.total ?? args.maxSteps ?? 8, message: params?.message ?? "" }
|
|
3348
|
+
});
|
|
3349
|
+
} catch {
|
|
3350
|
+
}
|
|
3351
|
+
} : void 0;
|
|
3352
|
+
if (onProgress) adapter.bridgeClient.on("progress", onProgress);
|
|
3353
|
+
try {
|
|
3354
|
+
const result = await adapter.runSubAgent(args.instruction, {
|
|
3355
|
+
tabId: args.tabId,
|
|
3356
|
+
maxSteps: args.maxSteps,
|
|
3357
|
+
progressToken
|
|
3358
|
+
});
|
|
3359
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3360
|
+
} finally {
|
|
3361
|
+
if (onProgress) adapter.bridgeClient.off("progress", onProgress);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
);
|
|
3365
|
+
if (!isAgentMode) {
|
|
3366
|
+
server.registerTool(
|
|
3367
|
+
"browser_skills_list",
|
|
3368
|
+
{
|
|
3369
|
+
description: "\u3010\u6280\u80FD\u6E10\u8FDB\u5F0F\u62AB\u9732 - L1 \u7D22\u5F15\u5C42\u3011\u83B7\u53D6\u5F53\u524D\u6D4F\u89C8\u5668\u6269\u5C55\u4E2D\u5DF2\u7EF4\u62A4\u7684\u6240\u6709 Skills \u6280\u80FD\u7B80\u77ED\u6E05\u5355\uFF08\u4EC5\u8FD4\u56DE\u540D\u79F0\u3001\u7B80\u4ECB\uFF0C\u6D88\u8017\u6781\u5C11 Token\uFF09\u3002\u5728\u81EA\u4E3B\u6267\u884C\u590D\u6742\u6216\u5782\u76F4\u9886\u57DF\u4EFB\u52A1\u524D\uFF0C\u53EF\u5148\u8C03\u7528\u6B64\u5DE5\u5177\u5339\u914D\u6700\u9002\u7528\u7684\u6280\u80FD\u3002",
|
|
3370
|
+
inputSchema: BrowserSkillsListInput.shape
|
|
3371
|
+
},
|
|
3372
|
+
async () => {
|
|
3373
|
+
const result = await adapter.listSkills();
|
|
3374
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3375
|
+
}
|
|
3376
|
+
);
|
|
3377
|
+
server.registerTool(
|
|
3378
|
+
"browser_skills_read",
|
|
3379
|
+
{
|
|
3380
|
+
description: "\u3010\u6280\u80FD\u6E10\u8FDB\u5F0F\u62AB\u9732 - L2 \u5951\u7EA6\u5C42\u3011\u6839\u636E\u6280\u80FD\u540D\u79F0\u6309\u9700\u8BFB\u53D6\u8BE5\u6280\u80FD\u7684\u5B8C\u6574\u8BE6\u7EC6\u6267\u884C\u6307\u4EE4 (instructions)\u3001\u5DE5\u4F5C\u6D41\u89C4\u8303\u4E0E\u8F93\u5165\u8F93\u51FA\u5951\u7EA6\u3002",
|
|
3381
|
+
inputSchema: BrowserSkillsReadInput.shape
|
|
3382
|
+
},
|
|
3383
|
+
async (args) => {
|
|
3384
|
+
const result = await adapter.getSkill(args.name);
|
|
3385
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3386
|
+
}
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function registerSkillsResources(server, adapter) {
|
|
3391
|
+
server.registerResource(
|
|
3392
|
+
"skills_index",
|
|
3393
|
+
"skills://index",
|
|
3394
|
+
{ description: "Skills L1\uFF1A\u6269\u5C55\u5DF2\u6CE8\u518C\u7684\u6240\u6709\u6280\u80FD\u5217\u8868\uFF08\u4EC5\u5143\u6570\u636E\uFF0C\u6D88\u8017\u6781\u5C11 Token\uFF09", mimeType: "application/json" },
|
|
3395
|
+
async () => {
|
|
3396
|
+
try {
|
|
3397
|
+
const skills = await adapter.listSkills();
|
|
3398
|
+
return { contents: [{ uri: "skills://index", text: JSON.stringify(skills, null, 2), mimeType: "application/json" }] };
|
|
3399
|
+
} catch (err) {
|
|
3400
|
+
return { contents: [{ uri: "skills://index", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }), mimeType: "application/json" }] };
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
);
|
|
3404
|
+
const skillDetailTemplate = new import_mcp.ResourceTemplate("skills://{skillName}/detail", { list: void 0 });
|
|
3405
|
+
server.registerResource(
|
|
3406
|
+
"skill_detail",
|
|
3407
|
+
skillDetailTemplate,
|
|
3408
|
+
{ description: "Skills L2\uFF1A\u6309\u6280\u80FD\u540D\u5B9E\u65F6\u62C9\u53D6\u5B8C\u6574 SOP \u6267\u884C\u6307\u4EE4\u4E0E\u8F93\u5165\u8F93\u51FA\u5951\u7EA6", mimeType: "application/json" },
|
|
3409
|
+
async (uri, params) => {
|
|
3410
|
+
const { skillName } = params;
|
|
3411
|
+
try {
|
|
3412
|
+
const detail = await adapter.getSkill(skillName);
|
|
3413
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify(detail, null, 2), mimeType: "application/json" }] };
|
|
3414
|
+
} catch (err) {
|
|
3415
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }), mimeType: "application/json" }] };
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
);
|
|
3419
|
+
const skillResourceTemplate = new import_mcp.ResourceTemplate("skills://{skillName}/{resourcePath}", { list: void 0 });
|
|
3420
|
+
server.registerResource(
|
|
3421
|
+
"skill_resource",
|
|
3422
|
+
skillResourceTemplate,
|
|
3423
|
+
{ description: "Skills L3\uFF1A\u8BFB\u53D6\u6280\u80FD\u5173\u8054\u7684\u6DF1\u5165\u53C2\u8003\u8D44\u6599\u6216\u914D\u7F6E\u6587\u4EF6\uFF08\u6309\u9700\u52A0\u8F7D\uFF09", mimeType: "text/plain" },
|
|
3424
|
+
async (uri, params) => {
|
|
3425
|
+
const { skillName, resourcePath } = params;
|
|
3426
|
+
try {
|
|
3427
|
+
const text = await adapter.readSkillResource(skillName, resourcePath);
|
|
3428
|
+
return { contents: [{ uri: uri.href, text: String(text), mimeType: "text/plain" }] };
|
|
3429
|
+
} catch (err) {
|
|
3430
|
+
return { contents: [{ uri: uri.href, text: `\u8BFB\u53D6\u8D44\u6E90\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`, mimeType: "text/plain" }] };
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
3435
|
+
var import_mcp, import_stdio, SERVER_NAME, SERVER_VERSION;
|
|
3436
|
+
var init_server = __esm({
|
|
3437
|
+
"src/mcp/server.ts"() {
|
|
3438
|
+
"use strict";
|
|
3439
|
+
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
3440
|
+
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3441
|
+
init_package();
|
|
3442
|
+
init_tools();
|
|
3443
|
+
SERVER_NAME = "webmcp-cli";
|
|
3444
|
+
SERVER_VERSION = package_default.version || "0.0.9";
|
|
3445
|
+
}
|
|
3446
|
+
});
|
|
3447
|
+
|
|
3448
|
+
// src/commands/agent.ts
|
|
3449
|
+
var agent_exports = {};
|
|
3450
|
+
__export(agent_exports, {
|
|
3451
|
+
handleAgentCommand: () => handleAgentCommand
|
|
3452
|
+
});
|
|
3453
|
+
async function handleAgentCommand(adapter, args) {
|
|
3454
|
+
const subCommand = args[0];
|
|
3455
|
+
const restArgs = args.slice(1);
|
|
3456
|
+
if (!subCommand || subCommand === "--help" || subCommand === "-h") {
|
|
3457
|
+
printAgentHelp();
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
if (subCommand === "run") {
|
|
3461
|
+
await handleAgentRun(adapter, restArgs);
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
console.error(`[\u9519\u8BEF] \u672A\u77E5\u5B50\u547D\u4EE4: agent ${subCommand}`);
|
|
3465
|
+
console.error("\u8FD0\u884C webmcp-cli --mode wxt agent --help \u67E5\u770B\u652F\u6301\u7684\u547D\u4EE4");
|
|
3466
|
+
process.exit(1);
|
|
3467
|
+
}
|
|
3468
|
+
async function handleAgentRun(adapter, args) {
|
|
3469
|
+
let instruction = "";
|
|
3470
|
+
let maxSteps;
|
|
3471
|
+
let tabId;
|
|
3472
|
+
const isJson = args.includes("--json");
|
|
3473
|
+
for (let i = 0; i < args.length; i++) {
|
|
3474
|
+
const arg = args[i];
|
|
3475
|
+
if (arg === "--max-steps" && args[i + 1]) {
|
|
3476
|
+
maxSteps = parseInt(args[++i], 10);
|
|
3477
|
+
} else if (arg === "--tab" && args[i + 1]) {
|
|
3478
|
+
tabId = parseInt(args[++i], 10);
|
|
3479
|
+
} else if (!arg.startsWith("-")) {
|
|
3480
|
+
instruction = arg;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
if (!instruction) {
|
|
3484
|
+
console.error('[\u9519\u8BEF] \u8BF7\u63D0\u4F9B\u4EFB\u52A1\u6307\u4EE4\u3002\u793A\u4F8B\uFF1Awebmcp-cli --mode wxt agent run "\u5E2E\u6211\u641C\u7D22\u6700\u65B0\u65B0\u95FB"');
|
|
3485
|
+
process.exit(1);
|
|
3486
|
+
}
|
|
3487
|
+
if (!isJson) {
|
|
3488
|
+
console.log(`\u{1F916} \u6B63\u5728\u59D4\u6258 Tiny Robot \u5B50\u4EE3\u7406\u6267\u884C\u4EFB\u52A1\uFF1A\u300C${instruction}\u300D`);
|
|
3489
|
+
if (maxSteps) console.log(` \u6700\u5927\u6B65\u6570\uFF1A${maxSteps}`);
|
|
3490
|
+
}
|
|
3491
|
+
try {
|
|
3492
|
+
const result = await adapter.runSubAgent(instruction, { tabId, maxSteps });
|
|
3493
|
+
if (isJson) {
|
|
3494
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3495
|
+
} else {
|
|
3496
|
+
console.log("\n\u2705 \u5B50\u4EE3\u7406\u6267\u884C\u5B8C\u6210\uFF1A");
|
|
3497
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3498
|
+
}
|
|
3499
|
+
} catch (err) {
|
|
3500
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3501
|
+
if (isJson) {
|
|
3502
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
3503
|
+
} else {
|
|
3504
|
+
console.error(`\u274C \u6267\u884C\u5931\u8D25\uFF1A${msg}`);
|
|
3505
|
+
}
|
|
3506
|
+
process.exit(1);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
function printAgentHelp() {
|
|
3510
|
+
console.log(`
|
|
3511
|
+
webmcp-cli --mode wxt agent - \u59D4\u6258 Chrome \u6269\u5C55\u5185 Tiny Robot \u5B50\u4EE3\u7406\u6267\u884C\u9AD8\u5C42\u4EFB\u52A1
|
|
3512
|
+
|
|
3513
|
+
\u7528\u6CD5\uFF1A
|
|
3514
|
+
webmcp-cli --mode wxt agent run "<\u6307\u4EE4>" \u59D4\u6258\u5B50\u4EE3\u7406\u81EA\u4E3B\u6267\u884C\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1
|
|
3515
|
+
|
|
3516
|
+
\u9009\u9879\uFF1A
|
|
3517
|
+
--max-steps <n> \u6700\u5927\u6267\u884C\u6B65\u6570\uFF08\u9ED8\u8BA4 8\uFF0C\u6700\u5927 20\uFF09
|
|
3518
|
+
--tab <tabId> \u6307\u5B9A\u76EE\u6807\u6807\u7B7E\u9875 ID
|
|
3519
|
+
--json \u8F93\u51FA JSON \u683C\u5F0F\u7ED3\u679C
|
|
3520
|
+
|
|
3521
|
+
\u793A\u4F8B\uFF1A
|
|
3522
|
+
webmcp-cli --mode wxt agent run "\u5E2E\u6211\u628A\u5F53\u524D\u5546\u54C1\u52A0\u5165\u8D2D\u7269\u8F66"
|
|
3523
|
+
webmcp-cli --mode wxt agent run "\u641C\u7D22\u5E76\u6536\u85CF\u524D3\u7BC7\u6587\u7AE0" --max-steps 10
|
|
3524
|
+
`.trim());
|
|
3525
|
+
}
|
|
3526
|
+
var init_agent = __esm({
|
|
3527
|
+
"src/commands/agent.ts"() {
|
|
3528
|
+
"use strict";
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
|
|
3532
|
+
// src/commands/skills.ts
|
|
3533
|
+
var skills_exports = {};
|
|
3534
|
+
__export(skills_exports, {
|
|
3535
|
+
handleSkillsCommand: () => handleSkillsCommand
|
|
3536
|
+
});
|
|
3537
|
+
async function handleSkillsCommand(adapter, args) {
|
|
3538
|
+
const subCommand = args[0];
|
|
3539
|
+
const restArgs = args.slice(1);
|
|
3540
|
+
const isJson = args.includes("--json");
|
|
3541
|
+
if (!subCommand || subCommand === "--help" || subCommand === "-h") {
|
|
3542
|
+
printSkillsHelp();
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
if (subCommand === "list") {
|
|
3546
|
+
await handleSkillsList(adapter, isJson);
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
if (subCommand === "get") {
|
|
3550
|
+
const skillName = restArgs.find((a) => !a.startsWith("-"));
|
|
3551
|
+
if (!skillName) {
|
|
3552
|
+
console.error("[\u9519\u8BEF] \u8BF7\u63D0\u4F9B\u6280\u80FD\u540D\u79F0\u3002\u793A\u4F8B\uFF1Awebmcp-cli --mode wxt skills get <skillName>");
|
|
3553
|
+
process.exit(1);
|
|
3554
|
+
}
|
|
3555
|
+
await handleSkillsGet(adapter, skillName, isJson);
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
console.error(`[\u9519\u8BEF] \u672A\u77E5\u5B50\u547D\u4EE4: skills ${subCommand}`);
|
|
3559
|
+
printSkillsHelp();
|
|
3560
|
+
process.exit(1);
|
|
3561
|
+
}
|
|
3562
|
+
async function handleSkillsList(adapter, isJson) {
|
|
3563
|
+
try {
|
|
3564
|
+
const result = await adapter.listSkills();
|
|
3565
|
+
if (isJson) {
|
|
3566
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3567
|
+
} else {
|
|
3568
|
+
const skills = result;
|
|
3569
|
+
if (!skills || skills.length === 0) {
|
|
3570
|
+
console.log("\uFF08\u5F53\u524D\u6269\u5C55\u672A\u6CE8\u518C\u4EFB\u4F55\u6280\u80FD\uFF09");
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
console.log(`
|
|
3574
|
+
\u{1F4DA} \u5DF2\u6CE8\u518C Skills\uFF08\u5171 ${skills.length} \u4E2A\uFF09\uFF1A
|
|
3575
|
+
`);
|
|
3576
|
+
for (const skill of skills) {
|
|
3577
|
+
console.log(` \u2022 ${skill.name}`);
|
|
3578
|
+
if (skill.description) {
|
|
3579
|
+
console.log(` ${skill.description.slice(0, 100)}${skill.description.length > 100 ? "..." : ""}`);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
console.log(`
|
|
3583
|
+
\u63D0\u793A\uFF1A\u4F7F\u7528 "webmcp-cli --mode wxt skills get <name>" \u67E5\u770B\u6280\u80FD\u8BE6\u60C5`);
|
|
3584
|
+
}
|
|
3585
|
+
} catch (err) {
|
|
3586
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3587
|
+
if (isJson) {
|
|
3588
|
+
console.log(JSON.stringify({ error: msg }));
|
|
3589
|
+
} else {
|
|
3590
|
+
console.error(`\u274C \u83B7\u53D6\u6280\u80FD\u5217\u8868\u5931\u8D25\uFF1A${msg}`);
|
|
3591
|
+
}
|
|
3592
|
+
process.exit(1);
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
async function handleSkillsGet(adapter, name, isJson) {
|
|
3596
|
+
try {
|
|
3597
|
+
const result = await adapter.getSkill(name);
|
|
3598
|
+
if (isJson) {
|
|
3599
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3600
|
+
} else {
|
|
3601
|
+
const skill = result;
|
|
3602
|
+
console.log(`
|
|
3603
|
+
\u{1F4D6} \u6280\u80FD\uFF1A${skill.name}`);
|
|
3604
|
+
if (skill.description) console.log(`\u63CF\u8FF0\uFF1A${skill.description}`);
|
|
3605
|
+
if (skill.instructions) {
|
|
3606
|
+
console.log("\n--- \u6307\u4EE4 ---");
|
|
3607
|
+
console.log(skill.instructions);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
} catch (err) {
|
|
3611
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3612
|
+
if (isJson) {
|
|
3613
|
+
console.log(JSON.stringify({ error: msg }));
|
|
3614
|
+
} else {
|
|
3615
|
+
console.error(`\u274C \u83B7\u53D6\u6280\u80FD\u8BE6\u60C5\u5931\u8D25\uFF1A${msg}`);
|
|
3616
|
+
}
|
|
3617
|
+
process.exit(1);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
function printSkillsHelp() {
|
|
3621
|
+
console.log(`
|
|
3622
|
+
webmcp-cli --mode wxt skills - \u67E5\u8BE2\u4E0E\u8BFB\u53D6\u6D4F\u89C8\u5668\u6269\u5C55\u4E2D\u6CE8\u518C\u7684 Skills \u6280\u80FD
|
|
3623
|
+
|
|
3624
|
+
\u7528\u6CD5\uFF1A
|
|
3625
|
+
webmcp-cli --mode wxt skills list \u5217\u51FA\u6240\u6709\u5DF2\u6CE8\u518C\u7684\u6280\u80FD
|
|
3626
|
+
webmcp-cli --mode wxt skills get <name> \u8BFB\u53D6\u6307\u5B9A\u6280\u80FD\u7684\u8BE6\u7EC6\u6267\u884C\u6307\u4EE4
|
|
3627
|
+
|
|
3628
|
+
\u9009\u9879\uFF1A
|
|
3629
|
+
--json \u8F93\u51FA JSON \u683C\u5F0F\u7ED3\u679C
|
|
3630
|
+
|
|
3631
|
+
\u793A\u4F8B\uFF1A
|
|
3632
|
+
webmcp-cli --mode wxt skills list
|
|
3633
|
+
webmcp-cli --mode wxt skills get order-management
|
|
3634
|
+
`.trim());
|
|
3635
|
+
}
|
|
3636
|
+
var init_skills = __esm({
|
|
3637
|
+
"src/commands/skills.ts"() {
|
|
3638
|
+
"use strict";
|
|
3639
|
+
}
|
|
3640
|
+
});
|
|
3641
|
+
|
|
3642
|
+
// src/bin.ts
|
|
3643
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
3644
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
3645
|
+
var import_os2 = __toESM(require("os"), 1);
|
|
3646
|
+
var import_commander = require("commander");
|
|
3647
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
3648
|
+
var import_fs4 = require("fs");
|
|
3649
|
+
var import_path4 = require("path");
|
|
3650
|
+
|
|
3651
|
+
// src/expand-file-refs.ts
|
|
3652
|
+
var import_fs = require("fs");
|
|
3653
|
+
var import_path = require("path");
|
|
3654
|
+
var import_url = require("url");
|
|
3655
|
+
var JSON_STRING_LITERAL = /"(?:[^"\\]|\\.)*"/g;
|
|
3656
|
+
var UNQUOTED_FILE_REF = /(?<![\\"])@(base64)?file:([^\s"',}\]]+)/g;
|
|
3657
|
+
var FILE_REF_PREFIX = /^@(base64)?file:(.+)$/s;
|
|
3658
|
+
function resolveFilePath(filePath, baseDir = process.cwd()) {
|
|
3659
|
+
let normalizedPath = filePath.trim();
|
|
3660
|
+
if (normalizedPath.startsWith('"') && normalizedPath.endsWith('"') || normalizedPath.startsWith("'") && normalizedPath.endsWith("'")) {
|
|
3661
|
+
normalizedPath = normalizedPath.slice(1, -1).trim();
|
|
3662
|
+
}
|
|
3663
|
+
if (normalizedPath.startsWith("file://")) {
|
|
3664
|
+
normalizedPath = (0, import_url.fileURLToPath)(normalizedPath);
|
|
3665
|
+
}
|
|
3666
|
+
if ((0, import_path.isAbsolute)(normalizedPath)) {
|
|
3667
|
+
return (0, import_path.normalize)(normalizedPath);
|
|
3668
|
+
}
|
|
3669
|
+
return (0, import_path.resolve)(baseDir, normalizedPath);
|
|
3670
|
+
}
|
|
3671
|
+
function decodeFilePath(rawPath) {
|
|
3672
|
+
return rawPath.replace(/\\\\/g, "\\");
|
|
3673
|
+
}
|
|
3674
|
+
function readFileRefContent(filePath, baseDir, base64) {
|
|
3675
|
+
const absPath = resolveFilePath(filePath, baseDir);
|
|
3676
|
+
let content;
|
|
3677
|
+
try {
|
|
3678
|
+
content = (0, import_fs.readFileSync)(absPath, "utf-8");
|
|
3679
|
+
} catch (e) {
|
|
3680
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}" (${absPath}): ${e.message}`);
|
|
3681
|
+
}
|
|
3682
|
+
return base64 ? Buffer.from(content, "utf-8").toString("base64") : content;
|
|
3683
|
+
}
|
|
3684
|
+
function expandFileRefValue(rawInner, baseDir) {
|
|
3685
|
+
const match = rawInner.match(FILE_REF_PREFIX);
|
|
3686
|
+
if (!match) {
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
const [, base64Flag, rawPath] = match;
|
|
3690
|
+
const filePath = decodeFilePath(rawPath);
|
|
3691
|
+
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
3692
|
+
return JSON.stringify(value);
|
|
3693
|
+
}
|
|
3694
|
+
function expandQuotedFileRefs(argsJson, baseDir) {
|
|
3695
|
+
return argsJson.replace(JSON_STRING_LITERAL, (jsonStringLiteral) => {
|
|
3696
|
+
const rawInner = jsonStringLiteral.slice(1, -1);
|
|
3697
|
+
const replacement = expandFileRefValue(rawInner, baseDir);
|
|
3698
|
+
return replacement ?? jsonStringLiteral;
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
function expandUnquotedFileRefs(argsJson, baseDir) {
|
|
3702
|
+
return argsJson.replace(UNQUOTED_FILE_REF, (match, base64Flag, filePath) => {
|
|
3703
|
+
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
3704
|
+
return JSON.stringify(value);
|
|
3705
|
+
});
|
|
3706
|
+
}
|
|
3707
|
+
function expandFileRefs(argsJson, baseDir = process.cwd()) {
|
|
3708
|
+
const withQuotedExpanded = expandQuotedFileRefs(argsJson, baseDir);
|
|
3709
|
+
return expandUnquotedFileRefs(withQuotedExpanded, baseDir);
|
|
3710
|
+
}
|
|
3711
|
+
function getFileBaseDir(fileOption) {
|
|
3712
|
+
if (!fileOption) {
|
|
3713
|
+
return process.cwd();
|
|
3714
|
+
}
|
|
3715
|
+
return (0, import_path.dirname)((0, import_path.resolve)(process.cwd(), fileOption));
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
// src/parse-run-args.ts
|
|
3719
|
+
var import_json5 = __toESM(require("json5"), 1);
|
|
3720
|
+
function cleanJsonString(str) {
|
|
3721
|
+
let cleaned = str.trim();
|
|
3722
|
+
if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
|
|
3723
|
+
cleaned = cleaned.slice(1, -1).trim();
|
|
3724
|
+
}
|
|
3725
|
+
return cleaned;
|
|
3726
|
+
}
|
|
3727
|
+
function looksLikeObjectBody(raw) {
|
|
3728
|
+
return /^("?[\w-]+"?\s*:)/.test(raw);
|
|
3729
|
+
}
|
|
3730
|
+
function joinShellArgs(args) {
|
|
3731
|
+
const cleaned = args.map(cleanJsonString);
|
|
3732
|
+
const first = cleaned[0] ?? "";
|
|
3733
|
+
if (first.startsWith("{")) {
|
|
3734
|
+
return cleaned.join(" ");
|
|
555
3735
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
3736
|
+
if (looksLikeObjectBody(first)) {
|
|
3737
|
+
let joined = cleaned.join(" ");
|
|
3738
|
+
if (!joined.startsWith("{")) {
|
|
3739
|
+
joined = `{${joined}}`;
|
|
561
3740
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const lastTabId = getLastActiveTabId();
|
|
565
|
-
if (lastTabId) {
|
|
566
|
-
const lastTarget = findPageTargetByTabId(browser, lastTabId);
|
|
567
|
-
if (lastTarget) {
|
|
568
|
-
targetPage = await lastTarget.page();
|
|
569
|
-
}
|
|
3741
|
+
if (!joined.endsWith("}")) {
|
|
3742
|
+
joined = `${joined}}`;
|
|
570
3743
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
activeTargetId = active.id;
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
} catch {
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
if (activeTargetId) {
|
|
592
|
-
for (const target of pageTargets) {
|
|
593
|
-
if (getTargetIdFromTarget(target) === activeTargetId) {
|
|
594
|
-
targetPage = await target.page();
|
|
595
|
-
break;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
} catch {
|
|
3744
|
+
return joined;
|
|
3745
|
+
}
|
|
3746
|
+
return cleaned.join(" ");
|
|
3747
|
+
}
|
|
3748
|
+
function insertMissingCommas(raw) {
|
|
3749
|
+
return raw.replace(/"(\s+)("?[\w-]+"?\s*:)/g, '",$2');
|
|
3750
|
+
}
|
|
3751
|
+
function quoteUnquotedStringValues(raw) {
|
|
3752
|
+
return raw.replace(
|
|
3753
|
+
/([{,]\s*(?:"([^"]+)"|([\w-]+))\s*:\s*)(?!"@(?:base64)?file:|"|true|false|null|-?\d|\{|\[)([^",}\]]+?)(?=\s*[,}])/g,
|
|
3754
|
+
(_match, prefix, _quotedKey, _unquotedKey, value) => {
|
|
3755
|
+
const trimmed = value.trim();
|
|
3756
|
+
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
|
|
3757
|
+
return prefix + trimmed;
|
|
600
3758
|
}
|
|
3759
|
+
return prefix + JSON.stringify(trimmed);
|
|
601
3760
|
}
|
|
602
|
-
|
|
603
|
-
const lastTarget = pageTargets[pageTargets.length - 1];
|
|
604
|
-
targetPage = await lastTarget.page();
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
if (!targetPage) {
|
|
608
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u76EE\u6807\u9875\u9762");
|
|
609
|
-
}
|
|
610
|
-
await injectWebMCPPolyfillAndTools(targetPage);
|
|
611
|
-
return targetPage;
|
|
3761
|
+
);
|
|
612
3762
|
}
|
|
613
|
-
|
|
614
|
-
|
|
3763
|
+
function normalizeShellDamagedJson(raw) {
|
|
3764
|
+
let normalized = raw.trim();
|
|
3765
|
+
normalized = insertMissingCommas(normalized);
|
|
3766
|
+
normalized = quoteUnquotedStringValues(normalized);
|
|
3767
|
+
return normalized;
|
|
615
3768
|
}
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const injectScriptPath = import_path2.default.resolve(__dirname, "inject-bundle.js");
|
|
623
|
-
if (!import_fs2.default.existsSync(injectScriptPath)) {
|
|
624
|
-
throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`);
|
|
3769
|
+
function buildArgsFromKeyValuePairs(args) {
|
|
3770
|
+
const obj = {};
|
|
3771
|
+
for (const pair of args) {
|
|
3772
|
+
const index = pair.indexOf("=");
|
|
3773
|
+
if (index === -1) {
|
|
3774
|
+
throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u8BF7\u4F7F\u7528 key=value\uFF0C\u6216 JSON / -f \u6587\u4EF6\u3002`);
|
|
625
3775
|
}
|
|
626
|
-
const
|
|
3776
|
+
const key = pair.substring(0, index).trim();
|
|
3777
|
+
let val = cleanJsonString(pair.substring(index + 1).trim());
|
|
627
3778
|
try {
|
|
628
|
-
|
|
629
|
-
} catch
|
|
630
|
-
|
|
631
|
-
throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
|
|
3779
|
+
obj[key] = JSON.parse(val);
|
|
3780
|
+
} catch {
|
|
3781
|
+
obj[key] = val;
|
|
632
3782
|
}
|
|
633
|
-
await page.waitForFunction(() => {
|
|
634
|
-
const mcp = navigator.modelContext;
|
|
635
|
-
return mcp && typeof mcp.registerTool === "function";
|
|
636
|
-
}, { timeout: 1e4 }).catch(() => {
|
|
637
|
-
console.warn("\u7B49\u5F85 WebMCP registerTool \u63A5\u53E3\u5C31\u7EEA\u8D85\u65F6\uFF0C\u6B63\u5728\u7EE7\u7EED\u6CE8\u5165...");
|
|
638
|
-
});
|
|
639
3783
|
}
|
|
640
|
-
|
|
3784
|
+
return JSON.stringify(obj);
|
|
641
3785
|
}
|
|
642
|
-
function
|
|
643
|
-
const
|
|
644
|
-
const toolsDir = import_path2.default.resolve(__dirname, "webmcp-tools");
|
|
645
|
-
if (!import_fs2.default.existsSync(toolsDir)) {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
let files = [];
|
|
3786
|
+
function parseArgsJson(raw) {
|
|
3787
|
+
const trimmed = cleanJsonString(raw);
|
|
649
3788
|
try {
|
|
650
|
-
|
|
651
|
-
} catch {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
return `webmcp-tools/${domain}.js`;
|
|
3789
|
+
return JSON.stringify(import_json5.default.parse(trimmed));
|
|
3790
|
+
} catch (firstError) {
|
|
3791
|
+
const repaired = normalizeShellDamagedJson(trimmed);
|
|
3792
|
+
try {
|
|
3793
|
+
return JSON.stringify(import_json5.default.parse(repaired));
|
|
3794
|
+
} catch {
|
|
3795
|
+
const hint = `\u63D0\u793A\uFF1Abash \u4E2D JSON \u5FC5\u987B\u7528\u5355\u5F15\u53F7\u5305\u88F9\uFF0C\u4F8B\u5982 '{"title":"\u6807\u9898","content":"@base64file:./article.md"}'\uFF1B\u6216\u4F7F\u7528 key=value\uFF1Atitle=\u6807\u9898 content=@base64file:./article.md\uFF1B\u6216\u4F7F\u7528 -f args.json\u3002`;
|
|
3796
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
3797
|
+
throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${message}
|
|
3798
|
+
${hint}`);
|
|
661
3799
|
}
|
|
662
3800
|
}
|
|
663
|
-
return null;
|
|
664
3801
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const bundleName = getToolsBundleName(hostname);
|
|
674
|
-
if (!bundleName) {
|
|
675
|
-
return;
|
|
3802
|
+
function prepareRunArgsJson(args, fileContent, baseDir) {
|
|
3803
|
+
if (fileContent !== void 0) {
|
|
3804
|
+
const trimmed = fileContent.trim();
|
|
3805
|
+
if (!trimmed) {
|
|
3806
|
+
throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3807
|
+
}
|
|
3808
|
+
const expanded2 = expandFileRefs(trimmed, baseDir);
|
|
3809
|
+
return parseArgsJson(expanded2);
|
|
676
3810
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return;
|
|
3811
|
+
if (args.length === 0) {
|
|
3812
|
+
return "{}";
|
|
680
3813
|
}
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
3814
|
+
const joined = joinShellArgs(args);
|
|
3815
|
+
let raw = "";
|
|
3816
|
+
if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
|
|
3817
|
+
raw = joined;
|
|
3818
|
+
} else if (args.every((arg) => arg.includes("="))) {
|
|
3819
|
+
raw = buildArgsFromKeyValuePairs(args);
|
|
3820
|
+
} else {
|
|
3821
|
+
raw = joined;
|
|
686
3822
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
try {
|
|
690
|
-
await page.evaluate(toolScript);
|
|
691
|
-
console.log(import_picocolors.default.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
|
|
692
|
-
} catch (err) {
|
|
693
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
694
|
-
console.warn(import_picocolors.default.yellow(`\u57DF\u540D\u5DE5\u5177\u6CE8\u5165\u5931\u8D25 (${hostname}): ${msg}`));
|
|
3823
|
+
if (!raw.trim()) {
|
|
3824
|
+
throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
|
|
695
3825
|
}
|
|
3826
|
+
const expanded = expandFileRefs(raw, baseDir);
|
|
3827
|
+
return parseArgsJson(expanded);
|
|
696
3828
|
}
|
|
697
3829
|
|
|
698
3830
|
// src/commands/state.ts
|
|
3831
|
+
init_browser();
|
|
699
3832
|
async function stateCommand({ tabid }) {
|
|
700
3833
|
const browser = await connectBrowser();
|
|
701
3834
|
try {
|
|
@@ -749,6 +3882,7 @@ async function stateCommand({ tabid }) {
|
|
|
749
3882
|
}
|
|
750
3883
|
|
|
751
3884
|
// src/commands/run.ts
|
|
3885
|
+
init_browser();
|
|
752
3886
|
async function runCommand({
|
|
753
3887
|
toolName,
|
|
754
3888
|
argsJson,
|
|
@@ -823,6 +3957,7 @@ async function setClipboard(content) {
|
|
|
823
3957
|
}
|
|
824
3958
|
|
|
825
3959
|
// src/commands/tabs.ts
|
|
3960
|
+
init_browser();
|
|
826
3961
|
function normalizeUrl(url) {
|
|
827
3962
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
828
3963
|
return "https://" + url;
|
|
@@ -944,75 +4079,26 @@ async function tabsForwardCommand(tabid) {
|
|
|
944
4079
|
}
|
|
945
4080
|
}
|
|
946
4081
|
|
|
947
|
-
// package.json
|
|
948
|
-
var package_default = {
|
|
949
|
-
name: "@opentiny/webmcp-cli",
|
|
950
|
-
version: "0.1.1-alpha.1",
|
|
951
|
-
type: "module",
|
|
952
|
-
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
953
|
-
repository: {
|
|
954
|
-
type: "git",
|
|
955
|
-
url: "git+https://github.com/opentiny/webmcp-sdk.git",
|
|
956
|
-
directory: "packages/webmcp-cli"
|
|
957
|
-
},
|
|
958
|
-
bugs: {
|
|
959
|
-
url: "https://github.com/opentiny/webmcp-sdk/issues"
|
|
960
|
-
},
|
|
961
|
-
main: "dist/index.js",
|
|
962
|
-
types: "dist/index.d.ts",
|
|
963
|
-
bin: {
|
|
964
|
-
"webmcp-cli": "./dist/bin.js"
|
|
965
|
-
},
|
|
966
|
-
files: [
|
|
967
|
-
"dist",
|
|
968
|
-
"README.md"
|
|
969
|
-
],
|
|
970
|
-
scripts: {
|
|
971
|
-
dev: "tsup --watch",
|
|
972
|
-
"build:inject": "node scripts/build-inject.mjs",
|
|
973
|
-
build: "tsup && pnpm run build:inject",
|
|
974
|
-
prepublishOnly: "pnpm run build",
|
|
975
|
-
"link:global": "npm install -g .",
|
|
976
|
-
start: "node dist/bin.js",
|
|
977
|
-
test: "vitest run",
|
|
978
|
-
"test:watch": "vitest",
|
|
979
|
-
"test:browser": "vitest run --config vitest.e2e.config.ts"
|
|
980
|
-
},
|
|
981
|
-
keywords: [
|
|
982
|
-
"chrome",
|
|
983
|
-
"puppeteer",
|
|
984
|
-
"cli"
|
|
985
|
-
],
|
|
986
|
-
license: "MIT",
|
|
987
|
-
author: "OpenTiny Team",
|
|
988
|
-
engines: {
|
|
989
|
-
node: ">=16"
|
|
990
|
-
},
|
|
991
|
-
publishConfig: {
|
|
992
|
-
access: "public"
|
|
993
|
-
},
|
|
994
|
-
dependencies: {
|
|
995
|
-
"@mcp-b/webmcp-polyfill": "catalog:",
|
|
996
|
-
"@opentiny/next-sdk": "workspace:^",
|
|
997
|
-
"@page-agent/page-controller": "catalog:",
|
|
998
|
-
clipboardy: "^5.3.1",
|
|
999
|
-
commander: "^11.1.0",
|
|
1000
|
-
json5: "^2.2.3",
|
|
1001
|
-
marked: "^15.0.7",
|
|
1002
|
-
picocolors: "^1.0.0",
|
|
1003
|
-
"puppeteer-core": "^22.0.0"
|
|
1004
|
-
},
|
|
1005
|
-
devDependencies: {
|
|
1006
|
-
"@types/node": "^20.0.0",
|
|
1007
|
-
esbuild: "^0.25.0",
|
|
1008
|
-
tsup: "^8.0.2",
|
|
1009
|
-
tsx: "^4.0.0",
|
|
1010
|
-
typescript: "^5.3.3",
|
|
1011
|
-
vitest: "^4.1.10"
|
|
1012
|
-
}
|
|
1013
|
-
};
|
|
1014
|
-
|
|
1015
4082
|
// src/bin.ts
|
|
4083
|
+
init_package();
|
|
4084
|
+
function extractMode(argv) {
|
|
4085
|
+
let mode = process.env.WEBMCP_MODE === "wxt" ? "wxt" : "cdp";
|
|
4086
|
+
const filteredArgv = [];
|
|
4087
|
+
for (let i = 0; i < argv.length; i++) {
|
|
4088
|
+
if (argv[i] === "--mode" && argv[i + 1]) {
|
|
4089
|
+
const val = argv[i + 1].toLowerCase();
|
|
4090
|
+
if (val === "wxt" || val === "cdp") mode = val;
|
|
4091
|
+
i++;
|
|
4092
|
+
} else if (argv[i].startsWith("--mode=")) {
|
|
4093
|
+
const val = argv[i].split("=")[1]?.toLowerCase();
|
|
4094
|
+
if (val === "wxt" || val === "cdp") mode = val;
|
|
4095
|
+
} else {
|
|
4096
|
+
filteredArgv.push(argv[i]);
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
return { mode, filteredArgv };
|
|
4100
|
+
}
|
|
4101
|
+
var { mode: CLI_MODE, filteredArgv: FILTERED_ARGV } = extractMode(process.argv);
|
|
1016
4102
|
var program = new import_commander.Command();
|
|
1017
4103
|
function parseTabId(id) {
|
|
1018
4104
|
if (!id) return void 0;
|
|
@@ -1174,11 +4260,22 @@ function handleCommandError(error, commandName, args) {
|
|
|
1174
4260
|
console.error(import_picocolors2.default.red(`Error executing ${commandName} command: ${msg}`));
|
|
1175
4261
|
process.exit(1);
|
|
1176
4262
|
}
|
|
1177
|
-
program.name("webmcp-cli").description(
|
|
4263
|
+
program.name("webmcp-cli").description(
|
|
4264
|
+
"WebMCP CLI \u2014 \u652F\u6301\u4E24\u79CD\u6D4F\u89C8\u5668\u8FDE\u63A5\u6A21\u5F0F\uFF1A\n --mode cdp\uFF08\u9ED8\u8BA4\uFF09: \u57FA\u4E8E Puppeteer + CDP \u76F4\u8FDE\u6D4F\u89C8\u5668\uFF08\u72EC\u7ACB profile\uFF09\n --mode wxt : \u57FA\u4E8E WebSocket + Chrome \u6269\u5C55\u6865\u63A5\uFF08\u590D\u7528\u7528\u6237\u6570\u636E\uFF09"
|
|
4265
|
+
).version(package_default.version).option("-w, --workspace <path>", "\u6307\u5B9A\u81EA\u5B9A\u4E49\u7684\u6D4F\u89C8\u5668\u5DE5\u4F5C\u7A7A\u95F4\uFF08\u7528\u6237\u914D\u7F6E\u76EE\u5F55\uFF09\u8DEF\u5F84\uFF0C\u4EC5 CDP \u6A21\u5F0F\u6709\u6548").option(
|
|
4266
|
+
"--ignore-certificate-errors",
|
|
4267
|
+
"\u5173\u95ED\u53D7\u63A7\u6D4F\u89C8\u5668\u7684 HTTPS \u8BC1\u4E66\u6821\u9A8C\uFF08\u81EA\u7B7E\u540D\u3001\u8FC7\u671F\u6216\u4E3B\u673A\u540D\u4E0D\u5339\u914D\uFF09\u3002\u4E5F\u53EF\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF WEBMCP_IGNORE_CERTIFICATE_ERRORS=1\u3002\u9ED8\u8BA4\u4ECD\u6821\u9A8C\u3002\u4EC5 CDP \u6A21\u5F0F\u6709\u6548"
|
|
4268
|
+
).hook("preAction", (thisCommand) => {
|
|
1178
4269
|
const opts = thisCommand.opts();
|
|
1179
4270
|
if (opts.workspace) {
|
|
1180
4271
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
1181
4272
|
}
|
|
4273
|
+
if (opts.ignoreCertificateErrors) {
|
|
4274
|
+
process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS = "1";
|
|
4275
|
+
}
|
|
4276
|
+
if (process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS === "1") {
|
|
4277
|
+
console.error(import_picocolors2.default.yellow("\u5F53\u524D\u53D7\u63A7\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED TLS \u8BC1\u4E66\u6821\u9A8C\u3002"));
|
|
4278
|
+
}
|
|
1182
4279
|
});
|
|
1183
4280
|
program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\u5BFC\u822A\u5143\u6570\u636E\uFF08url\u3001title\u3001webmcpTools\u3001\u6240\u6709\u9875\u7B7E\u5217\u8868\uFF09").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (options) => {
|
|
1184
4281
|
const args = { tabid: parseTabId(options.tabid) };
|
|
@@ -1286,5 +4383,174 @@ tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9
|
|
|
1286
4383
|
handleCommandError(error, "tabs forward", args);
|
|
1287
4384
|
}
|
|
1288
4385
|
});
|
|
1289
|
-
program.
|
|
4386
|
+
program.command("mcp").description("\u4EE5\u6807\u51C6 MCP Server \u6A21\u5F0F\u542F\u52A8\uFF08stdio \u4F20\u8F93\uFF0CCDP \u5E95\u5C42\uFF09\u3002\n\u4F7F\u7528 --mode wxt mcp \u5207\u6362\u5230 WXT \u6865\u63A5\u5E95\u5C42\u3002").option("--agent", "\u4EE5\u5B50\u4EE3\u7406\u6A21\u5F0F\u542F\u52A8\uFF08\u6536\u655B\u5DE5\u5177\u4E3A\u9AD8\u5C42\u59D4\u6258\u63A5\u53E3\uFF09").action(async (opts) => {
|
|
4387
|
+
try {
|
|
4388
|
+
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4389
|
+
await startMcpServer2({ mode: "cdp", agentMode: opts.agent ? "agent" : "tools" });
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
handleCommandError(error, "mcp", {});
|
|
4392
|
+
}
|
|
4393
|
+
});
|
|
4394
|
+
if (CLI_MODE === "wxt") {
|
|
4395
|
+
void handleWxtMode(FILTERED_ARGV.slice(2)).catch((error) => {
|
|
4396
|
+
handleCommandError(error, `wxt:${FILTERED_ARGV[2] ?? "help"}`, {});
|
|
4397
|
+
});
|
|
4398
|
+
} else {
|
|
4399
|
+
program.parse(FILTERED_ARGV);
|
|
4400
|
+
}
|
|
4401
|
+
async function handleWxtMode(args) {
|
|
4402
|
+
const command = args[0];
|
|
4403
|
+
if (command === "daemon" || command === "server") {
|
|
4404
|
+
const { runDaemonProcess: runDaemonProcess2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
4405
|
+
await runDaemonProcess2(args.slice(1));
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
if (command === "stop") {
|
|
4409
|
+
const { stopBridgeDaemon: stopBridgeDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
4410
|
+
const stopped = await stopBridgeDaemon2();
|
|
4411
|
+
console.log(stopped ? "\u2705 \u5DF2\u505C\u6B62\u540E\u53F0 WebSocket \u6865\u63A5\u670D\u52A1" : "\u2139\uFE0F \u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u540E\u53F0\u6865\u63A5\u670D\u52A1");
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
if (command === "token") {
|
|
4415
|
+
const subArgs = args.slice(1);
|
|
4416
|
+
let wsPort2;
|
|
4417
|
+
const cleanArgs = [];
|
|
4418
|
+
for (let i = 0; i < subArgs.length; i++) {
|
|
4419
|
+
if ((subArgs[i] === "--ws-port" || subArgs[i] === "--port") && subArgs[i + 1]) {
|
|
4420
|
+
wsPort2 = parseInt(subArgs[++i], 10) || void 0;
|
|
4421
|
+
} else {
|
|
4422
|
+
cleanArgs.push(subArgs[i]);
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
const action = cleanArgs[0];
|
|
4426
|
+
let tokenToSet;
|
|
4427
|
+
if (action === "set" && cleanArgs[1]) {
|
|
4428
|
+
tokenToSet = cleanArgs[1].trim();
|
|
4429
|
+
} else if (action && action !== "get" && !action.startsWith("-")) {
|
|
4430
|
+
tokenToSet = action.trim();
|
|
4431
|
+
}
|
|
4432
|
+
const { resolveOrCreateAuthToken: resolveOrCreateAuthToken2 } = await Promise.resolve().then(() => (init_bridge_client(), bridge_client_exports));
|
|
4433
|
+
if (tokenToSet) {
|
|
4434
|
+
resolveOrCreateAuthToken2(tokenToSet);
|
|
4435
|
+
console.log("\u2705 Token \u5DF2\u6210\u529F\u4FDD\u5B58\u81F3 ~/.robot-wxt/bridge-token");
|
|
4436
|
+
const { stopBridgeDaemon: stopBridgeDaemon2, ensureBridgeDaemon: ensureBridgeDaemon3 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
4437
|
+
await stopBridgeDaemon2(wsPort2);
|
|
4438
|
+
await ensureBridgeDaemon3({ token: tokenToSet, port: wsPort2 });
|
|
4439
|
+
console.log("\u{1F4A1} \u540E\u7EED\u6240\u6709 CLI \u547D\u4EE4\u4E0E MCP \u5DE5\u5177\u5747\u5C06\u81EA\u52A8\u4F7F\u7528\u6B64\u51ED\u636E\uFF0C\u65E0\u9700\u91CD\u590D\u8F93\u5165\u3002");
|
|
4440
|
+
return;
|
|
4441
|
+
}
|
|
4442
|
+
console.log(resolveOrCreateAuthToken2());
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
if (command === "mcp") {
|
|
4446
|
+
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4447
|
+
const isAgent = args.includes("--agent") || args.includes("--mode=agent");
|
|
4448
|
+
const tokenIdx = args.indexOf("--token");
|
|
4449
|
+
const token2 = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
|
|
4450
|
+
const wsPortIdx = args.indexOf("--ws-port");
|
|
4451
|
+
const wsPort2 = wsPortIdx !== -1 ? parseInt(args[wsPortIdx + 1], 10) || void 0 : void 0;
|
|
4452
|
+
await startMcpServer2({ mode: "wxt", agentMode: isAgent ? "agent" : "tools", token: token2, wsPort: wsPort2 });
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
if (!command || command === "--help" || command === "-h") {
|
|
4456
|
+
printWxtHelp();
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
let token;
|
|
4460
|
+
let wsPort;
|
|
4461
|
+
const commandArgs = [];
|
|
4462
|
+
for (let i = 1; i < args.length; i++) {
|
|
4463
|
+
if (args[i] === "--token" && args[i + 1]) {
|
|
4464
|
+
token = args[++i];
|
|
4465
|
+
} else if (args[i] === "--ws-port" && args[i + 1]) {
|
|
4466
|
+
wsPort = parseInt(args[++i], 10) || void 0;
|
|
4467
|
+
} else {
|
|
4468
|
+
commandArgs.push(args[i]);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
const { ensureBridgeDaemon: ensureBridgeDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
4472
|
+
const isJson = commandArgs.includes("--json");
|
|
4473
|
+
await ensureBridgeDaemon2({ token, port: wsPort, silent: isJson });
|
|
4474
|
+
const { WxtBrowserAdapter: WxtBrowserAdapter2 } = await Promise.resolve().then(() => (init_wxt_adapter(), wxt_adapter_exports));
|
|
4475
|
+
const adapter = new WxtBrowserAdapter2(token, wsPort);
|
|
4476
|
+
await adapter.ensureConnected();
|
|
4477
|
+
try {
|
|
4478
|
+
switch (command) {
|
|
4479
|
+
case "state": {
|
|
4480
|
+
const tabIdx = commandArgs.indexOf("--tab");
|
|
4481
|
+
const tabId = tabIdx !== -1 ? commandArgs[tabIdx + 1] : void 0;
|
|
4482
|
+
const result = await adapter.getState(tabId ? parseInt(tabId, 10) : void 0);
|
|
4483
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4484
|
+
break;
|
|
4485
|
+
}
|
|
4486
|
+
case "run": {
|
|
4487
|
+
const toolName = commandArgs.find((a) => !a.startsWith("-"));
|
|
4488
|
+
const argsStr = commandArgs.slice(commandArgs.indexOf(toolName) + 1).find((a) => !a.startsWith("-"));
|
|
4489
|
+
if (!toolName) {
|
|
4490
|
+
console.error("[\u9519\u8BEF] \u8BF7\u63D0\u4F9B\u5DE5\u5177\u540D\u79F0");
|
|
4491
|
+
process.exit(1);
|
|
4492
|
+
}
|
|
4493
|
+
const toolArgs = argsStr ? JSON.parse(argsStr) : {};
|
|
4494
|
+
const result = await adapter.callTool(toolName, toolArgs);
|
|
4495
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4496
|
+
break;
|
|
4497
|
+
}
|
|
4498
|
+
case "tabs": {
|
|
4499
|
+
const tabAction = commandArgs[0];
|
|
4500
|
+
const tabArgs = commandArgs.slice(1);
|
|
4501
|
+
const url = tabArgs.find((a) => a.startsWith("http"));
|
|
4502
|
+
const tabIdVal = tabArgs.find((a) => !a.startsWith("-") && !a.startsWith("http"));
|
|
4503
|
+
const result = await adapter.tabs(tabAction, {
|
|
4504
|
+
url,
|
|
4505
|
+
tabId: tabIdVal ? parseInt(tabIdVal, 10) : void 0
|
|
4506
|
+
});
|
|
4507
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4508
|
+
break;
|
|
4509
|
+
}
|
|
4510
|
+
case "agent": {
|
|
4511
|
+
const { handleAgentCommand: handleAgentCommand2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
4512
|
+
await handleAgentCommand2(adapter, commandArgs);
|
|
4513
|
+
break;
|
|
4514
|
+
}
|
|
4515
|
+
case "skills":
|
|
4516
|
+
case "skill": {
|
|
4517
|
+
const { handleSkillsCommand: handleSkillsCommand2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
4518
|
+
await handleSkillsCommand2(adapter, commandArgs);
|
|
4519
|
+
break;
|
|
4520
|
+
}
|
|
4521
|
+
default:
|
|
4522
|
+
console.error(`[\u9519\u8BEF] WXT \u6A21\u5F0F\u4E0B\u672A\u77E5\u547D\u4EE4: ${command}`);
|
|
4523
|
+
printWxtHelp();
|
|
4524
|
+
process.exit(1);
|
|
4525
|
+
}
|
|
4526
|
+
} finally {
|
|
4527
|
+
await adapter.dispose();
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
function printWxtHelp() {
|
|
4531
|
+
console.log(`
|
|
4532
|
+
webmcp-cli --mode wxt \u2014 WXT \u6865\u63A5\u6A21\u5F0F\uFF08\u901A\u8FC7 Chrome \u6269\u5C55\u8FDE\u63A5\uFF0C\u590D\u7528\u7528\u6237\u6570\u636E\uFF09
|
|
4533
|
+
|
|
4534
|
+
\u547D\u4EE4\uFF1A
|
|
4535
|
+
state \u83B7\u53D6\u5F53\u524D\u6D3B\u8DC3 tab \u4FE1\u606F\u4E0E\u9875\u9762\u5DE5\u5177\u5217\u8868
|
|
4536
|
+
run <tool> [args] \u8C03\u7528\u9875\u9762 WebMCP \u5DE5\u5177
|
|
4537
|
+
tabs <action> [...] \u7BA1\u7406\u6807\u7B7E\u9875\uFF08open/close/switch/back/forward\uFF09
|
|
4538
|
+
agent run "<\u6307\u4EE4>" \u59D4\u6258 Tiny Robot \u5B50\u4EE3\u7406\u81EA\u4E3B\u6267\u884C\u9AD8\u5C42\u4EFB\u52A1
|
|
4539
|
+
skills list \u5217\u51FA\u5DF2\u6CE8\u518C\u7684 Skills \u6280\u80FD
|
|
4540
|
+
skills get <name> \u8BFB\u53D6\u6307\u5B9A\u6280\u80FD\u7684\u8BE6\u7EC6\u6307\u4EE4
|
|
4541
|
+
token [get|set <t>] \u67E5\u770B\u6216\u4FDD\u5B58\u8BA4\u8BC1 Token
|
|
4542
|
+
stop \u505C\u6B62\u540E\u53F0 WebSocket \u6865\u63A5\u670D\u52A1
|
|
4543
|
+
mcp \u4EE5 MCP Server \u6A21\u5F0F\u542F\u52A8
|
|
4544
|
+
|
|
4545
|
+
\u9009\u9879\uFF1A
|
|
4546
|
+
--token <token> \u8BA4\u8BC1 Token\uFF08\u4F18\u5148\u4E8E ~/.robot-wxt/bridge-token\uFF09
|
|
4547
|
+
--ws-port <port> WS \u6865\u63A5\u7AEF\u53E3\uFF08\u9ED8\u8BA4 18999\uFF09
|
|
4548
|
+
--json \u8F93\u51FA JSON \u683C\u5F0F
|
|
4549
|
+
|
|
4550
|
+
\u793A\u4F8B\uFF1A
|
|
4551
|
+
webmcp-cli --mode wxt state
|
|
4552
|
+
webmcp-cli --mode wxt agent run "\u5E2E\u6211\u628A\u5546\u54C1\u52A0\u5165\u8D2D\u7269\u8F66"
|
|
4553
|
+
webmcp-cli --mode wxt mcp --agent
|
|
4554
|
+
`.trim());
|
|
4555
|
+
}
|
|
1290
4556
|
//# sourceMappingURL=bin.cjs.map
|