@opentiny/webmcp-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3104,9 +3104,9 @@
3104
3104
  }
3105
3105
  });
3106
3106
 
3107
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js
3107
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js
3108
3108
  var require_utils = __commonJS({
3109
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js"(exports, module) {
3109
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js"(exports, module) {
3110
3110
  "use strict";
3111
3111
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3112
3112
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
@@ -3338,6 +3338,37 @@
3338
3338
  }
3339
3339
  return output.join("");
3340
3340
  }
3341
+ function removeDotSegmentsRelative(path) {
3342
+ if (path === "" || path[0] === "/") {
3343
+ return removeDotSegments(path);
3344
+ }
3345
+ const segments = path.split("/");
3346
+ const last = segments.length - 1;
3347
+ const output = [];
3348
+ let trailingDirectory = false;
3349
+ for (let i = 0; i <= last; i++) {
3350
+ const segment = segments[i];
3351
+ if (segment === "." || segment === "..") {
3352
+ if (segment === "..") {
3353
+ if (output.length !== 0 && output[output.length - 1] !== "..") {
3354
+ output.pop();
3355
+ } else {
3356
+ output.push("..");
3357
+ }
3358
+ }
3359
+ trailingDirectory = i === last;
3360
+ } else {
3361
+ output.push(segment);
3362
+ }
3363
+ }
3364
+ if (trailingDirectory && output[output.length - 1] !== "..") {
3365
+ output.push("");
3366
+ }
3367
+ if (output.length === 0 || output[0] === "" || output[0].includes(":")) {
3368
+ output.unshift(".");
3369
+ }
3370
+ return output.join("/");
3371
+ }
3341
3372
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
3342
3373
  var HOST_DELIM_RE = /[@/?#:]/g;
3343
3374
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
@@ -3598,6 +3629,7 @@
3598
3629
  encodeFragment,
3599
3630
  escapePreservingEscapes,
3600
3631
  removeDotSegments,
3632
+ removeDotSegmentsRelative,
3601
3633
  isIPv4,
3602
3634
  isUUID,
3603
3635
  normalizeIPv6,
@@ -3606,9 +3638,9 @@
3606
3638
  }
3607
3639
  });
3608
3640
 
3609
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js
3641
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js
3610
3642
  var require_schemes = __commonJS({
3611
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3643
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3612
3644
  "use strict";
3613
3645
  var { isUUID } = require_utils();
3614
3646
  var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
@@ -3817,20 +3849,22 @@
3817
3849
  }
3818
3850
  });
3819
3851
 
