@opentiny/webmcp-cli 0.0.9 → 0.1.0-beta.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 +1 -0
- package/dist/bin.cjs +641 -130
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +635 -124
- 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 +108 -48
- package/dist/webmcp-tools/juejin.cn.js +86 -30
- package/package.json +11 -2
package/dist/bin.cjs
CHANGED
|
@@ -46,6 +46,38 @@ function shouldLaunchHeadless() {
|
|
|
46
46
|
if (process.env.WEBMCP_HEADLESS === "0") return false;
|
|
47
47
|
return process.env.WEBMCP_HEADLESS === "1" || process.env.CI === "true" || process.env.CI === "1";
|
|
48
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
|
+
}
|
|
49
81
|
function readActivePortFromProfile(userDataDir) {
|
|
50
82
|
try {
|
|
51
83
|
const content = import_fs2.default.readFileSync(import_path2.default.join(userDataDir, "DevToolsActivePort"), "utf-8");
|
|
@@ -140,7 +172,7 @@ async function findAvailableCdpUrl(addresses, retries = 2) {
|
|
|
140
172
|
async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
|
|
141
173
|
try {
|
|
142
174
|
return await promiseWithTimeout(
|
|
143
|
-
import_puppeteer_core.default.connect(
|
|
175
|
+
import_puppeteer_core.default.connect(buildPuppeteerConnectOptions(browserBaseUrl)),
|
|
144
176
|
1e4,
|
|
145
177
|
`puppeteer.connect to ${browserBaseUrl} timed out`
|
|
146
178
|
);
|
|
@@ -215,21 +247,11 @@ async function startBrowserInBackground() {
|
|
|
215
247
|
import_fs2.default.unlinkSync(import_path2.default.join(userDataDir, "DevToolsActivePort"));
|
|
216
248
|
} catch {
|
|
217
249
|
}
|
|
218
|
-
const launchArgs =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
];
|
|
224
|
-
if (shouldLaunchHeadless()) {
|
|
225
|
-
launchArgs.push(
|
|
226
|
-
"--headless=new",
|
|
227
|
-
"--no-sandbox",
|
|
228
|
-
"--disable-setuid-sandbox",
|
|
229
|
-
"--disable-gpu",
|
|
230
|
-
"--disable-dev-shm-usage"
|
|
231
|
-
);
|
|
232
|
-
}
|
|
250
|
+
const launchArgs = buildChromeLaunchArgs({
|
|
251
|
+
cdpPort,
|
|
252
|
+
userDataDir,
|
|
253
|
+
headless: shouldLaunchHeadless()
|
|
254
|
+
});
|
|
233
255
|
const child = (0, import_child_process.spawn)(
|
|
234
256
|
browserInfo.path,
|
|
235
257
|
launchArgs,
|
|
@@ -526,9 +548,17 @@ var init_package = __esm({
|
|
|
526
548
|
"package.json"() {
|
|
527
549
|
package_default = {
|
|
528
550
|
name: "@opentiny/webmcp-cli",
|
|
529
|
-
version: "0.0.
|
|
551
|
+
version: "0.1.0-beta.1",
|
|
530
552
|
type: "module",
|
|
531
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
|
+
},
|
|
532
562
|
main: "dist/index.js",
|
|
533
563
|
types: "dist/index.d.ts",
|
|
534
564
|
bin: {
|
|
@@ -557,7 +587,7 @@ var init_package = __esm({
|
|
|
557
587
|
license: "MIT",
|
|
558
588
|
author: "OpenTiny Team",
|
|
559
589
|
engines: {
|
|
560
|
-
node: ">=
|
|
590
|
+
node: ">=18.19.0"
|
|
561
591
|
},
|
|
562
592
|
publishConfig: {
|
|
563
593
|
access: "public"
|
|
@@ -569,6 +599,7 @@ var init_package = __esm({
|
|
|
569
599
|
"@page-agent/page-controller": "catalog:",
|
|
570
600
|
clipboardy: "^5.3.1",
|
|
571
601
|
commander: "^11.1.0",
|
|
602
|
+
execa: "^9.6.1",
|
|
572
603
|
json5: "^2.2.3",
|
|
573
604
|
marked: "^15.0.7",
|
|
574
605
|
picocolors: "^1.0.0",
|
|
@@ -797,12 +828,15 @@ var init_cdp_adapter = __esm({
|
|
|
797
828
|
});
|
|
798
829
|
|
|
799
830
|
// src/bridge/protocol.ts
|
|
800
|
-
|
|
831
|
+
function isTerminalMethod(method) {
|
|
832
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
833
|
+
}
|
|
834
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
801
835
|
var init_protocol = __esm({
|
|
802
836
|
"src/bridge/protocol.ts"() {
|
|
803
837
|
"use strict";
|
|
804
838
|
DEFAULT_WS_PORT = 18999;
|
|
805
|
-
PROTOCOL_VERSION = "1.
|
|
839
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
806
840
|
WS_CLOSE_CODE = {
|
|
807
841
|
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
808
842
|
REPLACED: 4e3,
|
|
@@ -836,8 +870,25 @@ var init_protocol = __esm({
|
|
|
836
870
|
SKILLS_GET: "skills/get",
|
|
837
871
|
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
838
872
|
SYSTEM_BASH: "system/bash",
|
|
839
|
-
SYSTEM_PICK_DIRECTORY: "system/pick_directory"
|
|
873
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
874
|
+
TERMINAL_OPEN: "terminal/open",
|
|
875
|
+
TERMINAL_INPUT: "terminal/input",
|
|
876
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
877
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
878
|
+
};
|
|
879
|
+
TERMINAL_EVENTS = {
|
|
880
|
+
OUTPUT: "notification/terminal_output",
|
|
881
|
+
EXIT: "notification/terminal_exit"
|
|
840
882
|
};
|
|
883
|
+
BASH_EVENTS = {
|
|
884
|
+
OUTPUT: "notification/bash_output"
|
|
885
|
+
};
|
|
886
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
887
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
888
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
889
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
890
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
891
|
+
]);
|
|
841
892
|
}
|
|
842
893
|
});
|
|
843
894
|
|
|
@@ -946,25 +997,56 @@ function resolveShell(forcePlatform) {
|
|
|
946
997
|
if (!forcePlatform) cachedShell = fallback;
|
|
947
998
|
return fallback;
|
|
948
999
|
}
|
|
949
|
-
function
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1000
|
+
function withoutCarriageReturn(buf) {
|
|
1001
|
+
if (!buf.includes(13)) return buf;
|
|
1002
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
1003
|
+
let size = 0;
|
|
1004
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
1005
|
+
const byte = buf[index];
|
|
1006
|
+
if (byte === 13) continue;
|
|
1007
|
+
out[size] = byte;
|
|
1008
|
+
size += 1;
|
|
1009
|
+
}
|
|
1010
|
+
return out.subarray(0, size);
|
|
1011
|
+
}
|
|
1012
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false) {
|
|
1013
|
+
const state = { chunks: [], truncated: false };
|
|
1014
|
+
if (!stream) return state;
|
|
1015
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1016
|
+
let bytes = 0;
|
|
1017
|
+
const emit = (buf) => {
|
|
1018
|
+
const text = decoder.write(buf);
|
|
1019
|
+
if (text) onText?.(text);
|
|
1020
|
+
};
|
|
1021
|
+
stream.on("data", (chunk) => {
|
|
1022
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1023
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1024
|
+
if (buf.length === 0) return;
|
|
1025
|
+
if (bytes >= maxBuffer) {
|
|
1026
|
+
state.truncated = true;
|
|
1027
|
+
return;
|
|
958
1028
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1029
|
+
const remaining = maxBuffer - bytes;
|
|
1030
|
+
if (buf.length <= remaining) {
|
|
1031
|
+
state.chunks.push(buf);
|
|
1032
|
+
bytes += buf.length;
|
|
1033
|
+
emit(buf);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const kept = buf.subarray(0, remaining);
|
|
1037
|
+
state.chunks.push(kept);
|
|
1038
|
+
bytes += remaining;
|
|
1039
|
+
state.truncated = true;
|
|
1040
|
+
emit(kept);
|
|
1041
|
+
});
|
|
1042
|
+
stream.on("end", () => {
|
|
1043
|
+
const rest = decoder.end();
|
|
1044
|
+
if (rest) onText?.(rest);
|
|
1045
|
+
});
|
|
1046
|
+
return state;
|
|
1047
|
+
}
|
|
1048
|
+
function decodeLimited(state) {
|
|
1049
|
+
return Buffer.concat(state.chunks).toString("utf8").trim();
|
|
968
1050
|
}
|
|
969
1051
|
async function executeBash(params) {
|
|
970
1052
|
const trimmedCommand = params.command?.trim();
|
|
@@ -983,83 +1065,66 @@ async function executeBash(params) {
|
|
|
983
1065
|
});
|
|
984
1066
|
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
985
1067
|
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
986
|
-
const startTime = Date.now();
|
|
987
1068
|
const shell = resolveShell();
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
} else {
|
|
1018
|
-
truncated = true;
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
child.stderr.on("data", (chunk) => {
|
|
1022
|
-
if (stderrBytes < maxBuffer) {
|
|
1023
|
-
const remaining = maxBuffer - stderrBytes;
|
|
1024
|
-
if (chunk.length <= remaining) {
|
|
1025
|
-
stderrChunks.push(chunk);
|
|
1026
|
-
stderrBytes += chunk.length;
|
|
1027
|
-
} else {
|
|
1028
|
-
stderrChunks.push(chunk.subarray(0, remaining));
|
|
1029
|
-
stderrBytes += remaining;
|
|
1030
|
-
truncated = true;
|
|
1031
|
-
}
|
|
1032
|
-
} else {
|
|
1033
|
-
truncated = true;
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
child.on("error", (err) => {
|
|
1037
|
-
clearTimeout(killTimeoutTimer);
|
|
1038
|
-
reject(err);
|
|
1039
|
-
});
|
|
1040
|
-
child.on("close", (exitCode, signal) => {
|
|
1041
|
-
clearTimeout(killTimeoutTimer);
|
|
1042
|
-
if (isKilled) return;
|
|
1043
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
1044
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
1045
|
-
resolve3({
|
|
1046
|
-
stdout,
|
|
1047
|
-
stderr,
|
|
1048
|
-
exitCode,
|
|
1049
|
-
signal,
|
|
1050
|
-
durationMs: Date.now() - startTime,
|
|
1051
|
-
...truncated ? { truncated: true } : {}
|
|
1052
|
-
});
|
|
1053
|
-
});
|
|
1069
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1070
|
+
const subprocess = useTty ? (0, import_execa.execa)("python3", ["-u", "-c", TTY_HELPER], {
|
|
1071
|
+
cwd,
|
|
1072
|
+
env: {
|
|
1073
|
+
...env,
|
|
1074
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1075
|
+
BASH_TTY_SHELL: shell
|
|
1076
|
+
},
|
|
1077
|
+
stdin: "ignore",
|
|
1078
|
+
buffer: false,
|
|
1079
|
+
encoding: "buffer",
|
|
1080
|
+
reject: false,
|
|
1081
|
+
timeout: normalizedTimeoutMs,
|
|
1082
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1083
|
+
windowsHide: true,
|
|
1084
|
+
stripFinalNewline: false
|
|
1085
|
+
}) : (0, import_execa.execa)(trimmedCommand, {
|
|
1086
|
+
shell,
|
|
1087
|
+
cwd,
|
|
1088
|
+
env,
|
|
1089
|
+
stdin: "ignore",
|
|
1090
|
+
buffer: false,
|
|
1091
|
+
encoding: "buffer",
|
|
1092
|
+
reject: false,
|
|
1093
|
+
timeout: normalizedTimeoutMs,
|
|
1094
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1095
|
+
windowsHide: true,
|
|
1096
|
+
stripFinalNewline: false
|
|
1054
1097
|
});
|
|
1098
|
+
const emit = (stream) => (text) => {
|
|
1099
|
+
params.onOutput?.({ stream, text });
|
|
1100
|
+
};
|
|
1101
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty);
|
|
1102
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1103
|
+
const result = await subprocess;
|
|
1104
|
+
if (result.timedOut) {
|
|
1105
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1106
|
+
}
|
|
1107
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1108
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1109
|
+
}
|
|
1110
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1111
|
+
return {
|
|
1112
|
+
stdout: decodeLimited(stdoutState),
|
|
1113
|
+
stderr: decodeLimited(stderrState),
|
|
1114
|
+
exitCode: result.exitCode ?? null,
|
|
1115
|
+
signal: result.signal ?? null,
|
|
1116
|
+
durationMs: result.durationMs ?? 0,
|
|
1117
|
+
...truncated ? { truncated: true } : {}
|
|
1118
|
+
};
|
|
1055
1119
|
}
|
|
1056
|
-
var import_node_fs2, import_node_path2,
|
|
1120
|
+
var 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;
|
|
1057
1121
|
var init_bash_executor = __esm({
|
|
1058
1122
|
"src/bridge/bash-executor.ts"() {
|
|
1059
1123
|
"use strict";
|
|
1060
1124
|
import_node_fs2 = __toESM(require("fs"), 1);
|
|
1061
1125
|
import_node_path2 = __toESM(require("path"), 1);
|
|
1062
|
-
|
|
1126
|
+
import_node_string_decoder = require("string_decoder");
|
|
1127
|
+
import_execa = require("execa");
|
|
1063
1128
|
init_workspace_manager();
|
|
1064
1129
|
BashTimeoutError = class extends Error {
|
|
1065
1130
|
code = "TIMEOUT";
|
|
@@ -1074,6 +1139,46 @@ var init_bash_executor = __esm({
|
|
|
1074
1139
|
MAX_TIMEOUT_MS = 6e5;
|
|
1075
1140
|
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1076
1141
|
cachedShell = null;
|
|
1142
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1143
|
+
TTY_HELPER = `
|
|
1144
|
+
import errno, os, sys
|
|
1145
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1146
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1147
|
+
if not cmd:
|
|
1148
|
+
sys.exit(127)
|
|
1149
|
+
import pty
|
|
1150
|
+
pid, fd = pty.fork()
|
|
1151
|
+
if pid == 0:
|
|
1152
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1153
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1154
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1155
|
+
try:
|
|
1156
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1157
|
+
except OSError:
|
|
1158
|
+
os._exit(127)
|
|
1159
|
+
while True:
|
|
1160
|
+
try:
|
|
1161
|
+
data = os.read(fd, 65536)
|
|
1162
|
+
except OSError as exc:
|
|
1163
|
+
if exc.errno == errno.EINTR:
|
|
1164
|
+
continue
|
|
1165
|
+
break
|
|
1166
|
+
if not data:
|
|
1167
|
+
break
|
|
1168
|
+
try:
|
|
1169
|
+
os.write(1, data)
|
|
1170
|
+
except OSError:
|
|
1171
|
+
break
|
|
1172
|
+
try:
|
|
1173
|
+
_, status = os.waitpid(pid, 0)
|
|
1174
|
+
except OSError:
|
|
1175
|
+
sys.exit(1)
|
|
1176
|
+
if os.WIFEXITED(status):
|
|
1177
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1178
|
+
if os.WIFSIGNALED(status):
|
|
1179
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1180
|
+
sys.exit(1)
|
|
1181
|
+
`;
|
|
1077
1182
|
}
|
|
1078
1183
|
});
|
|
1079
1184
|
|
|
@@ -1096,7 +1201,7 @@ function pickDirectoryMac(title, defaultPath) {
|
|
|
1096
1201
|
const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
|
|
1097
1202
|
const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
|
|
1098
1203
|
return new Promise((resolve3, reject) => {
|
|
1099
|
-
const child = (0,
|
|
1204
|
+
const child = (0, import_node_child_process.spawn)("osascript", ["-e", appleScript], {
|
|
1100
1205
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1101
1206
|
});
|
|
1102
1207
|
let stdout = "";
|
|
@@ -1214,7 +1319,7 @@ $result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
|
|
|
1214
1319
|
if ($result) { Write-Output $result }
|
|
1215
1320
|
`;
|
|
1216
1321
|
return new Promise((resolve3, reject) => {
|
|
1217
|
-
const child = (0,
|
|
1322
|
+
const child = (0, import_node_child_process.spawn)("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
|
|
1218
1323
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1219
1324
|
windowsHide: true
|
|
1220
1325
|
});
|
|
@@ -1245,7 +1350,7 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1245
1350
|
args.push(`--filename=${defaultPath}`);
|
|
1246
1351
|
}
|
|
1247
1352
|
return new Promise((resolve3, reject) => {
|
|
1248
|
-
const child = (0,
|
|
1353
|
+
const child = (0, import_node_child_process.spawn)("zenity", args, {
|
|
1249
1354
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1250
1355
|
});
|
|
1251
1356
|
let stdout = "";
|
|
@@ -1277,11 +1382,349 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1277
1382
|
});
|
|
1278
1383
|
});
|
|
1279
1384
|
}
|
|
1280
|
-
var
|
|
1385
|
+
var import_node_child_process;
|
|
1281
1386
|
var init_directory_picker = __esm({
|
|
1282
1387
|
"src/bridge/directory-picker.ts"() {
|
|
1388
|
+
"use strict";
|
|
1389
|
+
import_node_child_process = require("child_process");
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// src/bridge/pty-backend.ts
|
|
1394
|
+
function spawnHostPty(options) {
|
|
1395
|
+
if (process.platform === "win32") {
|
|
1396
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u4EA4\u4E92\u5F0F\u7EC8\u7AEF");
|
|
1397
|
+
}
|
|
1398
|
+
const shell = options.shell || resolveShell();
|
|
1399
|
+
const child = (0, import_node_child_process2.spawn)("python3", ["-u", "-c", PTY_HELPER], {
|
|
1400
|
+
cwd: options.cwd,
|
|
1401
|
+
env: {
|
|
1402
|
+
...process.env,
|
|
1403
|
+
TERM: "xterm-256color",
|
|
1404
|
+
TERMINAL_SHELL: shell,
|
|
1405
|
+
TERMINAL_ROWS: String(options.rows),
|
|
1406
|
+
TERMINAL_COLS: String(options.cols)
|
|
1407
|
+
},
|
|
1408
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
1409
|
+
});
|
|
1410
|
+
const pending = [];
|
|
1411
|
+
let dataCb = null;
|
|
1412
|
+
let exitCb = null;
|
|
1413
|
+
let pendingExit = null;
|
|
1414
|
+
let settled = false;
|
|
1415
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1416
|
+
let stderrText = "";
|
|
1417
|
+
const emitData = (data) => {
|
|
1418
|
+
if (!data) return;
|
|
1419
|
+
if (dataCb) dataCb(data);
|
|
1420
|
+
else pending.push(data);
|
|
1421
|
+
};
|
|
1422
|
+
const finish = (info) => {
|
|
1423
|
+
if (settled) return;
|
|
1424
|
+
settled = true;
|
|
1425
|
+
if (stderrText.trim()) emitData(`\r
|
|
1426
|
+
${stderrText.trim()}\r
|
|
1427
|
+
`);
|
|
1428
|
+
if (exitCb) exitCb(info);
|
|
1429
|
+
else pendingExit = info;
|
|
1430
|
+
};
|
|
1431
|
+
child.stdout.on("data", (buf) => {
|
|
1432
|
+
emitData(decoder.decode(buf, { stream: true }));
|
|
1433
|
+
});
|
|
1434
|
+
child.stderr.on("data", (buf) => {
|
|
1435
|
+
stderrText += buf.toString("utf8");
|
|
1436
|
+
});
|
|
1437
|
+
child.on("error", (err) => {
|
|
1438
|
+
emitData(`\r
|
|
1439
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${err.message}\r
|
|
1440
|
+
`);
|
|
1441
|
+
finish({ exitCode: null, signal: null });
|
|
1442
|
+
});
|
|
1443
|
+
child.on("exit", (code, signal) => {
|
|
1444
|
+
const rest = decoder.decode();
|
|
1445
|
+
if (rest) emitData(rest);
|
|
1446
|
+
finish({ exitCode: code, signal: signal ?? null });
|
|
1447
|
+
});
|
|
1448
|
+
const control = child.stdio[3];
|
|
1449
|
+
const writeControl = (payload) => {
|
|
1450
|
+
if (!control || !("write" in control)) return;
|
|
1451
|
+
try {
|
|
1452
|
+
;
|
|
1453
|
+
control.write(`${JSON.stringify(payload)}
|
|
1454
|
+
`);
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
return {
|
|
1459
|
+
write(data) {
|
|
1460
|
+
try {
|
|
1461
|
+
child.stdin.write(data);
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
resize(cols, rows) {
|
|
1466
|
+
writeControl({ type: "resize", cols, rows });
|
|
1467
|
+
},
|
|
1468
|
+
kill() {
|
|
1469
|
+
writeControl({ type: "kill" });
|
|
1470
|
+
try {
|
|
1471
|
+
child.kill("SIGTERM");
|
|
1472
|
+
} catch {
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
onData(cb) {
|
|
1476
|
+
dataCb = cb;
|
|
1477
|
+
for (const chunk of pending) cb(chunk);
|
|
1478
|
+
pending.length = 0;
|
|
1479
|
+
},
|
|
1480
|
+
onExit(cb) {
|
|
1481
|
+
exitCb = cb;
|
|
1482
|
+
if (pendingExit) cb(pendingExit);
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
var import_node_child_process2, PTY_HELPER;
|
|
1487
|
+
var init_pty_backend = __esm({
|
|
1488
|
+
"src/bridge/pty-backend.ts"() {
|
|
1283
1489
|
"use strict";
|
|
1284
1490
|
import_node_child_process2 = require("child_process");
|
|
1491
|
+
init_bash_executor();
|
|
1492
|
+
PTY_HELPER = `
|
|
1493
|
+
import errno, fcntl, json, os, pty, select, signal, struct, sys, termios
|
|
1494
|
+
|
|
1495
|
+
shell = os.environ.get("TERMINAL_SHELL") or "/bin/bash"
|
|
1496
|
+
rows = int(os.environ.get("TERMINAL_ROWS") or "24")
|
|
1497
|
+
cols = int(os.environ.get("TERMINAL_COLS") or "80")
|
|
1498
|
+
control_fd = 3
|
|
1499
|
+
|
|
1500
|
+
def set_winsize(fd, next_rows, next_cols):
|
|
1501
|
+
winsize = struct.pack("HHHH", int(next_rows), int(next_cols), 0, 0)
|
|
1502
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
|
1503
|
+
|
|
1504
|
+
pid, master = pty.fork()
|
|
1505
|
+
if pid == 0:
|
|
1506
|
+
try:
|
|
1507
|
+
os.close(control_fd)
|
|
1508
|
+
except OSError:
|
|
1509
|
+
pass
|
|
1510
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1511
|
+
try:
|
|
1512
|
+
os.execvp(shell, [shell, "-i"])
|
|
1513
|
+
except OSError:
|
|
1514
|
+
os._exit(127)
|
|
1515
|
+
|
|
1516
|
+
def control_loop():
|
|
1517
|
+
try:
|
|
1518
|
+
reader = os.fdopen(control_fd, "r", buffering=1, encoding="utf-8", errors="replace")
|
|
1519
|
+
except OSError:
|
|
1520
|
+
return
|
|
1521
|
+
for line in reader:
|
|
1522
|
+
text = line.strip()
|
|
1523
|
+
if not text:
|
|
1524
|
+
continue
|
|
1525
|
+
try:
|
|
1526
|
+
msg = json.loads(text)
|
|
1527
|
+
except json.JSONDecodeError:
|
|
1528
|
+
continue
|
|
1529
|
+
kind = msg.get("type")
|
|
1530
|
+
if kind == "resize":
|
|
1531
|
+
try:
|
|
1532
|
+
set_winsize(master, msg.get("rows") or rows, msg.get("cols") or cols)
|
|
1533
|
+
os.kill(pid, signal.SIGWINCH)
|
|
1534
|
+
except OSError:
|
|
1535
|
+
pass
|
|
1536
|
+
elif kind == "kill":
|
|
1537
|
+
try:
|
|
1538
|
+
os.kill(pid, signal.SIGHUP)
|
|
1539
|
+
except OSError:
|
|
1540
|
+
pass
|
|
1541
|
+
os._exit(0)
|
|
1542
|
+
|
|
1543
|
+
import threading
|
|
1544
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
1545
|
+
|
|
1546
|
+
try:
|
|
1547
|
+
set_winsize(master, rows, cols)
|
|
1548
|
+
except OSError:
|
|
1549
|
+
pass
|
|
1550
|
+
|
|
1551
|
+
stdin_fd = sys.stdin.fileno()
|
|
1552
|
+
stdout_fd = sys.stdout.fileno()
|
|
1553
|
+
|
|
1554
|
+
while True:
|
|
1555
|
+
try:
|
|
1556
|
+
readable, _, _ = select.select([master, stdin_fd], [], [])
|
|
1557
|
+
except InterruptedError:
|
|
1558
|
+
continue
|
|
1559
|
+
if master in readable:
|
|
1560
|
+
try:
|
|
1561
|
+
data = os.read(master, 65536)
|
|
1562
|
+
except OSError as err:
|
|
1563
|
+
if err.errno == errno.EIO:
|
|
1564
|
+
break
|
|
1565
|
+
raise
|
|
1566
|
+
if not data:
|
|
1567
|
+
break
|
|
1568
|
+
os.write(stdout_fd, data)
|
|
1569
|
+
if stdin_fd in readable:
|
|
1570
|
+
try:
|
|
1571
|
+
data = os.read(stdin_fd, 65536)
|
|
1572
|
+
except OSError:
|
|
1573
|
+
break
|
|
1574
|
+
if not data:
|
|
1575
|
+
break
|
|
1576
|
+
try:
|
|
1577
|
+
os.write(master, data)
|
|
1578
|
+
except OSError:
|
|
1579
|
+
break
|
|
1580
|
+
|
|
1581
|
+
try:
|
|
1582
|
+
_, status = os.waitpid(pid, 0)
|
|
1583
|
+
except ChildProcessError:
|
|
1584
|
+
status = 0
|
|
1585
|
+
code = os.waitstatus_to_exitcode(status)
|
|
1586
|
+
os._exit(0 if code < 0 else code)
|
|
1587
|
+
`;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// src/bridge/terminal-session.ts
|
|
1592
|
+
function clampDimension(value, fallback) {
|
|
1593
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1594
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1595
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1596
|
+
}
|
|
1597
|
+
var import_node_crypto, OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TerminalSessionManager;
|
|
1598
|
+
var init_terminal_session = __esm({
|
|
1599
|
+
"src/bridge/terminal-session.ts"() {
|
|
1600
|
+
"use strict";
|
|
1601
|
+
import_node_crypto = require("crypto");
|
|
1602
|
+
init_workspace_manager();
|
|
1603
|
+
init_pty_backend();
|
|
1604
|
+
init_protocol();
|
|
1605
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1606
|
+
MAX_INPUT = 64 * 1024;
|
|
1607
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1608
|
+
TerminalSessionManager = class {
|
|
1609
|
+
session = null;
|
|
1610
|
+
buffer = "";
|
|
1611
|
+
timer = null;
|
|
1612
|
+
send;
|
|
1613
|
+
spawnPty;
|
|
1614
|
+
coalesceMs;
|
|
1615
|
+
constructor(options) {
|
|
1616
|
+
this.send = options.send;
|
|
1617
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1618
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1619
|
+
}
|
|
1620
|
+
open(params = {}) {
|
|
1621
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1622
|
+
const cols = clampDimension(params.cols, 80);
|
|
1623
|
+
const rows = clampDimension(params.rows, 24);
|
|
1624
|
+
this.closeActive();
|
|
1625
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
1626
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1627
|
+
this.session = { id, pty, cwd };
|
|
1628
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1629
|
+
pty.onExit((info) => {
|
|
1630
|
+
if (this.session?.id !== id) return;
|
|
1631
|
+
this.flush(id);
|
|
1632
|
+
this.session = null;
|
|
1633
|
+
this.send({
|
|
1634
|
+
jsonrpc: "2.0",
|
|
1635
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1636
|
+
params: {
|
|
1637
|
+
sessionId: id,
|
|
1638
|
+
exitCode: info.exitCode,
|
|
1639
|
+
signal: info.signal
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
});
|
|
1643
|
+
return { sessionId: id, cwd };
|
|
1644
|
+
}
|
|
1645
|
+
input(params) {
|
|
1646
|
+
const session = this.requireSession(params.sessionId);
|
|
1647
|
+
if (typeof params.data !== "string") {
|
|
1648
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1649
|
+
}
|
|
1650
|
+
if (params.data.length > MAX_INPUT) {
|
|
1651
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1652
|
+
}
|
|
1653
|
+
session.pty.write(params.data);
|
|
1654
|
+
return { ok: true };
|
|
1655
|
+
}
|
|
1656
|
+
resize(params) {
|
|
1657
|
+
const session = this.requireSession(params.sessionId);
|
|
1658
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1659
|
+
return { ok: true };
|
|
1660
|
+
}
|
|
1661
|
+
close(params = {}) {
|
|
1662
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1663
|
+
return { ok: true };
|
|
1664
|
+
}
|
|
1665
|
+
this.closeActive();
|
|
1666
|
+
return { ok: true };
|
|
1667
|
+
}
|
|
1668
|
+
disposeAll() {
|
|
1669
|
+
this.closeActive();
|
|
1670
|
+
}
|
|
1671
|
+
requireSession(sessionId) {
|
|
1672
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1673
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1674
|
+
}
|
|
1675
|
+
return this.session;
|
|
1676
|
+
}
|
|
1677
|
+
closeActive() {
|
|
1678
|
+
const current = this.session;
|
|
1679
|
+
this.session = null;
|
|
1680
|
+
this.buffer = "";
|
|
1681
|
+
if (this.timer) {
|
|
1682
|
+
clearTimeout(this.timer);
|
|
1683
|
+
this.timer = null;
|
|
1684
|
+
}
|
|
1685
|
+
current?.pty.kill();
|
|
1686
|
+
}
|
|
1687
|
+
pushOutput(sessionId, chunk) {
|
|
1688
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1689
|
+
this.buffer += chunk;
|
|
1690
|
+
this.flushFullChunks(sessionId);
|
|
1691
|
+
if (!this.buffer) return;
|
|
1692
|
+
if (this.coalesceMs <= 0) {
|
|
1693
|
+
this.flush(sessionId);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (!this.timer) {
|
|
1697
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
flushFullChunks(sessionId) {
|
|
1701
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1702
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1703
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1704
|
+
this.emitOutput(sessionId, data);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
flush(sessionId) {
|
|
1708
|
+
if (this.timer) {
|
|
1709
|
+
clearTimeout(this.timer);
|
|
1710
|
+
this.timer = null;
|
|
1711
|
+
}
|
|
1712
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1713
|
+
this.buffer = "";
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const data = this.buffer;
|
|
1717
|
+
this.buffer = "";
|
|
1718
|
+
this.emitOutput(sessionId, data);
|
|
1719
|
+
}
|
|
1720
|
+
emitOutput(sessionId, data) {
|
|
1721
|
+
this.send({
|
|
1722
|
+
jsonrpc: "2.0",
|
|
1723
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1724
|
+
params: { sessionId, data }
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1285
1728
|
}
|
|
1286
1729
|
});
|
|
1287
1730
|
|
|
@@ -1319,7 +1762,7 @@ function resolveOrCreateAuthToken(explicitToken) {
|
|
|
1319
1762
|
}
|
|
1320
1763
|
} catch {
|
|
1321
1764
|
}
|
|
1322
|
-
const newToken = (0,
|
|
1765
|
+
const newToken = (0, import_node_crypto2.randomBytes)(32).toString("hex");
|
|
1323
1766
|
try {
|
|
1324
1767
|
if (!import_node_fs3.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1325
1768
|
import_node_fs3.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
@@ -1333,10 +1776,10 @@ function parseMajorVersion(v) {
|
|
|
1333
1776
|
return parseInt(v.split(".")[0] ?? "0", 10);
|
|
1334
1777
|
}
|
|
1335
1778
|
function deriveSharedSecret(tokenOrExtensionId, hmacSalt) {
|
|
1336
|
-
return (0,
|
|
1779
|
+
return (0, import_node_crypto2.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
|
|
1337
1780
|
}
|
|
1338
1781
|
function computeHmac(secret, challenge) {
|
|
1339
|
-
return (0,
|
|
1782
|
+
return (0, import_node_crypto2.createHmac)("sha256", secret).update(challenge).digest("hex");
|
|
1340
1783
|
}
|
|
1341
1784
|
function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
1342
1785
|
if (!actualHex || typeof actualHex !== "string") return false;
|
|
@@ -1344,7 +1787,7 @@ function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
|
1344
1787
|
const actualBuf = Buffer.from(actualHex, "hex");
|
|
1345
1788
|
const expectedBuf = Buffer.from(expectedHex, "hex");
|
|
1346
1789
|
if (actualBuf.length !== expectedBuf.length) return false;
|
|
1347
|
-
return (0,
|
|
1790
|
+
return (0, import_node_crypto2.timingSafeEqual)(actualBuf, expectedBuf);
|
|
1348
1791
|
} catch {
|
|
1349
1792
|
return false;
|
|
1350
1793
|
}
|
|
@@ -1355,19 +1798,20 @@ function getKillPortHint(port) {
|
|
|
1355
1798
|
}
|
|
1356
1799
|
return `lsof -ti:${port} | xargs kill -9`;
|
|
1357
1800
|
}
|
|
1358
|
-
var import_ws, import_node_events,
|
|
1801
|
+
var import_ws, import_node_events, import_node_crypto2, import_node_fs3, import_node_path3, import_node_os2, WS_PORT, REQUEST_TIMEOUT_MS, HANDSHAKE_TIMEOUT_MS, WS_OPEN, TOKEN_FILE_DIR, TOKEN_FILE_PATH, ExtensionBridgeClient;
|
|
1359
1802
|
var init_bridge_client = __esm({
|
|
1360
1803
|
"src/bridge/bridge-client.ts"() {
|
|
1361
1804
|
"use strict";
|
|
1362
1805
|
import_ws = require("ws");
|
|
1363
1806
|
import_node_events = require("events");
|
|
1364
|
-
|
|
1807
|
+
import_node_crypto2 = require("crypto");
|
|
1365
1808
|
import_node_fs3 = __toESM(require("fs"), 1);
|
|
1366
1809
|
import_node_path3 = __toESM(require("path"), 1);
|
|
1367
1810
|
import_node_os2 = __toESM(require("os"), 1);
|
|
1368
1811
|
init_protocol();
|
|
1369
1812
|
init_bash_executor();
|
|
1370
1813
|
init_directory_picker();
|
|
1814
|
+
init_terminal_session();
|
|
1371
1815
|
WS_PORT = DEFAULT_WS_PORT;
|
|
1372
1816
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1373
1817
|
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
@@ -1389,6 +1833,7 @@ var init_bridge_client = __esm({
|
|
|
1389
1833
|
pending = /* @__PURE__ */ new Map();
|
|
1390
1834
|
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1391
1835
|
destroyed = false;
|
|
1836
|
+
terminalSessions = null;
|
|
1392
1837
|
idCounter = 0;
|
|
1393
1838
|
isClientMode = false;
|
|
1394
1839
|
explicitAuthToken;
|
|
@@ -1643,8 +2088,8 @@ var init_bridge_client = __esm({
|
|
|
1643
2088
|
this.emit("error", err);
|
|
1644
2089
|
});
|
|
1645
2090
|
this.wss.on("connection", (socket) => {
|
|
1646
|
-
const challenge = (0,
|
|
1647
|
-
const hmacSalt = (0,
|
|
2091
|
+
const challenge = (0, import_node_crypto2.randomUUID)();
|
|
2092
|
+
const hmacSalt = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
1648
2093
|
const authState = {
|
|
1649
2094
|
authenticated: false,
|
|
1650
2095
|
challenge,
|
|
@@ -1685,6 +2130,7 @@ var init_bridge_client = __esm({
|
|
|
1685
2130
|
}
|
|
1686
2131
|
if (socket === this.extensionSocket) {
|
|
1687
2132
|
this.extensionSocket = null;
|
|
2133
|
+
this.terminalSessions?.disposeAll();
|
|
1688
2134
|
this.emit("disconnected");
|
|
1689
2135
|
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
1690
2136
|
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
@@ -1731,6 +2177,16 @@ var init_bridge_client = __esm({
|
|
|
1731
2177
|
return;
|
|
1732
2178
|
}
|
|
1733
2179
|
if (msg.method && msg.id) {
|
|
2180
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2181
|
+
this.sendJsonRpcResponse(socket, {
|
|
2182
|
+
id: msg.id,
|
|
2183
|
+
error: {
|
|
2184
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2185
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
1734
2190
|
if (socket === this.extensionSocket) {
|
|
1735
2191
|
await this.handleExtensionSystemRpc(socket, msg);
|
|
1736
2192
|
return;
|
|
@@ -1894,9 +2350,6 @@ var init_bridge_client = __esm({
|
|
|
1894
2350
|
systemRpcHandlers = Object.assign(
|
|
1895
2351
|
/* @__PURE__ */ Object.create(null),
|
|
1896
2352
|
{
|
|
1897
|
-
[BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
|
|
1898
|
-
return executeBash(params || {});
|
|
1899
|
-
},
|
|
1900
2353
|
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
1901
2354
|
const selectedPath = await pickDirectory({
|
|
1902
2355
|
title: params?.title || void 0,
|
|
@@ -1907,11 +2360,43 @@ var init_bridge_client = __esm({
|
|
|
1907
2360
|
selectedPath,
|
|
1908
2361
|
canceled: selectedPath === null
|
|
1909
2362
|
};
|
|
1910
|
-
}
|
|
2363
|
+
},
|
|
2364
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2365
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2366
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2367
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
1911
2368
|
}
|
|
1912
2369
|
);
|
|
2370
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2371
|
+
if (socket.readyState === WS_OPEN) {
|
|
2372
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async executeHostBash(socket, id, params) {
|
|
2376
|
+
try {
|
|
2377
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2378
|
+
const result = await executeBash({
|
|
2379
|
+
...requestParams,
|
|
2380
|
+
onOutput: (chunk) => {
|
|
2381
|
+
if (!chunk.text) return;
|
|
2382
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2383
|
+
id,
|
|
2384
|
+
stream: chunk.stream,
|
|
2385
|
+
text: chunk.text
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
1913
2394
|
async handleExtensionSystemRpc(socket, msg) {
|
|
1914
2395
|
const { id, method, params } = msg;
|
|
2396
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2397
|
+
await this.executeHostBash(socket, id, params);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
1915
2400
|
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
1916
2401
|
if (!handler || typeof handler !== "function") {
|
|
1917
2402
|
this.sendJsonRpcResponse(socket, {
|
|
@@ -2007,9 +2492,26 @@ var init_bridge_client = __esm({
|
|
|
2007
2492
|
}
|
|
2008
2493
|
});
|
|
2009
2494
|
}
|
|
2495
|
+
getTerminalSessions() {
|
|
2496
|
+
if (!this.terminalSessions) {
|
|
2497
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2498
|
+
send: (frame) => {
|
|
2499
|
+
const socket = this.extensionSocket;
|
|
2500
|
+
if (socket && socket.readyState === WS_OPEN) {
|
|
2501
|
+
try {
|
|
2502
|
+
socket.send(JSON.stringify(frame));
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
return this.terminalSessions;
|
|
2510
|
+
}
|
|
2010
2511
|
/** 销毁服务,断开所有连接 */
|
|
2011
2512
|
destroy() {
|
|
2012
2513
|
this.destroyed = true;
|
|
2514
|
+
this.terminalSessions?.disposeAll();
|
|
2013
2515
|
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
2014
2516
|
this.forwardedRequests.clear();
|
|
2015
2517
|
if (this.clientSocket) {
|
|
@@ -2154,7 +2656,7 @@ __export(daemon_exports, {
|
|
|
2154
2656
|
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2155
2657
|
isProcessAlive: () => isProcessAlive,
|
|
2156
2658
|
killProcessOnPort: () => killProcessOnPort,
|
|
2157
|
-
killProcessTree: () =>
|
|
2659
|
+
killProcessTree: () => killProcessTree,
|
|
2158
2660
|
probeServerReady: () => probeServerReady,
|
|
2159
2661
|
runDaemonProcess: () => runDaemonProcess,
|
|
2160
2662
|
stopBridgeDaemon: () => stopBridgeDaemon
|
|
@@ -2207,7 +2709,7 @@ async function findPidByPort(port, excludePid) {
|
|
|
2207
2709
|
return null;
|
|
2208
2710
|
}
|
|
2209
2711
|
}
|
|
2210
|
-
async function
|
|
2712
|
+
async function killProcessTree(pid) {
|
|
2211
2713
|
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2212
2714
|
if (process.platform === "win32") {
|
|
2213
2715
|
try {
|
|
@@ -2245,7 +2747,7 @@ async function killProcessTree2(pid) {
|
|
|
2245
2747
|
async function killProcessOnPort(port) {
|
|
2246
2748
|
const pid = await findPidByPort(port, process.pid);
|
|
2247
2749
|
if (pid) {
|
|
2248
|
-
await
|
|
2750
|
+
await killProcessTree(pid);
|
|
2249
2751
|
for (let i = 0; i < 20; i++) {
|
|
2250
2752
|
await new Promise((r) => setTimeout(r, 100));
|
|
2251
2753
|
const remainingPid = await findPidByPort(port, process.pid);
|
|
@@ -2427,7 +2929,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2427
2929
|
if (portPid && portPid !== daemonPid) {
|
|
2428
2930
|
return false;
|
|
2429
2931
|
}
|
|
2430
|
-
const killed = await
|
|
2932
|
+
const killed = await killProcessTree(daemonPid);
|
|
2431
2933
|
if (killed) {
|
|
2432
2934
|
killedAny = true;
|
|
2433
2935
|
try {
|
|
@@ -2438,7 +2940,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2438
2940
|
} else {
|
|
2439
2941
|
if (daemonPid) {
|
|
2440
2942
|
try {
|
|
2441
|
-
const killed = await
|
|
2943
|
+
const killed = await killProcessTree(daemonPid);
|
|
2442
2944
|
if (killed) {
|
|
2443
2945
|
killedAny = true;
|
|
2444
2946
|
try {
|
|
@@ -3568,11 +4070,20 @@ function handleCommandError(error, commandName, args) {
|
|
|
3568
4070
|
}
|
|
3569
4071
|
program.name("webmcp-cli").description(
|
|
3570
4072
|
"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"
|
|
3571
|
-
).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").
|
|
4073
|
+
).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(
|
|
4074
|
+
"--ignore-certificate-errors",
|
|
4075
|
+
"\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"
|
|
4076
|
+
).hook("preAction", (thisCommand) => {
|
|
3572
4077
|
const opts = thisCommand.opts();
|
|
3573
4078
|
if (opts.workspace) {
|
|
3574
4079
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
3575
4080
|
}
|
|
4081
|
+
if (opts.ignoreCertificateErrors) {
|
|
4082
|
+
process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS = "1";
|
|
4083
|
+
}
|
|
4084
|
+
if (process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS === "1") {
|
|
4085
|
+
console.error(import_picocolors2.default.yellow("\u5F53\u524D\u53D7\u63A7\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED TLS \u8BC1\u4E66\u6821\u9A8C\u3002"));
|
|
4086
|
+
}
|
|
3576
4087
|
});
|
|
3577
4088
|
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) => {
|
|
3578
4089
|
const args = { tabid: parseTabId(options.tabid) };
|