@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.js
CHANGED
|
@@ -33,6 +33,38 @@ function shouldLaunchHeadless() {
|
|
|
33
33
|
if (process.env.WEBMCP_HEADLESS === "0") return false;
|
|
34
34
|
return process.env.WEBMCP_HEADLESS === "1" || process.env.CI === "true" || process.env.CI === "1";
|
|
35
35
|
}
|
|
36
|
+
function shouldIgnoreCertificateErrors() {
|
|
37
|
+
return process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS === "1";
|
|
38
|
+
}
|
|
39
|
+
function buildChromeLaunchArgs(input) {
|
|
40
|
+
const launchArgs = [
|
|
41
|
+
`--remote-debugging-port=${input.cdpPort}`,
|
|
42
|
+
`--user-data-dir=${input.userDataDir}`,
|
|
43
|
+
"--no-first-run",
|
|
44
|
+
"--no-default-browser-check"
|
|
45
|
+
];
|
|
46
|
+
if (input.headless) {
|
|
47
|
+
launchArgs.push(
|
|
48
|
+
"--headless=new",
|
|
49
|
+
"--no-sandbox",
|
|
50
|
+
"--disable-setuid-sandbox",
|
|
51
|
+
"--disable-gpu",
|
|
52
|
+
"--disable-dev-shm-usage"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const ignoreCertificateErrors = input.ignoreCertificateErrors ?? shouldIgnoreCertificateErrors();
|
|
56
|
+
if (ignoreCertificateErrors) {
|
|
57
|
+
launchArgs.push("--ignore-certificate-errors");
|
|
58
|
+
}
|
|
59
|
+
return launchArgs;
|
|
60
|
+
}
|
|
61
|
+
function buildPuppeteerConnectOptions(browserBaseUrl) {
|
|
62
|
+
return {
|
|
63
|
+
browserURL: browserBaseUrl,
|
|
64
|
+
defaultViewport: null,
|
|
65
|
+
...shouldIgnoreCertificateErrors() ? { ignoreHTTPSErrors: true } : {}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
36
68
|
function readActivePortFromProfile(userDataDir) {
|
|
37
69
|
try {
|
|
38
70
|
const content = fs.readFileSync(path.join(userDataDir, "DevToolsActivePort"), "utf-8");
|
|
@@ -127,7 +159,7 @@ async function findAvailableCdpUrl(addresses, retries = 2) {
|
|
|
127
159
|
async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
|
|
128
160
|
try {
|
|
129
161
|
return await promiseWithTimeout(
|
|
130
|
-
puppeteer.connect(
|
|
162
|
+
puppeteer.connect(buildPuppeteerConnectOptions(browserBaseUrl)),
|
|
131
163
|
1e4,
|
|
132
164
|
`puppeteer.connect to ${browserBaseUrl} timed out`
|
|
133
165
|
);
|
|
@@ -202,21 +234,11 @@ async function startBrowserInBackground() {
|
|
|
202
234
|
fs.unlinkSync(path.join(userDataDir, "DevToolsActivePort"));
|
|
203
235
|
} catch {
|
|
204
236
|
}
|
|
205
|
-
const launchArgs =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
];
|
|
211
|
-
if (shouldLaunchHeadless()) {
|
|
212
|
-
launchArgs.push(
|
|
213
|
-
"--headless=new",
|
|
214
|
-
"--no-sandbox",
|
|
215
|
-
"--disable-setuid-sandbox",
|
|
216
|
-
"--disable-gpu",
|
|
217
|
-
"--disable-dev-shm-usage"
|
|
218
|
-
);
|
|
219
|
-
}
|
|
237
|
+
const launchArgs = buildChromeLaunchArgs({
|
|
238
|
+
cdpPort,
|
|
239
|
+
userDataDir,
|
|
240
|
+
headless: shouldLaunchHeadless()
|
|
241
|
+
});
|
|
220
242
|
const child = spawn(
|
|
221
243
|
browserInfo.path,
|
|
222
244
|
launchArgs,
|
|
@@ -504,9 +526,17 @@ var init_package = __esm({
|
|
|
504
526
|
"package.json"() {
|
|
505
527
|
package_default = {
|
|
506
528
|
name: "@opentiny/webmcp-cli",
|
|
507
|
-
version: "0.0.
|
|
529
|
+
version: "0.1.0-beta.1",
|
|
508
530
|
type: "module",
|
|
509
531
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
532
|
+
repository: {
|
|
533
|
+
type: "git",
|
|
534
|
+
url: "git+https://github.com/opentiny/webmcp-sdk.git",
|
|
535
|
+
directory: "packages/webmcp-cli"
|
|
536
|
+
},
|
|
537
|
+
bugs: {
|
|
538
|
+
url: "https://github.com/opentiny/webmcp-sdk/issues"
|
|
539
|
+
},
|
|
510
540
|
main: "dist/index.js",
|
|
511
541
|
types: "dist/index.d.ts",
|
|
512
542
|
bin: {
|
|
@@ -535,7 +565,7 @@ var init_package = __esm({
|
|
|
535
565
|
license: "MIT",
|
|
536
566
|
author: "OpenTiny Team",
|
|
537
567
|
engines: {
|
|
538
|
-
node: ">=
|
|
568
|
+
node: ">=18.19.0"
|
|
539
569
|
},
|
|
540
570
|
publishConfig: {
|
|
541
571
|
access: "public"
|
|
@@ -547,6 +577,7 @@ var init_package = __esm({
|
|
|
547
577
|
"@page-agent/page-controller": "catalog:",
|
|
548
578
|
clipboardy: "^5.3.1",
|
|
549
579
|
commander: "^11.1.0",
|
|
580
|
+
execa: "^9.6.1",
|
|
550
581
|
json5: "^2.2.3",
|
|
551
582
|
marked: "^15.0.7",
|
|
552
583
|
picocolors: "^1.0.0",
|
|
@@ -775,12 +806,15 @@ var init_cdp_adapter = __esm({
|
|
|
775
806
|
});
|
|
776
807
|
|
|
777
808
|
// src/bridge/protocol.ts
|
|
778
|
-
|
|
809
|
+
function isTerminalMethod(method) {
|
|
810
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
811
|
+
}
|
|
812
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
779
813
|
var init_protocol = __esm({
|
|
780
814
|
"src/bridge/protocol.ts"() {
|
|
781
815
|
"use strict";
|
|
782
816
|
DEFAULT_WS_PORT = 18999;
|
|
783
|
-
PROTOCOL_VERSION = "1.
|
|
817
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
784
818
|
WS_CLOSE_CODE = {
|
|
785
819
|
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
786
820
|
REPLACED: 4e3,
|
|
@@ -814,8 +848,25 @@ var init_protocol = __esm({
|
|
|
814
848
|
SKILLS_GET: "skills/get",
|
|
815
849
|
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
816
850
|
SYSTEM_BASH: "system/bash",
|
|
817
|
-
SYSTEM_PICK_DIRECTORY: "system/pick_directory"
|
|
851
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
852
|
+
TERMINAL_OPEN: "terminal/open",
|
|
853
|
+
TERMINAL_INPUT: "terminal/input",
|
|
854
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
855
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
856
|
+
};
|
|
857
|
+
TERMINAL_EVENTS = {
|
|
858
|
+
OUTPUT: "notification/terminal_output",
|
|
859
|
+
EXIT: "notification/terminal_exit"
|
|
818
860
|
};
|
|
861
|
+
BASH_EVENTS = {
|
|
862
|
+
OUTPUT: "notification/bash_output"
|
|
863
|
+
};
|
|
864
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
865
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
866
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
867
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
868
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
869
|
+
]);
|
|
819
870
|
}
|
|
820
871
|
});
|
|
821
872
|
|
|
@@ -876,7 +927,8 @@ var init_workspace_manager = __esm({
|
|
|
876
927
|
// src/bridge/bash-executor.ts
|
|
877
928
|
import fs3 from "fs";
|
|
878
929
|
import path3 from "path";
|
|
879
|
-
import {
|
|
930
|
+
import { StringDecoder } from "string_decoder";
|
|
931
|
+
import { execa } from "execa";
|
|
880
932
|
function findWindowsBash() {
|
|
881
933
|
const pathDirs = (process.env.PATH || "").split(path3.delimiter);
|
|
882
934
|
for (const dir of pathDirs) {
|
|
@@ -927,25 +979,56 @@ function resolveShell(forcePlatform) {
|
|
|
927
979
|
if (!forcePlatform) cachedShell = fallback;
|
|
928
980
|
return fallback;
|
|
929
981
|
}
|
|
930
|
-
function
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
982
|
+
function withoutCarriageReturn(buf) {
|
|
983
|
+
if (!buf.includes(13)) return buf;
|
|
984
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
985
|
+
let size = 0;
|
|
986
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
987
|
+
const byte = buf[index];
|
|
988
|
+
if (byte === 13) continue;
|
|
989
|
+
out[size] = byte;
|
|
990
|
+
size += 1;
|
|
991
|
+
}
|
|
992
|
+
return out.subarray(0, size);
|
|
993
|
+
}
|
|
994
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false) {
|
|
995
|
+
const state = { chunks: [], truncated: false };
|
|
996
|
+
if (!stream) return state;
|
|
997
|
+
const decoder = new StringDecoder("utf8");
|
|
998
|
+
let bytes = 0;
|
|
999
|
+
const emit = (buf) => {
|
|
1000
|
+
const text = decoder.write(buf);
|
|
1001
|
+
if (text) onText?.(text);
|
|
1002
|
+
};
|
|
1003
|
+
stream.on("data", (chunk) => {
|
|
1004
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1005
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1006
|
+
if (buf.length === 0) return;
|
|
1007
|
+
if (bytes >= maxBuffer) {
|
|
1008
|
+
state.truncated = true;
|
|
1009
|
+
return;
|
|
939
1010
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1011
|
+
const remaining = maxBuffer - bytes;
|
|
1012
|
+
if (buf.length <= remaining) {
|
|
1013
|
+
state.chunks.push(buf);
|
|
1014
|
+
bytes += buf.length;
|
|
1015
|
+
emit(buf);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const kept = buf.subarray(0, remaining);
|
|
1019
|
+
state.chunks.push(kept);
|
|
1020
|
+
bytes += remaining;
|
|
1021
|
+
state.truncated = true;
|
|
1022
|
+
emit(kept);
|
|
1023
|
+
});
|
|
1024
|
+
stream.on("end", () => {
|
|
1025
|
+
const rest = decoder.end();
|
|
1026
|
+
if (rest) onText?.(rest);
|
|
1027
|
+
});
|
|
1028
|
+
return state;
|
|
1029
|
+
}
|
|
1030
|
+
function decodeLimited(state) {
|
|
1031
|
+
return Buffer.concat(state.chunks).toString("utf8").trim();
|
|
949
1032
|
}
|
|
950
1033
|
async function executeBash(params) {
|
|
951
1034
|
const trimmedCommand = params.command?.trim();
|
|
@@ -964,77 +1047,59 @@ async function executeBash(params) {
|
|
|
964
1047
|
});
|
|
965
1048
|
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
966
1049
|
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
967
|
-
const startTime = Date.now();
|
|
968
1050
|
const shell = resolveShell();
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
} else {
|
|
999
|
-
truncated = true;
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1002
|
-
child.stderr.on("data", (chunk) => {
|
|
1003
|
-
if (stderrBytes < maxBuffer) {
|
|
1004
|
-
const remaining = maxBuffer - stderrBytes;
|
|
1005
|
-
if (chunk.length <= remaining) {
|
|
1006
|
-
stderrChunks.push(chunk);
|
|
1007
|
-
stderrBytes += chunk.length;
|
|
1008
|
-
} else {
|
|
1009
|
-
stderrChunks.push(chunk.subarray(0, remaining));
|
|
1010
|
-
stderrBytes += remaining;
|
|
1011
|
-
truncated = true;
|
|
1012
|
-
}
|
|
1013
|
-
} else {
|
|
1014
|
-
truncated = true;
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
child.on("error", (err) => {
|
|
1018
|
-
clearTimeout(killTimeoutTimer);
|
|
1019
|
-
reject(err);
|
|
1020
|
-
});
|
|
1021
|
-
child.on("close", (exitCode, signal) => {
|
|
1022
|
-
clearTimeout(killTimeoutTimer);
|
|
1023
|
-
if (isKilled) return;
|
|
1024
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
1025
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
1026
|
-
resolve3({
|
|
1027
|
-
stdout,
|
|
1028
|
-
stderr,
|
|
1029
|
-
exitCode,
|
|
1030
|
-
signal,
|
|
1031
|
-
durationMs: Date.now() - startTime,
|
|
1032
|
-
...truncated ? { truncated: true } : {}
|
|
1033
|
-
});
|
|
1034
|
-
});
|
|
1051
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1052
|
+
const subprocess = useTty ? execa("python3", ["-u", "-c", TTY_HELPER], {
|
|
1053
|
+
cwd,
|
|
1054
|
+
env: {
|
|
1055
|
+
...env,
|
|
1056
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1057
|
+
BASH_TTY_SHELL: shell
|
|
1058
|
+
},
|
|
1059
|
+
stdin: "ignore",
|
|
1060
|
+
buffer: false,
|
|
1061
|
+
encoding: "buffer",
|
|
1062
|
+
reject: false,
|
|
1063
|
+
timeout: normalizedTimeoutMs,
|
|
1064
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1065
|
+
windowsHide: true,
|
|
1066
|
+
stripFinalNewline: false
|
|
1067
|
+
}) : execa(trimmedCommand, {
|
|
1068
|
+
shell,
|
|
1069
|
+
cwd,
|
|
1070
|
+
env,
|
|
1071
|
+
stdin: "ignore",
|
|
1072
|
+
buffer: false,
|
|
1073
|
+
encoding: "buffer",
|
|
1074
|
+
reject: false,
|
|
1075
|
+
timeout: normalizedTimeoutMs,
|
|
1076
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1077
|
+
windowsHide: true,
|
|
1078
|
+
stripFinalNewline: false
|
|
1035
1079
|
});
|
|
1080
|
+
const emit = (stream) => (text) => {
|
|
1081
|
+
params.onOutput?.({ stream, text });
|
|
1082
|
+
};
|
|
1083
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty);
|
|
1084
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1085
|
+
const result = await subprocess;
|
|
1086
|
+
if (result.timedOut) {
|
|
1087
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1088
|
+
}
|
|
1089
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1090
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1091
|
+
}
|
|
1092
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1093
|
+
return {
|
|
1094
|
+
stdout: decodeLimited(stdoutState),
|
|
1095
|
+
stderr: decodeLimited(stderrState),
|
|
1096
|
+
exitCode: result.exitCode ?? null,
|
|
1097
|
+
signal: result.signal ?? null,
|
|
1098
|
+
durationMs: result.durationMs ?? 0,
|
|
1099
|
+
...truncated ? { truncated: true } : {}
|
|
1100
|
+
};
|
|
1036
1101
|
}
|
|
1037
|
-
var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell;
|
|
1102
|
+
var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell, FORCE_KILL_AFTER_DELAY_MS, TTY_HELPER;
|
|
1038
1103
|
var init_bash_executor = __esm({
|
|
1039
1104
|
"src/bridge/bash-executor.ts"() {
|
|
1040
1105
|
"use strict";
|
|
@@ -1052,11 +1117,51 @@ var init_bash_executor = __esm({
|
|
|
1052
1117
|
MAX_TIMEOUT_MS = 6e5;
|
|
1053
1118
|
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1054
1119
|
cachedShell = null;
|
|
1120
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1121
|
+
TTY_HELPER = `
|
|
1122
|
+
import errno, os, sys
|
|
1123
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1124
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1125
|
+
if not cmd:
|
|
1126
|
+
sys.exit(127)
|
|
1127
|
+
import pty
|
|
1128
|
+
pid, fd = pty.fork()
|
|
1129
|
+
if pid == 0:
|
|
1130
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1131
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1132
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1133
|
+
try:
|
|
1134
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1135
|
+
except OSError:
|
|
1136
|
+
os._exit(127)
|
|
1137
|
+
while True:
|
|
1138
|
+
try:
|
|
1139
|
+
data = os.read(fd, 65536)
|
|
1140
|
+
except OSError as exc:
|
|
1141
|
+
if exc.errno == errno.EINTR:
|
|
1142
|
+
continue
|
|
1143
|
+
break
|
|
1144
|
+
if not data:
|
|
1145
|
+
break
|
|
1146
|
+
try:
|
|
1147
|
+
os.write(1, data)
|
|
1148
|
+
except OSError:
|
|
1149
|
+
break
|
|
1150
|
+
try:
|
|
1151
|
+
_, status = os.waitpid(pid, 0)
|
|
1152
|
+
except OSError:
|
|
1153
|
+
sys.exit(1)
|
|
1154
|
+
if os.WIFEXITED(status):
|
|
1155
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1156
|
+
if os.WIFSIGNALED(status):
|
|
1157
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1158
|
+
sys.exit(1)
|
|
1159
|
+
`;
|
|
1055
1160
|
}
|
|
1056
1161
|
});
|
|
1057
1162
|
|
|
1058
1163
|
// src/bridge/directory-picker.ts
|
|
1059
|
-
import { spawn as
|
|
1164
|
+
import { spawn as spawn2 } from "child_process";
|
|
1060
1165
|
async function pickDirectory(optionsOrTitle) {
|
|
1061
1166
|
const options = typeof optionsOrTitle === "string" ? { title: optionsOrTitle } : optionsOrTitle || {};
|
|
1062
1167
|
const title = options.title || "\u8BF7\u9009\u62E9\u5DE5\u4F5C\u7A7A\u95F4\u6587\u4EF6\u5939";
|
|
@@ -1075,7 +1180,7 @@ function pickDirectoryMac(title, defaultPath) {
|
|
|
1075
1180
|
const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
|
|
1076
1181
|
const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
|
|
1077
1182
|
return new Promise((resolve3, reject) => {
|
|
1078
|
-
const child =
|
|
1183
|
+
const child = spawn2("osascript", ["-e", appleScript], {
|
|
1079
1184
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1080
1185
|
});
|
|
1081
1186
|
let stdout = "";
|
|
@@ -1193,7 +1298,7 @@ $result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
|
|
|
1193
1298
|
if ($result) { Write-Output $result }
|
|
1194
1299
|
`;
|
|
1195
1300
|
return new Promise((resolve3, reject) => {
|
|
1196
|
-
const child =
|
|
1301
|
+
const child = spawn2("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
|
|
1197
1302
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1198
1303
|
windowsHide: true
|
|
1199
1304
|
});
|
|
@@ -1224,7 +1329,7 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1224
1329
|
args.push(`--filename=${defaultPath}`);
|
|
1225
1330
|
}
|
|
1226
1331
|
return new Promise((resolve3, reject) => {
|
|
1227
|
-
const child =
|
|
1332
|
+
const child = spawn2("zenity", args, {
|
|
1228
1333
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1229
1334
|
});
|
|
1230
1335
|
let stdout = "";
|
|
@@ -1262,6 +1367,344 @@ var init_directory_picker = __esm({
|
|
|
1262
1367
|
}
|
|
1263
1368
|
});
|
|
1264
1369
|
|
|
1370
|
+
// src/bridge/pty-backend.ts
|
|
1371
|
+
import { spawn as spawn3 } from "child_process";
|
|
1372
|
+
function spawnHostPty(options) {
|
|
1373
|
+
if (process.platform === "win32") {
|
|
1374
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u4EA4\u4E92\u5F0F\u7EC8\u7AEF");
|
|
1375
|
+
}
|
|
1376
|
+
const shell = options.shell || resolveShell();
|
|
1377
|
+
const child = spawn3("python3", ["-u", "-c", PTY_HELPER], {
|
|
1378
|
+
cwd: options.cwd,
|
|
1379
|
+
env: {
|
|
1380
|
+
...process.env,
|
|
1381
|
+
TERM: "xterm-256color",
|
|
1382
|
+
TERMINAL_SHELL: shell,
|
|
1383
|
+
TERMINAL_ROWS: String(options.rows),
|
|
1384
|
+
TERMINAL_COLS: String(options.cols)
|
|
1385
|
+
},
|
|
1386
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
1387
|
+
});
|
|
1388
|
+
const pending = [];
|
|
1389
|
+
let dataCb = null;
|
|
1390
|
+
let exitCb = null;
|
|
1391
|
+
let pendingExit = null;
|
|
1392
|
+
let settled = false;
|
|
1393
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1394
|
+
let stderrText = "";
|
|
1395
|
+
const emitData = (data) => {
|
|
1396
|
+
if (!data) return;
|
|
1397
|
+
if (dataCb) dataCb(data);
|
|
1398
|
+
else pending.push(data);
|
|
1399
|
+
};
|
|
1400
|
+
const finish = (info) => {
|
|
1401
|
+
if (settled) return;
|
|
1402
|
+
settled = true;
|
|
1403
|
+
if (stderrText.trim()) emitData(`\r
|
|
1404
|
+
${stderrText.trim()}\r
|
|
1405
|
+
`);
|
|
1406
|
+
if (exitCb) exitCb(info);
|
|
1407
|
+
else pendingExit = info;
|
|
1408
|
+
};
|
|
1409
|
+
child.stdout.on("data", (buf) => {
|
|
1410
|
+
emitData(decoder.decode(buf, { stream: true }));
|
|
1411
|
+
});
|
|
1412
|
+
child.stderr.on("data", (buf) => {
|
|
1413
|
+
stderrText += buf.toString("utf8");
|
|
1414
|
+
});
|
|
1415
|
+
child.on("error", (err) => {
|
|
1416
|
+
emitData(`\r
|
|
1417
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${err.message}\r
|
|
1418
|
+
`);
|
|
1419
|
+
finish({ exitCode: null, signal: null });
|
|
1420
|
+
});
|
|
1421
|
+
child.on("exit", (code, signal) => {
|
|
1422
|
+
const rest = decoder.decode();
|
|
1423
|
+
if (rest) emitData(rest);
|
|
1424
|
+
finish({ exitCode: code, signal: signal ?? null });
|
|
1425
|
+
});
|
|
1426
|
+
const control = child.stdio[3];
|
|
1427
|
+
const writeControl = (payload) => {
|
|
1428
|
+
if (!control || !("write" in control)) return;
|
|
1429
|
+
try {
|
|
1430
|
+
;
|
|
1431
|
+
control.write(`${JSON.stringify(payload)}
|
|
1432
|
+
`);
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
return {
|
|
1437
|
+
write(data) {
|
|
1438
|
+
try {
|
|
1439
|
+
child.stdin.write(data);
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
resize(cols, rows) {
|
|
1444
|
+
writeControl({ type: "resize", cols, rows });
|
|
1445
|
+
},
|
|
1446
|
+
kill() {
|
|
1447
|
+
writeControl({ type: "kill" });
|
|
1448
|
+
try {
|
|
1449
|
+
child.kill("SIGTERM");
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
onData(cb) {
|
|
1454
|
+
dataCb = cb;
|
|
1455
|
+
for (const chunk of pending) cb(chunk);
|
|
1456
|
+
pending.length = 0;
|
|
1457
|
+
},
|
|
1458
|
+
onExit(cb) {
|
|
1459
|
+
exitCb = cb;
|
|
1460
|
+
if (pendingExit) cb(pendingExit);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
var PTY_HELPER;
|
|
1465
|
+
var init_pty_backend = __esm({
|
|
1466
|
+
"src/bridge/pty-backend.ts"() {
|
|
1467
|
+
"use strict";
|
|
1468
|
+
init_bash_executor();
|
|
1469
|
+
PTY_HELPER = `
|
|
1470
|
+
import errno, fcntl, json, os, pty, select, signal, struct, sys, termios
|
|
1471
|
+
|
|
1472
|
+
shell = os.environ.get("TERMINAL_SHELL") or "/bin/bash"
|
|
1473
|
+
rows = int(os.environ.get("TERMINAL_ROWS") or "24")
|
|
1474
|
+
cols = int(os.environ.get("TERMINAL_COLS") or "80")
|
|
1475
|
+
control_fd = 3
|
|
1476
|
+
|
|
1477
|
+
def set_winsize(fd, next_rows, next_cols):
|
|
1478
|
+
winsize = struct.pack("HHHH", int(next_rows), int(next_cols), 0, 0)
|
|
1479
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
|
1480
|
+
|
|
1481
|
+
pid, master = pty.fork()
|
|
1482
|
+
if pid == 0:
|
|
1483
|
+
try:
|
|
1484
|
+
os.close(control_fd)
|
|
1485
|
+
except OSError:
|
|
1486
|
+
pass
|
|
1487
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1488
|
+
try:
|
|
1489
|
+
os.execvp(shell, [shell, "-i"])
|
|
1490
|
+
except OSError:
|
|
1491
|
+
os._exit(127)
|
|
1492
|
+
|
|
1493
|
+
def control_loop():
|
|
1494
|
+
try:
|
|
1495
|
+
reader = os.fdopen(control_fd, "r", buffering=1, encoding="utf-8", errors="replace")
|
|
1496
|
+
except OSError:
|
|
1497
|
+
return
|
|
1498
|
+
for line in reader:
|
|
1499
|
+
text = line.strip()
|
|
1500
|
+
if not text:
|
|
1501
|
+
continue
|
|
1502
|
+
try:
|
|
1503
|
+
msg = json.loads(text)
|
|
1504
|
+
except json.JSONDecodeError:
|
|
1505
|
+
continue
|
|
1506
|
+
kind = msg.get("type")
|
|
1507
|
+
if kind == "resize":
|
|
1508
|
+
try:
|
|
1509
|
+
set_winsize(master, msg.get("rows") or rows, msg.get("cols") or cols)
|
|
1510
|
+
os.kill(pid, signal.SIGWINCH)
|
|
1511
|
+
except OSError:
|
|
1512
|
+
pass
|
|
1513
|
+
elif kind == "kill":
|
|
1514
|
+
try:
|
|
1515
|
+
os.kill(pid, signal.SIGHUP)
|
|
1516
|
+
except OSError:
|
|
1517
|
+
pass
|
|
1518
|
+
os._exit(0)
|
|
1519
|
+
|
|
1520
|
+
import threading
|
|
1521
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
1522
|
+
|
|
1523
|
+
try:
|
|
1524
|
+
set_winsize(master, rows, cols)
|
|
1525
|
+
except OSError:
|
|
1526
|
+
pass
|
|
1527
|
+
|
|
1528
|
+
stdin_fd = sys.stdin.fileno()
|
|
1529
|
+
stdout_fd = sys.stdout.fileno()
|
|
1530
|
+
|
|
1531
|
+
while True:
|
|
1532
|
+
try:
|
|
1533
|
+
readable, _, _ = select.select([master, stdin_fd], [], [])
|
|
1534
|
+
except InterruptedError:
|
|
1535
|
+
continue
|
|
1536
|
+
if master in readable:
|
|
1537
|
+
try:
|
|
1538
|
+
data = os.read(master, 65536)
|
|
1539
|
+
except OSError as err:
|
|
1540
|
+
if err.errno == errno.EIO:
|
|
1541
|
+
break
|
|
1542
|
+
raise
|
|
1543
|
+
if not data:
|
|
1544
|
+
break
|
|
1545
|
+
os.write(stdout_fd, data)
|
|
1546
|
+
if stdin_fd in readable:
|
|
1547
|
+
try:
|
|
1548
|
+
data = os.read(stdin_fd, 65536)
|
|
1549
|
+
except OSError:
|
|
1550
|
+
break
|
|
1551
|
+
if not data:
|
|
1552
|
+
break
|
|
1553
|
+
try:
|
|
1554
|
+
os.write(master, data)
|
|
1555
|
+
except OSError:
|
|
1556
|
+
break
|
|
1557
|
+
|
|
1558
|
+
try:
|
|
1559
|
+
_, status = os.waitpid(pid, 0)
|
|
1560
|
+
except ChildProcessError:
|
|
1561
|
+
status = 0
|
|
1562
|
+
code = os.waitstatus_to_exitcode(status)
|
|
1563
|
+
os._exit(0 if code < 0 else code)
|
|
1564
|
+
`;
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
// src/bridge/terminal-session.ts
|
|
1569
|
+
import { randomUUID } from "crypto";
|
|
1570
|
+
function clampDimension(value, fallback) {
|
|
1571
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1572
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1573
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1574
|
+
}
|
|
1575
|
+
var OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TerminalSessionManager;
|
|
1576
|
+
var init_terminal_session = __esm({
|
|
1577
|
+
"src/bridge/terminal-session.ts"() {
|
|
1578
|
+
"use strict";
|
|
1579
|
+
init_workspace_manager();
|
|
1580
|
+
init_pty_backend();
|
|
1581
|
+
init_protocol();
|
|
1582
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1583
|
+
MAX_INPUT = 64 * 1024;
|
|
1584
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1585
|
+
TerminalSessionManager = class {
|
|
1586
|
+
session = null;
|
|
1587
|
+
buffer = "";
|
|
1588
|
+
timer = null;
|
|
1589
|
+
send;
|
|
1590
|
+
spawnPty;
|
|
1591
|
+
coalesceMs;
|
|
1592
|
+
constructor(options) {
|
|
1593
|
+
this.send = options.send;
|
|
1594
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1595
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1596
|
+
}
|
|
1597
|
+
open(params = {}) {
|
|
1598
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1599
|
+
const cols = clampDimension(params.cols, 80);
|
|
1600
|
+
const rows = clampDimension(params.rows, 24);
|
|
1601
|
+
this.closeActive();
|
|
1602
|
+
const id = randomUUID();
|
|
1603
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1604
|
+
this.session = { id, pty, cwd };
|
|
1605
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1606
|
+
pty.onExit((info) => {
|
|
1607
|
+
if (this.session?.id !== id) return;
|
|
1608
|
+
this.flush(id);
|
|
1609
|
+
this.session = null;
|
|
1610
|
+
this.send({
|
|
1611
|
+
jsonrpc: "2.0",
|
|
1612
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1613
|
+
params: {
|
|
1614
|
+
sessionId: id,
|
|
1615
|
+
exitCode: info.exitCode,
|
|
1616
|
+
signal: info.signal
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
return { sessionId: id, cwd };
|
|
1621
|
+
}
|
|
1622
|
+
input(params) {
|
|
1623
|
+
const session = this.requireSession(params.sessionId);
|
|
1624
|
+
if (typeof params.data !== "string") {
|
|
1625
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1626
|
+
}
|
|
1627
|
+
if (params.data.length > MAX_INPUT) {
|
|
1628
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1629
|
+
}
|
|
1630
|
+
session.pty.write(params.data);
|
|
1631
|
+
return { ok: true };
|
|
1632
|
+
}
|
|
1633
|
+
resize(params) {
|
|
1634
|
+
const session = this.requireSession(params.sessionId);
|
|
1635
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1636
|
+
return { ok: true };
|
|
1637
|
+
}
|
|
1638
|
+
close(params = {}) {
|
|
1639
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1640
|
+
return { ok: true };
|
|
1641
|
+
}
|
|
1642
|
+
this.closeActive();
|
|
1643
|
+
return { ok: true };
|
|
1644
|
+
}
|
|
1645
|
+
disposeAll() {
|
|
1646
|
+
this.closeActive();
|
|
1647
|
+
}
|
|
1648
|
+
requireSession(sessionId) {
|
|
1649
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1650
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1651
|
+
}
|
|
1652
|
+
return this.session;
|
|
1653
|
+
}
|
|
1654
|
+
closeActive() {
|
|
1655
|
+
const current = this.session;
|
|
1656
|
+
this.session = null;
|
|
1657
|
+
this.buffer = "";
|
|
1658
|
+
if (this.timer) {
|
|
1659
|
+
clearTimeout(this.timer);
|
|
1660
|
+
this.timer = null;
|
|
1661
|
+
}
|
|
1662
|
+
current?.pty.kill();
|
|
1663
|
+
}
|
|
1664
|
+
pushOutput(sessionId, chunk) {
|
|
1665
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1666
|
+
this.buffer += chunk;
|
|
1667
|
+
this.flushFullChunks(sessionId);
|
|
1668
|
+
if (!this.buffer) return;
|
|
1669
|
+
if (this.coalesceMs <= 0) {
|
|
1670
|
+
this.flush(sessionId);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (!this.timer) {
|
|
1674
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
flushFullChunks(sessionId) {
|
|
1678
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1679
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1680
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1681
|
+
this.emitOutput(sessionId, data);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
flush(sessionId) {
|
|
1685
|
+
if (this.timer) {
|
|
1686
|
+
clearTimeout(this.timer);
|
|
1687
|
+
this.timer = null;
|
|
1688
|
+
}
|
|
1689
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1690
|
+
this.buffer = "";
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const data = this.buffer;
|
|
1694
|
+
this.buffer = "";
|
|
1695
|
+
this.emitOutput(sessionId, data);
|
|
1696
|
+
}
|
|
1697
|
+
emitOutput(sessionId, data) {
|
|
1698
|
+
this.send({
|
|
1699
|
+
jsonrpc: "2.0",
|
|
1700
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1701
|
+
params: { sessionId, data }
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1265
1708
|
// src/bridge/bridge-client.ts
|
|
1266
1709
|
var bridge_client_exports = {};
|
|
1267
1710
|
__export(bridge_client_exports, {
|
|
@@ -1276,7 +1719,7 @@ __export(bridge_client_exports, {
|
|
|
1276
1719
|
});
|
|
1277
1720
|
import { WebSocketServer, WebSocket as WsClient } from "ws";
|
|
1278
1721
|
import { EventEmitter } from "events";
|
|
1279
|
-
import { randomUUID, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
|
|
1722
|
+
import { randomUUID as randomUUID2, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
|
|
1280
1723
|
import fs4 from "fs";
|
|
1281
1724
|
import path4 from "path";
|
|
1282
1725
|
import os3 from "os";
|
|
@@ -1345,6 +1788,7 @@ var init_bridge_client = __esm({
|
|
|
1345
1788
|
init_protocol();
|
|
1346
1789
|
init_bash_executor();
|
|
1347
1790
|
init_directory_picker();
|
|
1791
|
+
init_terminal_session();
|
|
1348
1792
|
WS_PORT = DEFAULT_WS_PORT;
|
|
1349
1793
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1350
1794
|
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
@@ -1366,6 +1810,7 @@ var init_bridge_client = __esm({
|
|
|
1366
1810
|
pending = /* @__PURE__ */ new Map();
|
|
1367
1811
|
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1368
1812
|
destroyed = false;
|
|
1813
|
+
terminalSessions = null;
|
|
1369
1814
|
idCounter = 0;
|
|
1370
1815
|
isClientMode = false;
|
|
1371
1816
|
explicitAuthToken;
|
|
@@ -1620,7 +2065,7 @@ var init_bridge_client = __esm({
|
|
|
1620
2065
|
this.emit("error", err);
|
|
1621
2066
|
});
|
|
1622
2067
|
this.wss.on("connection", (socket) => {
|
|
1623
|
-
const challenge =
|
|
2068
|
+
const challenge = randomUUID2();
|
|
1624
2069
|
const hmacSalt = randomBytes(16).toString("hex");
|
|
1625
2070
|
const authState = {
|
|
1626
2071
|
authenticated: false,
|
|
@@ -1662,6 +2107,7 @@ var init_bridge_client = __esm({
|
|
|
1662
2107
|
}
|
|
1663
2108
|
if (socket === this.extensionSocket) {
|
|
1664
2109
|
this.extensionSocket = null;
|
|
2110
|
+
this.terminalSessions?.disposeAll();
|
|
1665
2111
|
this.emit("disconnected");
|
|
1666
2112
|
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
1667
2113
|
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
@@ -1708,6 +2154,16 @@ var init_bridge_client = __esm({
|
|
|
1708
2154
|
return;
|
|
1709
2155
|
}
|
|
1710
2156
|
if (msg.method && msg.id) {
|
|
2157
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2158
|
+
this.sendJsonRpcResponse(socket, {
|
|
2159
|
+
id: msg.id,
|
|
2160
|
+
error: {
|
|
2161
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2162
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
1711
2167
|
if (socket === this.extensionSocket) {
|
|
1712
2168
|
await this.handleExtensionSystemRpc(socket, msg);
|
|
1713
2169
|
return;
|
|
@@ -1871,9 +2327,6 @@ var init_bridge_client = __esm({
|
|
|
1871
2327
|
systemRpcHandlers = Object.assign(
|
|
1872
2328
|
/* @__PURE__ */ Object.create(null),
|
|
1873
2329
|
{
|
|
1874
|
-
[BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
|
|
1875
|
-
return executeBash(params || {});
|
|
1876
|
-
},
|
|
1877
2330
|
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
1878
2331
|
const selectedPath = await pickDirectory({
|
|
1879
2332
|
title: params?.title || void 0,
|
|
@@ -1884,11 +2337,43 @@ var init_bridge_client = __esm({
|
|
|
1884
2337
|
selectedPath,
|
|
1885
2338
|
canceled: selectedPath === null
|
|
1886
2339
|
};
|
|
1887
|
-
}
|
|
2340
|
+
},
|
|
2341
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2342
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2343
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2344
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
1888
2345
|
}
|
|
1889
2346
|
);
|
|
2347
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2348
|
+
if (socket.readyState === WS_OPEN) {
|
|
2349
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async executeHostBash(socket, id, params) {
|
|
2353
|
+
try {
|
|
2354
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2355
|
+
const result = await executeBash({
|
|
2356
|
+
...requestParams,
|
|
2357
|
+
onOutput: (chunk) => {
|
|
2358
|
+
if (!chunk.text) return;
|
|
2359
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2360
|
+
id,
|
|
2361
|
+
stream: chunk.stream,
|
|
2362
|
+
text: chunk.text
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2367
|
+
} catch (err) {
|
|
2368
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
1890
2371
|
async handleExtensionSystemRpc(socket, msg) {
|
|
1891
2372
|
const { id, method, params } = msg;
|
|
2373
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2374
|
+
await this.executeHostBash(socket, id, params);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
1892
2377
|
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
1893
2378
|
if (!handler || typeof handler !== "function") {
|
|
1894
2379
|
this.sendJsonRpcResponse(socket, {
|
|
@@ -1984,9 +2469,26 @@ var init_bridge_client = __esm({
|
|
|
1984
2469
|
}
|
|
1985
2470
|
});
|
|
1986
2471
|
}
|
|
2472
|
+
getTerminalSessions() {
|
|
2473
|
+
if (!this.terminalSessions) {
|
|
2474
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2475
|
+
send: (frame) => {
|
|
2476
|
+
const socket = this.extensionSocket;
|
|
2477
|
+
if (socket && socket.readyState === WS_OPEN) {
|
|
2478
|
+
try {
|
|
2479
|
+
socket.send(JSON.stringify(frame));
|
|
2480
|
+
} catch {
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
return this.terminalSessions;
|
|
2487
|
+
}
|
|
1987
2488
|
/** 销毁服务,断开所有连接 */
|
|
1988
2489
|
destroy() {
|
|
1989
2490
|
this.destroyed = true;
|
|
2491
|
+
this.terminalSessions?.disposeAll();
|
|
1990
2492
|
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
1991
2493
|
this.forwardedRequests.clear();
|
|
1992
2494
|
if (this.clientSocket) {
|
|
@@ -2131,7 +2633,7 @@ __export(daemon_exports, {
|
|
|
2131
2633
|
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2132
2634
|
isProcessAlive: () => isProcessAlive,
|
|
2133
2635
|
killProcessOnPort: () => killProcessOnPort,
|
|
2134
|
-
killProcessTree: () =>
|
|
2636
|
+
killProcessTree: () => killProcessTree,
|
|
2135
2637
|
probeServerReady: () => probeServerReady,
|
|
2136
2638
|
runDaemonProcess: () => runDaemonProcess,
|
|
2137
2639
|
stopBridgeDaemon: () => stopBridgeDaemon
|
|
@@ -2190,7 +2692,7 @@ async function findPidByPort(port, excludePid) {
|
|
|
2190
2692
|
return null;
|
|
2191
2693
|
}
|
|
2192
2694
|
}
|
|
2193
|
-
async function
|
|
2695
|
+
async function killProcessTree(pid) {
|
|
2194
2696
|
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2195
2697
|
if (process.platform === "win32") {
|
|
2196
2698
|
try {
|
|
@@ -2228,7 +2730,7 @@ async function killProcessTree2(pid) {
|
|
|
2228
2730
|
async function killProcessOnPort(port) {
|
|
2229
2731
|
const pid = await findPidByPort(port, process.pid);
|
|
2230
2732
|
if (pid) {
|
|
2231
|
-
await
|
|
2733
|
+
await killProcessTree(pid);
|
|
2232
2734
|
for (let i = 0; i < 20; i++) {
|
|
2233
2735
|
await new Promise((r) => setTimeout(r, 100));
|
|
2234
2736
|
const remainingPid = await findPidByPort(port, process.pid);
|
|
@@ -2410,7 +2912,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2410
2912
|
if (portPid && portPid !== daemonPid) {
|
|
2411
2913
|
return false;
|
|
2412
2914
|
}
|
|
2413
|
-
const killed = await
|
|
2915
|
+
const killed = await killProcessTree(daemonPid);
|
|
2414
2916
|
if (killed) {
|
|
2415
2917
|
killedAny = true;
|
|
2416
2918
|
try {
|
|
@@ -2421,7 +2923,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2421
2923
|
} else {
|
|
2422
2924
|
if (daemonPid) {
|
|
2423
2925
|
try {
|
|
2424
|
-
const killed = await
|
|
2926
|
+
const killed = await killProcessTree(daemonPid);
|
|
2425
2927
|
if (killed) {
|
|
2426
2928
|
killedAny = true;
|
|
2427
2929
|
try {
|
|
@@ -3544,11 +4046,20 @@ function handleCommandError(error, commandName, args) {
|
|
|
3544
4046
|
}
|
|
3545
4047
|
program.name("webmcp-cli").description(
|
|
3546
4048
|
"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"
|
|
3547
|
-
).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").
|
|
4049
|
+
).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(
|
|
4050
|
+
"--ignore-certificate-errors",
|
|
4051
|
+
"\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"
|
|
4052
|
+
).hook("preAction", (thisCommand) => {
|
|
3548
4053
|
const opts = thisCommand.opts();
|
|
3549
4054
|
if (opts.workspace) {
|
|
3550
4055
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
3551
4056
|
}
|
|
4057
|
+
if (opts.ignoreCertificateErrors) {
|
|
4058
|
+
process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS = "1";
|
|
4059
|
+
}
|
|
4060
|
+
if (process.env.WEBMCP_IGNORE_CERTIFICATE_ERRORS === "1") {
|
|
4061
|
+
console.error(pc2.yellow("\u5F53\u524D\u53D7\u63A7\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED TLS \u8BC1\u4E66\u6821\u9A8C\u3002"));
|
|
4062
|
+
}
|
|
3552
4063
|
});
|
|
3553
4064
|
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) => {
|
|
3554
4065
|
const args = { tabid: parseTabId(options.tabid) };
|