3820
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js
3852
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js
3821
3853
  var require_fast_uri = __commonJS({
3822
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js"(exports, module) {
3854
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js"(exports, module) {
3823
3855
  "use strict";
3824
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3856
+ var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3825
3857
  var { SCHEMES, getSchemeHandler } = require_schemes();
3826
3858
  var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3827
3859
  var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3828
- function decodeValidScheme(scheme) {
3829
- const decodedScheme = unescape(String(scheme));
3830
- if (!VALID_SCHEME.test(decodedScheme)) {
3860
+ var MALFORMED_HOST_ERROR = "URI host is malformed.";
3861
+ var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
3862
+ function validateScheme(scheme) {
3863
+ const stringScheme = String(scheme);
3864
+ if (!VALID_SCHEME.test(stringScheme)) {
3831
3865
  throw new TypeError(MALFORMED_SCHEME_ERROR);
3832
3866
  }
3833
- return decodedScheme;
3867
+ return stringScheme;
3834
3868
  }
3835
3869
  function normalize(uri, options) {
3836
3870
  if (typeof uri === "string") {
@@ -3867,6 +3901,9 @@
3867
3901
  const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3868
3902
  const resolvedHost = resolved.host;
3869
3903
  const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3904
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
3905
+ throw new Error(MALFORMED_HOST_ERROR);
3906
+ }
3870
3907
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3871
3908
  const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
3872
3909
  if (resolved.error && !encodedASCIIHost) {
@@ -3908,14 +3945,15 @@
3908
3945
  if (relative.path[0] === "/") {
3909
3946
  target.path = removeDotSegments(relative.path);
3910
3947
  } else {
3911
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3948
+ const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
3949
+ if (baseHasAuthority && !base.path) {
3912
3950
  target.path = "/" + relative.path;
3913
3951
  } else if (!base.path) {
3914
3952
  target.path = relative.path;
3915
3953
  } else {
3916
3954
  target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
3917
3955
  }
3918
- target.path = removeDotSegments(target.path);
3956
+ target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
3919
3957
  }
3920
3958
  target.query = relative.query;
3921
3959
  }
@@ -3953,12 +3991,13 @@
3953
3991
  const options = Object.assign({}, opts);
3954
3992
  const uriTokens = [];
3955
3993
  if (component.scheme) {
3956
- component.scheme = decodeValidScheme(component.scheme);
3994
+ component.scheme = validateScheme(component.scheme);
3957
3995
  }
3958
3996
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3959
3997
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3960
3998
  const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
3961
- const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3999
+ const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
4000
+ const pathNoScheme = !options.skipEscape && schemelessReference;
3962
4001
  if (component.path !== void 0) {
3963
4002
  if (!options.skipEscape) {
3964
4003
  component.path = serializePathEncoding(component.path, pathNoScheme);
@@ -3967,7 +4006,7 @@
3967
4006
  }
3968
4007
  }
3969
4008
  if (options.reference !== "suffix" && component.scheme) {
3970
- component.scheme = decodeValidScheme(component.scheme);
4009
+ component.scheme = validateScheme(component.scheme);
3971
4010
  uriTokens.push(component.scheme, ":");
3972
4011
  }
3973
4012
  const authority = recomposeAuthority(component);
@@ -3983,9 +4022,9 @@
3983
4022
  if (component.path !== void 0) {
3984
4023
  let s = component.path;
3985
4024
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3986
- s = removeDotSegments(s);
4025
+ s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
3987
4026
  }
3988
- if (pathNoScheme) {
4027
+ if (schemelessReference) {
3989
4028
  s = serializePathEncoding(s, true);
3990
4029
  }
3991
4030
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
@@ -4032,16 +4071,34 @@
4032
4071
  return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4033
4072
  }
4034
4073
  function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4074
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
4075
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4076
+ return true;
4077
+ }
4035
4078
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4079
+ let url2;
4036
4080
  try {
4037
- parsed.host = new URL("http://" + parsed.host).hostname;
4081
+ url2 = new URL("http://" + parsed.host);
4038
4082
  } catch (e) {
4039
4083
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4040
4084
  return true;
4041
4085
  }
4086
+ if (!isHostOnlyURL(url2)) {
4087
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4088
+ return true;
4089
+ }
4090
+ parsed.host = url2.hostname;
4042
4091
  }
4043
4092
  return false;
4044
4093
  }
4094
+ function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
4095
+ return Boolean(
4096
+ parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
4097
+ );
4098
+ }
4099
+ function isHostOnlyURL(url2) {
4100
+ return url2.username === "" && url2.password === "" && url2.port === "" && url2.pathname === "/" && url2.search === "" && url2.hash === "" && url2.hostname !== "";
4101
+ }
4045
4102
  function parseWithStatus(uri, opts) {
4046
4103
  const options = Object.assign({}, opts);
4047
4104
  const parsed = {
@@ -4096,9 +4153,8 @@
4096
4153
  parsed.query = matches[7];
4097
4154
  parsed.fragment = matches[8];
4098
4155
  if (parsed.scheme !== void 0) {
4099
- const decodedScheme = unescape(parsed.scheme);
4100
- if (VALID_SCHEME.test(decodedScheme)) {
4101
- parsed.scheme = decodedScheme.toLowerCase();
4156
+ if (VALID_SCHEME.test(parsed.scheme)) {
4157
+ parsed.scheme = parsed.scheme.toLowerCase();
4102
4158
  } else {
4103
4159
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4104
4160
  malformedScheme = true;
@@ -4126,7 +4182,7 @@
4126
4182
  malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4127
4183
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4128
4184
  if (malformedIPLiteral) {
4129
- parsed.error = parsed.error || "URI host is malformed.";
4185
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4130
4186
  malformedAuthorityOrPort = true;
4131
4187
  }
4132
4188
  } else {
@@ -4154,7 +4210,7 @@
4154
4210
  if (!isIP) {
4155
4211
  host = normalizePercentEncoding(host.toLowerCase());
4156
4212
  }
4157
- parsed.host = reescapeHostDelimiters(host, isIP);
4213
+ parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
4158
4214
  }
4159
4215
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4160
4216
  if (parsed.path) {
@@ -18193,7 +18249,7 @@ gl_Position = vec4(aPosition, 0.0, 1.0);
18193
18249
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/external.js
18194
18250
  config(en_default2());
18195
18251
 
18196
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
18252
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
18197
18253
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
18198
18254
  var JSONRPC_VERSION = "2.0";
18199
18255
  var AssertObjectSchema = custom2((v) => v !== null && (typeof v === "object" || typeof v === "function"));
@@ -19668,14 +19724,14 @@ gl_Position = vec4(aPosition, 0.0, 1.0);
19668
19724
  // ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/parsers/string.js
19669
19725
  var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
19670
19726
 
19671
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
19727
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
19672
19728
  var import_ajv_formats = __toESM(require_dist(), 1);
19673
19729
 
19674
19730
  // ../../node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.browser.js
19675
19731
  var crypto2;
19676
19732
  crypto2 = globalThis.crypto;
19677
19733
 
19678
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js
19734
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js
19679
19735
  var SafeUrlSchema = url().superRefine((val, ctx) => {
19680
19736
  if (!URL.canParse(val)) {
19681
19737
  ctx.addIssue({
@@ -19819,7 +19875,7 @@ gl_Position = vec4(aPosition, 0.0, 1.0);
19819
19875
  token_type_hint: string2().optional()
19820
19876
  }).strip();
19821
19877
 
19822
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js
19878
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js
19823
19879
  var OAuthError = class extends Error {
19824
19880
  constructor(message, errorUri) {
19825
19881
  super(message);
@@ -19914,7 +19970,7 @@ gl_Position = vec4(aPosition, 0.0, 1.0);
19914
19970
  [InvalidTargetError.errorCode]: InvalidTargetError
19915
19971
  };
19916
19972
 
19917
- // ../../node_modules/.pnpm/@opentiny+next@0.3.2_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@opentiny/next/index.js
19973
+ // ../../node_modules/.pnpm/@opentiny+next@0.3.2_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@opentiny/next/index.js
19918
19974
  var _0x4f2826 = _0xad7a;
19919
19975
  (function(_0x5153c5, _0x3abbea) {
19920
19976
  const _0x5f186f = _0xad7a, _0x3f8155 = _0xad7a, _0x5ad68c = _0x5153c5();
@@ -20238,7 +20294,7 @@ gl_Position = vec4(aPosition, 0.0, 1.0);
20238
20294
  }, _0x275e(_0x25fced, _0x3ae797);
20239
20295
  }
20240
20296
 
20241
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.1_hono@4.13.13_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20297
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_@cfworker+json-schema@4.1.6_hono@4.13.17_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20242
20298
  var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
20243
20299
  var McpZodTypeKind;
20244
20300
  (function(McpZodTypeKind2) {
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@opentiny/webmcp-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/opentiny/webmcp-sdk.git",
9
+ "directory": "packages/webmcp-cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/opentiny/webmcp-sdk/issues"
13
+ },
6
14
  "main": "dist/index.js",
7
15
  "types": "dist/index.d.ts",
8
16
  "bin": {
@@ -10,7 +18,8 @@
10
18
  },
11
19
  "files": [
12
20
  "dist",
13
- "README.md"
21
+ "README.md",
22
+ "scripts/ensure-pty-helper.mjs"
14
23
  ],
15
24
  "keywords": [
16
25
  "chrome",
@@ -20,7 +29,7 @@
20
29
  "license": "MIT",
21
30
  "author": "OpenTiny Team",
22
31
  "engines": {
23
- "node": ">=16"
32
+ "node": ">=18.19.0"
24
33
  },
25
34
  "publishConfig": {
26
35
  "access": "public"
@@ -31,6 +40,7 @@
31
40
  "@page-agent/page-controller": "^1.10.0",
32
41
  "clipboardy": "^5.3.1",
33
42
  "commander": "^11.1.0",
43
+ "execa": "^9.6.1",
34
44
  "json5": "^2.2.3",
35
45
  "marked": "^15.0.7",
36
46
  "picocolors": "^1.0.0",
@@ -39,6 +49,9 @@
39
49
  "zod": "^3.23.0",
40
50
  "@opentiny/next-sdk": "^0.4.11"
41
51
  },
52
+ "optionalDependencies": {
53
+ "node-pty": "^1.1.0"
54
+ },
42
55
  "devDependencies": {
43
56
  "@types/node": "^20.0.0",
44
57
  "@types/ws": "^8.5.10",
@@ -54,6 +67,7 @@
54
67
  "build": "tsup && pnpm run build:inject",
55
68
  "link:global": "npm install -g .",
56
69
  "start": "node dist/bin.js",
70
+ "postinstall": "node scripts/ensure-pty-helper.mjs",
57
71
  "test": "vitest run",
58
72
  "test:watch": "vitest",
59
73
  "test:browser": "vitest run --config vitest.e2e.config.ts"
@@ -0,0 +1,24 @@
1
+ import { chmodSync, existsSync, readdirSync } from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import path from 'node:path'
4
+
5
+ // node-pty 预编译包里的 spawn-helper 可能是 0644。安装时改成可执行,
6
+ // 避免之后用非 root 用户运行时无法 chmod。
7
+ try {
8
+ const pkgJson = createRequire(import.meta.url).resolve('node-pty/package.json')
9
+ const prebuilds = path.join(path.dirname(pkgJson), 'prebuilds')
10
+ if (!existsSync(prebuilds)) process.exit(0)
11
+ for (const entry of readdirSync(prebuilds, { withFileTypes: true })) {
12
+ if (!entry.isDirectory()) continue
13
+ const helper = path.join(prebuilds, entry.name, 'spawn-helper')
14
+ if (!existsSync(helper)) continue
15
+ chmodSync(helper, 0o755)
16
+ }
17
+ } catch (err) {
18
+ const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : ''
19
+ if (code === 'EPERM' || code === 'EACCES') {
20
+ console.warn(
21
+ '[webmcp-cli] 无法把 node-pty spawn-helper 设为可执行。交互终端可能启动失败,system/bash 不受影响。'
22
+ )
23
+ }
24
+ }