@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/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({ browserURL: browserBaseUrl, defaultViewport: null }),
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
- `--remote-debugging-port=${cdpPort}`,
220
- `--user-data-dir=${userDataDir}`,
221
- "--no-first-run",
222
- "--no-default-browser-check"
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.9",
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: ">=16"
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
- var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS;
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.1.0";
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 killProcessTree(child) {
950
- if (process.platform === "win32" && child.pid) {
951
- try {
952
- (0, import_node_child_process.spawn)("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
953
- windowsHide: true,
954
- stdio: "ignore"
955
- });
956
- } catch {
957
- child.kill("SIGKILL");
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
- } else {
960
- child.kill("SIGTERM");
961
- setTimeout(() => {
962
- try {
963
- child.kill("SIGKILL");
964
- } catch {
965
- }
966
- }, 2e3);
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
- return new Promise((resolve3, reject) => {
989
- const stdoutChunks = [];
990
- let stdoutBytes = 0;
991
- const stderrChunks = [];
992
- let stderrBytes = 0;
993
- let truncated = false;
994
- let isKilled = false;
995
- const child = (0, import_node_child_process.spawn)(trimmedCommand, {
996
- shell,
997
- cwd,
998
- env: { ...process.env, ...env },
999
- windowsHide: true
1000
- });
1001
- const killTimeoutTimer = setTimeout(() => {
1002
- isKilled = true;
1003
- killProcessTree(child);
1004
- reject(new BashTimeoutError(normalizedTimeoutMs));
1005
- }, normalizedTimeoutMs);
1006
- child.stdout.on("data", (chunk) => {
1007
- if (stdoutBytes < maxBuffer) {
1008
- const remaining = maxBuffer - stdoutBytes;
1009
- if (chunk.length <= remaining) {
1010
- stdoutChunks.push(chunk);
1011
- stdoutBytes += chunk.length;
1012
- } else {
1013
- stdoutChunks.push(chunk.subarray(0, remaining));
1014
- stdoutBytes += remaining;
1015
- truncated = true;
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, import_node_child_process, BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell;
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
- import_node_child_process = require("child_process");
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, import_node_child_process2.spawn)("osascript", ["-e", appleScript], {
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, import_node_child_process2.spawn)("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
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, import_node_child_process2.spawn)("zenity", args, {
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 import_node_child_process2;
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, import_node_crypto.randomBytes)(32).toString("hex");
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, import_node_crypto.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
1779
+ return (0, import_node_crypto2.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
1337
1780
  }
1338
1781
  function computeHmac(secret, challenge) {
1339
- return (0, import_node_crypto.createHmac)("sha256", secret).update(challenge).digest("hex");
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, import_node_crypto.timingSafeEqual)(actualBuf, expectedBuf);
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, import_node_crypto, 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;
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
- import_node_crypto = require("crypto");
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, import_node_crypto.randomUUID)();
1647
- const hmacSalt = (0, import_node_crypto.randomBytes)(16).toString("hex");
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: () => killProcessTree2,
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 killProcessTree2(pid) {
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 killProcessTree2(pid);
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 killProcessTree2(daemonPid);
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 killProcessTree2(daemonPid);
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").hook("preAction", (thisCommand) => {
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) };