@zhin.js/agent 0.1.0 → 0.1.3

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.
Files changed (114) hide show
  1. package/lib/cron-engine.d.ts +20 -2
  2. package/lib/cron-engine.d.ts.map +1 -1
  3. package/lib/cron-engine.js +59 -18
  4. package/lib/cron-engine.js.map +1 -1
  5. package/lib/discover-skills.d.ts +3 -1
  6. package/lib/discover-skills.d.ts.map +1 -1
  7. package/lib/discover-skills.js +7 -9
  8. package/lib/discover-skills.js.map +1 -1
  9. package/lib/discover-tools.d.ts +1 -6
  10. package/lib/discover-tools.d.ts.map +1 -1
  11. package/lib/discover-tools.js +2 -6
  12. package/lib/discover-tools.js.map +1 -1
  13. package/lib/index.d.ts +2 -4
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.js +1 -2
  16. package/lib/index.js.map +1 -1
  17. package/lib/init/create-zhin-agent.d.ts.map +1 -1
  18. package/lib/init/create-zhin-agent.js +42 -20
  19. package/lib/init/create-zhin-agent.js.map +1 -1
  20. package/lib/init/register-ai-trigger.d.ts.map +1 -1
  21. package/lib/init/register-ai-trigger.js +10 -3
  22. package/lib/init/register-ai-trigger.js.map +1 -1
  23. package/lib/init/register-builtin-tools.d.ts.map +1 -1
  24. package/lib/init/register-builtin-tools.js +85 -15
  25. package/lib/init/register-builtin-tools.js.map +1 -1
  26. package/lib/init/register-db-models.d.ts.map +1 -1
  27. package/lib/init/register-db-models.js +1 -3
  28. package/lib/init/register-db-models.js.map +1 -1
  29. package/lib/init/register-db-upgrade.d.ts.map +1 -1
  30. package/lib/init/register-db-upgrade.js +1 -8
  31. package/lib/init/register-db-upgrade.js.map +1 -1
  32. package/lib/init/register-management-tools.d.ts.map +1 -1
  33. package/lib/init/register-management-tools.js +33 -20
  34. package/lib/init/register-management-tools.js.map +1 -1
  35. package/lib/service.d.ts.map +1 -1
  36. package/lib/service.js +0 -8
  37. package/lib/service.js.map +1 -1
  38. package/lib/zhin-agent/builtin-tools.d.ts +0 -2
  39. package/lib/zhin-agent/builtin-tools.d.ts.map +1 -1
  40. package/lib/zhin-agent/builtin-tools.js +0 -55
  41. package/lib/zhin-agent/builtin-tools.js.map +1 -1
  42. package/lib/zhin-agent/config.d.ts +2 -1
  43. package/lib/zhin-agent/config.d.ts.map +1 -1
  44. package/lib/zhin-agent/config.js +1 -1
  45. package/lib/zhin-agent/config.js.map +1 -1
  46. package/lib/zhin-agent/index.d.ts +1 -6
  47. package/lib/zhin-agent/index.d.ts.map +1 -1
  48. package/lib/zhin-agent/index.js +26 -34
  49. package/lib/zhin-agent/index.js.map +1 -1
  50. package/lib/zhin-agent/prompt.d.ts.map +1 -1
  51. package/lib/zhin-agent/prompt.js +31 -76
  52. package/lib/zhin-agent/prompt.js.map +1 -1
  53. package/lib/zhin-agent/tool-collector.d.ts.map +1 -1
  54. package/lib/zhin-agent/tool-collector.js +7 -7
  55. package/lib/zhin-agent/tool-collector.js.map +1 -1
  56. package/package.json +7 -4
  57. package/CHANGELOG.md +0 -190
  58. package/lib/follow-up.d.ts +0 -131
  59. package/lib/follow-up.d.ts.map +0 -1
  60. package/lib/follow-up.js +0 -265
  61. package/lib/follow-up.js.map +0 -1
  62. package/src/agent.ts +0 -6
  63. package/src/bootstrap.ts +0 -309
  64. package/src/builtin-tools.ts +0 -958
  65. package/src/compaction.ts +0 -28
  66. package/src/context-manager.ts +0 -15
  67. package/src/conversation-memory.ts +0 -5
  68. package/src/cron-engine.ts +0 -338
  69. package/src/discover-agents.ts +0 -138
  70. package/src/discover-skills.ts +0 -325
  71. package/src/discover-tools.ts +0 -302
  72. package/src/discovery-utils.ts +0 -96
  73. package/src/file-policy.ts +0 -333
  74. package/src/follow-up.ts +0 -357
  75. package/src/hooks.ts +0 -223
  76. package/src/index.ts +0 -183
  77. package/src/init/create-zhin-agent.ts +0 -161
  78. package/src/init/register-ai-service.ts +0 -53
  79. package/src/init/register-ai-trigger.ts +0 -253
  80. package/src/init/register-builtin-tools.ts +0 -308
  81. package/src/init/register-db-models.ts +0 -31
  82. package/src/init/register-db-upgrade.ts +0 -77
  83. package/src/init/register-management-tools.ts +0 -71
  84. package/src/init/register-message-recorder.ts +0 -31
  85. package/src/init/register-tool-service.ts +0 -9
  86. package/src/init/shared-refs.ts +0 -20
  87. package/src/init/types.ts +0 -18
  88. package/src/init.ts +0 -50
  89. package/src/output.ts +0 -15
  90. package/src/rate-limiter.ts +0 -5
  91. package/src/service.ts +0 -228
  92. package/src/session.ts +0 -13
  93. package/src/storage.ts +0 -9
  94. package/src/subagent.ts +0 -209
  95. package/src/tone-detector.ts +0 -5
  96. package/src/tools.ts +0 -214
  97. package/src/user-profile.ts +0 -182
  98. package/src/zhin-agent/builtin-tools.ts +0 -247
  99. package/src/zhin-agent/config.ts +0 -124
  100. package/src/zhin-agent/exec-policy.ts +0 -285
  101. package/src/zhin-agent/index.ts +0 -633
  102. package/src/zhin-agent/prompt.ts +0 -305
  103. package/src/zhin-agent/tool-collector.ts +0 -249
  104. package/tests/ai/follow-up.test.ts +0 -175
  105. package/tests/ai/integration.test.ts +0 -582
  106. package/tests/ai/multimodal.test.ts +0 -106
  107. package/tests/ai/setup.ts +0 -186
  108. package/tests/ai/subagent.test.ts +0 -270
  109. package/tests/ai/tools-builtin.test.ts +0 -310
  110. package/tests/ai/user-profile.test.ts +0 -73
  111. package/tests/ai/zhin-agent.test.ts +0 -306
  112. package/tests/exec-policy.test.ts +0 -355
  113. package/tests/file-policy.test.ts +0 -405
  114. package/tsconfig.json +0 -22
@@ -1,96 +0,0 @@
1
- /**
2
- * 发现模块共用的工具函数
3
- *
4
- * 被 builtin-tools / discover-skills / discover-agents / discover-tools 共同依赖,
5
- * 独立出来以避免循环导入。
6
- */
7
-
8
- import * as fs from 'fs';
9
- import * as os from 'os';
10
- import * as path from 'path';
11
- import type { Plugin } from '@zhin.js/core';
12
-
13
- /** 将 unknown 错误转为字符串 */
14
- export function errMsg(e: unknown): string {
15
- return e instanceof Error ? e.message : String(e);
16
- }
17
-
18
- /** 获取 data/ 目录路径,自动创建 */
19
- export function getDataDir(): string {
20
- const dir = path.join(process.cwd(), 'data');
21
- fs.mkdirSync(dir, { recursive: true });
22
- return dir;
23
- }
24
-
25
- /** 展开路径中的 ~ 为实际 home 目录 */
26
- export function expandHome(p: string): string {
27
- if (p === '~') return os.homedir();
28
- if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(os.homedir(), p.slice(2));
29
- return p;
30
- }
31
-
32
- /** Workspace / ~/.zhin / data 下 skills 根目录(与 activate_skill 扫描顺序一致的前缀) */
33
- export function buildStandardSkillDirs(): string[] {
34
- return [
35
- path.join(process.cwd(), 'skills'),
36
- path.join(os.homedir(), '.zhin', 'skills'),
37
- path.join(getDataDir(), 'skills'),
38
- ];
39
- }
40
-
41
- /**
42
- * 从根插件树收集:根插件与**直接子插件**包目录下的 `skills/`(其下为 `<name>/SKILL.md`)
43
- */
44
- export function collectPluginSkillSearchRoots(root: Plugin | null | undefined): string[] {
45
- if (!root) return [];
46
- const dirs: string[] = [];
47
- const push = (d: string) => {
48
- if (d && !dirs.includes(d)) dirs.push(d);
49
- };
50
- const fromPlugin = (p: Plugin) => {
51
- if (!p?.filePath) return;
52
- const dir = path.dirname(p.filePath);
53
- push(path.join(dir, 'skills'));
54
- // Also check package root when filePath is under src/ or lib/
55
- const dirName = path.basename(dir);
56
- if (dirName === 'src' || dirName === 'lib') {
57
- push(path.join(path.dirname(dir), 'skills'));
58
- }
59
- };
60
- fromPlugin(root);
61
- for (const child of root.children || []) {
62
- fromPlugin(child);
63
- }
64
- return dirs;
65
- }
66
-
67
- /**
68
- * 技能发现与 activate_skill 查找共用:标准目录 + 已加载插件包 skills/
69
- */
70
- export function getSkillSearchDirectories(root?: Plugin | null): string[] {
71
- const list = [...buildStandardSkillDirs()];
72
- for (const d of collectPluginSkillSearchRoots(root ?? undefined)) {
73
- if (!list.includes(d)) list.push(d);
74
- }
75
- return list;
76
- }
77
-
78
- export function mergeSkillDirsWithResolver(resolver?: () => string[]): string[] {
79
- const list = [...buildStandardSkillDirs()];
80
- for (const d of resolver?.() ?? []) {
81
- if (d && !list.includes(d)) list.push(d);
82
- }
83
- return list;
84
- }
85
-
86
- /** 将 Node 文件错误转为 miniclawd 风格的结构化短句,便于模型区分并重试 */
87
- export function nodeErrToFileMessage(err: unknown, filePath: string, kind: 'read' | 'write' | 'edit' | 'list'): string {
88
- const e = err as NodeJS.ErrnoException;
89
- if (e?.code === 'ENOENT') {
90
- if (kind === 'list') return `Error: Directory not found: ${filePath}`;
91
- return `Error: File not found: ${filePath}`;
92
- }
93
- if (e?.code === 'EACCES') return `Error: Permission denied: ${filePath}`;
94
- const action = kind === 'read' ? 'reading file' : kind === 'write' ? 'writing file' : kind === 'edit' ? 'editing file' : 'listing directory';
95
- return `Error ${action}: ${e?.message ?? String(err)}`;
96
- }
@@ -1,333 +0,0 @@
1
- /**
2
- * 文件访问安全策略
3
- *
4
- * 防止 AI Agent 读写敏感文件(如 .env、密钥、证书等),
5
- * 并将 bash/grep/glob 等工具的命令注入风险降到最低。
6
- *
7
- * 四层防御:
8
- * 1. 设备路径阻止 — 阻止 /dev/zero, /dev/stdin 等挂起进程的设备文件
9
- * 2. 敏感文件名/路径模式 — 阻止 .env、私钥、证书等
10
- * 3. 敏感目录 — 阻止 .ssh, .gnupg 等
11
- * 4. bash 安全分类 — 环境变量泄漏 + 命令读写分类
12
- */
13
-
14
- import * as path from 'path';
15
- import * as os from 'os';
16
-
17
- // ── 设备路径阻止(参考 Claude Code FileReadTool BLOCKED_DEVICE_PATHS)──
18
-
19
- /**
20
- * 会导致进程挂起的设备文件路径。
21
- * 检查纯路径即可(无 I/O),安全设备如 /dev/null 不在此列。
22
- */
23
- const BLOCKED_DEVICE_PATHS: ReadonlySet<string> = new Set([
24
- // 无限输出 — 永远无法 EOF
25
- '/dev/zero',
26
- '/dev/random',
27
- '/dev/urandom',
28
- '/dev/full',
29
- // 阻塞等待输入
30
- '/dev/stdin',
31
- '/dev/tty',
32
- '/dev/console',
33
- // 对读取无意义
34
- '/dev/stdout',
35
- '/dev/stderr',
36
- // stdio fd 别名
37
- '/dev/fd/0',
38
- '/dev/fd/1',
39
- '/dev/fd/2',
40
- ]);
41
-
42
- /**
43
- * 检测路径是否为被阻止的设备文件(含 Linux /proc/ fd 别名)。
44
- */
45
- export function isBlockedDevicePath(filePath: string): boolean {
46
- if (BLOCKED_DEVICE_PATHS.has(filePath)) return true;
47
- // /proc/self/fd/0-2 和 /proc/<pid>/fd/0-2 是 Linux 的 stdio 别名
48
- if (
49
- filePath.startsWith('/proc/') &&
50
- (filePath.endsWith('/fd/0') || filePath.endsWith('/fd/1') || filePath.endsWith('/fd/2'))
51
- ) return true;
52
- return false;
53
- }
54
-
55
- // ── 文件大小限制(参考 Claude Code FileEditTool MAX_EDIT_FILE_SIZE)──
56
-
57
- /** 读取文件最大字节数(256 MiB),防止 OOM */
58
- export const MAX_READ_FILE_SIZE = 256 * 1024 * 1024;
59
- /** 编辑文件最大字节数(1 GiB),防止 V8 字符串长度溢出 */
60
- export const MAX_EDIT_FILE_SIZE = 1024 * 1024 * 1024;
61
-
62
- // ── 敏感文件名模式 ──────────────────────────────────────────────────
63
-
64
- /**
65
- * 匹配文件基名(不含目录)的敏感模式。
66
- * 大小写不敏感匹配。
67
- */
68
- const SENSITIVE_BASENAME_PATTERNS: RegExp[] = [
69
- // 环境变量文件
70
- /^\.env(\..*)?$/i,
71
- /^\.zhin\.config(\..*)?$/i, // .env, .env.local, .env.production 等
72
- // 密钥 / 证书
73
- /\.pem$/i,
74
- /\.key$/i,
75
- /\.p12$/i,
76
- /\.pfx$/i,
77
- /\.jks$/i,
78
- /\.keystore$/i,
79
- /^id_rsa$/i,
80
- /^id_ed25519$/i,
81
- /^id_ecdsa$/i,
82
- /^id_dsa$/i,
83
- // 凭据文件
84
- /^\.npmrc$/i,
85
- /^\.pypirc$/i,
86
- /^\.netrc$/i,
87
- /^\.docker\/config\.json$/i,
88
- /^credentials$/i,
89
- /^credentials\.json$/i,
90
- /^service[_-]?account.*\.json$/i,
91
- /^token\.json$/i,
92
- // 数据库 / 密码文件
93
- /^\.pgpass$/i,
94
- /^\.my\.cnf$/i,
95
- /^\.passwd$/i,
96
- // 历史文件(可能含输入的密码等)
97
- /^\.bash_history$/i,
98
- /^\.zsh_history$/i,
99
- /^\.node_repl_history$/i,
100
- /^\.python_history$/i,
101
- ];
102
-
103
- /**
104
- * 被完全禁止访问的目录名(在路径中任何位置出现即拦截)。
105
- */
106
- const SENSITIVE_DIR_NAMES: ReadonlySet<string> = new Set([
107
- '.ssh',
108
- '.gnupg',
109
- '.gpg',
110
- '.aws',
111
- '.azure',
112
- 'data',
113
- '.gcloud',
114
- '.config/gcloud',
115
- '.kube',
116
- ]);
117
-
118
- /**
119
- * 被禁止访问的绝对路径前缀(系统级敏感目录)。
120
- */
121
- const SENSITIVE_PATH_PREFIXES: string[] = [
122
- '/etc/shadow',
123
- '/etc/gshadow',
124
- '/etc/ssl/private',
125
- ];
126
-
127
- // ── 核心检查函数 ────────────────────────────────────────────────────
128
-
129
- export interface FileAccessCheckResult {
130
- allowed: boolean;
131
- reason?: string;
132
- }
133
-
134
- /**
135
- * 检查文件路径是否允许被 AI Agent 访问。
136
- * 该函数只做「阻止明确敏感文件」的检查,不做正向白名单。
137
- */
138
- export function checkFileAccess(filePath: string): FileAccessCheckResult {
139
- // 解析为绝对路径
140
- const resolved = path.resolve(filePath.replace(/^~/, os.homedir()));
141
- const basename = path.basename(resolved);
142
- const normalized = resolved.replace(/\\/g, '/');
143
-
144
- // 1. 检查敏感路径前缀
145
- for (const prefix of SENSITIVE_PATH_PREFIXES) {
146
- if (normalized.startsWith(prefix)) {
147
- return { allowed: false, reason: `拒绝访问系统敏感文件: ${prefix}` };
148
- }
149
- }
150
-
151
- // 2. 检查敏感目录
152
- const parts = normalized.split('/');
153
- for (let i = 0; i < parts.length; i++) {
154
- if (SENSITIVE_DIR_NAMES.has(parts[i])) {
155
- return { allowed: false, reason: `拒绝访问敏感目录: ${parts[i]}` };
156
- }
157
- // 对多级目录名做拼接检查(如 .config/gcloud)
158
- if (i > 0) {
159
- const twoLevel = `${parts[i - 1]}/${parts[i]}`;
160
- if (SENSITIVE_DIR_NAMES.has(twoLevel)) {
161
- return { allowed: false, reason: `拒绝访问敏感目录: ${twoLevel}` };
162
- }
163
- }
164
- }
165
-
166
- // 3. 检查敏感文件名
167
- for (const pattern of SENSITIVE_BASENAME_PATTERNS) {
168
- if (pattern.test(basename)) {
169
- return { allowed: false, reason: `拒绝访问敏感文件: ${basename} 可能包含密钥或凭据` };
170
- }
171
- }
172
-
173
- return { allowed: true };
174
- }
175
-
176
- /**
177
- * 检查文件路径,若不允许则抛出 Error。
178
- * 用于在工具 execute 中调用。
179
- */
180
- export function assertFileAccess(filePath: string): void {
181
- const result = checkFileAccess(filePath);
182
- if (!result.allowed) {
183
- throw new Error(result.reason!);
184
- }
185
- }
186
-
187
- // ── bash 输出敏感信息过滤 ───────────────────────────────────────────
188
-
189
- /**
190
- * 用于检测 bash 命令是否意图泄漏环境变量的模式。
191
- * 匹配命令开头(支持管道前的部分)。
192
- */
193
- const ENV_DUMP_COMMANDS = /^\s*(env|printenv|export|set)\b/;
194
- const ENV_ECHO_PATTERN = /\$\{?\w*(KEY|SECRET|TOKEN|PASSWORD|PASS|CREDENTIAL|AUTH|APIKEY|API_KEY)\w*\}?/i;
195
- const ENV_CAT_SENSITIVE = /\bcat\b.*\.(env|pem|key|p12|pfx)\b/i;
196
-
197
- /**
198
- * 检查 bash 命令是否可能泄漏敏感环境变量或文件。
199
- * 返回 { safe: true } 或 { safe: false, reason: string }。
200
- */
201
- export function checkBashCommandSafety(command: string): { safe: boolean; reason?: string } {
202
- const trimmed = command.trim();
203
-
204
- // 拆管道,检查每段
205
- const segments = trimmed.split(/\s*\|\s*/);
206
- for (const seg of segments) {
207
- if (ENV_DUMP_COMMANDS.test(seg)) {
208
- return { safe: false, reason: '禁止执行环境变量导出命令(env/printenv/export/set),可能泄漏密钥' };
209
- }
210
- }
211
-
212
- if (ENV_ECHO_PATTERN.test(trimmed)) {
213
- return { safe: false, reason: '禁止通过 echo/printf 输出含密钥名的环境变量' };
214
- }
215
-
216
- if (ENV_CAT_SENSITIVE.test(trimmed)) {
217
- return { safe: false, reason: '禁止通过 cat 读取敏感文件(.env/.pem/.key)' };
218
- }
219
-
220
- return { safe: true };
221
- }
222
-
223
- // ── bash 命令读写分类(参考 Claude Code BashTool isSearchOrReadBashCommand)──
224
-
225
- /** 搜索类命令 */
226
- const BASH_SEARCH_COMMANDS: ReadonlySet<string> = new Set([
227
- 'find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis',
228
- ]);
229
-
230
- /** 读取/查看类命令 */
231
- const BASH_READ_COMMANDS: ReadonlySet<string> = new Set([
232
- 'cat', 'head', 'tail', 'less', 'more', 'wc', 'stat', 'file', 'strings',
233
- 'jq', 'awk', 'cut', 'sort', 'uniq', 'tr',
234
- ]);
235
-
236
- /** 目录列出类命令 */
237
- const BASH_LIST_COMMANDS: ReadonlySet<string> = new Set([
238
- 'ls', 'tree', 'du',
239
- ]);
240
-
241
- /** 语义中性命令 — 纯输出/状态命令,不影响管道是否只读 */
242
- const BASH_NEUTRAL_COMMANDS: ReadonlySet<string> = new Set([
243
- 'echo', 'printf', 'true', 'false', ':',
244
- ]);
245
-
246
- export interface BashCommandClassification {
247
- /** 是否为搜索命令 */
248
- isSearch: boolean;
249
- /** 是否为读取命令 */
250
- isRead: boolean;
251
- /** 是否为列出命令 */
252
- isList: boolean;
253
- /** 综合判断:命令是否只读(搜索/读取/列出) */
254
- isReadOnly: boolean;
255
- }
256
-
257
- /**
258
- * 对 bash 命令进行读/写分类。
259
- *
260
- * 对管道命令(如 `cat file | grep pattern`),所有非中性部分
261
- * 都必须是搜索/读/列出类,整条命令才算只读。
262
- *
263
- * 参考 Claude Code BashTool `isSearchOrReadBashCommand`。
264
- */
265
- export function classifyBashCommand(command: string): BashCommandClassification {
266
- const trimmed = command.trim();
267
- // 按管道和操作符拆分
268
- const parts = trimmed.split(/\s*(?:\|\||&&|\||;)\s*/);
269
-
270
- let hasSearch = false;
271
- let hasRead = false;
272
- let hasList = false;
273
- let hasNonNeutral = false;
274
-
275
- for (const part of parts) {
276
- const baseCmd = part.trim().split(/\s+/)[0];
277
- if (!baseCmd) continue;
278
-
279
- // 跳过中性命令
280
- if (BASH_NEUTRAL_COMMANDS.has(baseCmd)) continue;
281
-
282
- hasNonNeutral = true;
283
- if (BASH_SEARCH_COMMANDS.has(baseCmd)) hasSearch = true;
284
- else if (BASH_READ_COMMANDS.has(baseCmd)) hasRead = true;
285
- else if (BASH_LIST_COMMANDS.has(baseCmd)) hasList = true;
286
- else {
287
- // 包含非只读命令 → 整条命令不是只读
288
- return { isSearch: false, isRead: false, isList: false, isReadOnly: false };
289
- }
290
- }
291
-
292
- // 全部中性命令(如 echo "hi")— 视为只读
293
- if (!hasNonNeutral) {
294
- return { isSearch: false, isRead: false, isList: false, isReadOnly: true };
295
- }
296
-
297
- return {
298
- isSearch: hasSearch,
299
- isRead: hasRead,
300
- isList: hasList,
301
- isReadOnly: hasSearch || hasRead || hasList,
302
- };
303
- }
304
-
305
- // ── 文件 mtime 比对(参考 Claude Code FileEditTool stale detection)──
306
-
307
- /**
308
- * 文件修改时间快照,用于检测编辑前是否被并发修改。
309
- */
310
- export async function getFileMtime(filePath: string): Promise<number> {
311
- const { default: fsPromises } = await import('fs/promises');
312
- const stat = await fsPromises.stat(filePath);
313
- return stat.mtimeMs;
314
- }
315
-
316
- /**
317
- * 检查文件在读取后是否被修改。
318
- * @returns true 表示文件已被修改(stale),应中止编辑
319
- */
320
- export function isFileStale(savedMtime: number, currentMtime: number): boolean {
321
- // 允许 1ms 的精度误差
322
- return Math.abs(currentMtime - savedMtime) > 1;
323
- }
324
-
325
- // ── 命令参数转义 ────────────────────────────────────────────────────
326
-
327
- /**
328
- * 安全地转义 shell 参数,防止命令注入。
329
- * 用单引号包裹,内部单引号用 '\'' 转义。
330
- */
331
- export function shellEscape(arg: string): string {
332
- return "'" + arg.replace(/'/g, "'\\''") + "'";
333
- }