aico-cli 0.4.2 → 0.4.5

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.
@@ -0,0 +1,47 @@
1
+ import { p as processManager } from '../cli.mjs';
2
+ import 'cac';
3
+ import 'ansis';
4
+ import './simple-config.mjs';
5
+ import 'inquirer';
6
+ import 'tinyexec';
7
+ import 'node:os';
8
+ import 'node:fs';
9
+ import 'node:child_process';
10
+ import 'node:util';
11
+ import 'child_process';
12
+ import 'util';
13
+ import 'ora';
14
+ import 'dayjs';
15
+ import 'node:path';
16
+ import 'pathe';
17
+ import 'node:url';
18
+ import 'node:events';
19
+
20
+ async function run(command) {
21
+ try {
22
+ const parts = command.split(" ").filter((part) => part.trim());
23
+ if (parts.length === 0) {
24
+ throw new Error("\u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
25
+ }
26
+ const cmd = parts[0];
27
+ const args = parts.slice(1);
28
+ const result = await processManager.spawn(cmd, args, {
29
+ name: cmd,
30
+ timeout: 6e4,
31
+ // 60秒超时
32
+ stdio: "pipe"
33
+ });
34
+ return {
35
+ stdout: result.stdout || "",
36
+ stderr: result.stderr || ""
37
+ };
38
+ } catch (error) {
39
+ if (error instanceof Error) {
40
+ throw new Error(`Command failed: ${command}
41
+ Error: ${error.message}`);
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ export { run };
@@ -13,7 +13,7 @@ import { join as join$1 } from 'node:path';
13
13
  import { join, dirname, basename } from 'pathe';
14
14
  import { fileURLToPath } from 'node:url';
15
15
 
16
- const version = "0.4.2";
16
+ const version = "0.4.5";
17
17
 
18
18
  function displayBanner(subtitle) {
19
19
  const defaultSubtitle = "\u4E00\u952E\u914D\u7F6E\u4F60\u7684\u5F00\u53D1\u73AF\u5883";
package/dist/cli.mjs CHANGED
@@ -4,6 +4,7 @@ import ansis from 'ansis';
4
4
  import { N as createEscapablePrompt, O as displayBannerWithInfo, a as init, P as executeWithEscapeSupport, Q as handleExitPromptError, R as handleGeneralError, T as EscapeKeyPressed, U as displayBanner, V as ConfigCheckerInstaller, W as updateAicoConfig, X as version, Y as InstallationComposer, Z as readAicoConfig } from './chunks/simple-config.mjs';
5
5
  import inquirer$1 from 'inquirer';
6
6
  import { spawn, exec as exec$1 } from 'node:child_process';
7
+ import { EventEmitter } from 'node:events';
7
8
  import 'tinyexec';
8
9
  import 'node:os';
9
10
  import 'node:fs';
@@ -22,25 +23,538 @@ const inquirer = {
22
23
  createPromptModule: inquirer$1.createPromptModule
23
24
  };
24
25
 
25
- async function getCurrentVersion() {
26
- try {
27
- return new Promise((resolve) => {
28
- const child = spawn("npm", ["list", "-g", "aico-cli", "--depth=0"], {
29
- stdio: "pipe",
30
- shell: process.platform === "win32"
26
+ class ProcessManager extends EventEmitter {
27
+ processes = /* @__PURE__ */ new Map();
28
+ isShuttingDown = false;
29
+ constructor() {
30
+ super();
31
+ this.setupSignalHandlers();
32
+ }
33
+ /**
34
+ * 设置信号处理器
35
+ */
36
+ setupSignalHandlers() {
37
+ process.on("SIGINT", () => {
38
+ this.shutdown("SIGINT");
39
+ });
40
+ process.on("SIGTERM", () => {
41
+ this.shutdown("SIGTERM");
42
+ });
43
+ process.on("beforeExit", () => {
44
+ this.cleanup();
45
+ });
46
+ process.on("exit", () => {
47
+ this.cleanup();
48
+ });
49
+ }
50
+ /**
51
+ * 启动进程
52
+ */
53
+ async spawn(command, args = [], options = {}) {
54
+ const startTime = Date.now();
55
+ const processName = options.name || command;
56
+ const isWindows = process.platform === "win32";
57
+ this.log(`\u542F\u52A8\u8FDB\u7A0B: ${processName}`, "info");
58
+ try {
59
+ let finalCommand = command;
60
+ let finalArgs = args;
61
+ if (isWindows) {
62
+ if (command === "npx") {
63
+ finalCommand = "npx.cmd";
64
+ } else if (command === "claude") {
65
+ finalCommand = "claude.cmd";
66
+ }
67
+ }
68
+ const child = spawn(finalCommand, finalArgs, {
69
+ cwd: options.cwd || process.cwd(),
70
+ env: { ...process.env, ...options.env },
71
+ stdio: options.stdio || "pipe",
72
+ shell: false,
73
+ // 禁用shell模式,避免进程泄漏
74
+ windowsHide: isWindows
75
+ // Windows上隐藏子进程窗口
31
76
  });
32
- let output = "";
33
- child.stdout?.on("data", (data) => {
34
- output += data.toString();
77
+ this.registerProcess(child, processName);
78
+ let timeoutId;
79
+ let isTimeout = false;
80
+ if (options.timeout) {
81
+ timeoutId = setTimeout(() => {
82
+ isTimeout = true;
83
+ this.log(`\u8FDB\u7A0B ${processName} \u8D85\u65F6\uFF0C\u6B63\u5728\u7EC8\u6B62...`, "warning");
84
+ child.kill("SIGTERM");
85
+ setTimeout(() => {
86
+ if (child.pid && !child.killed) {
87
+ child.kill("SIGKILL");
88
+ }
89
+ }, 1e3);
90
+ }, options.timeout);
91
+ }
92
+ let stdout = "";
93
+ let stderr = "";
94
+ if (child.stdout) {
95
+ child.stdout.on("data", (data) => {
96
+ stdout += data.toString();
97
+ });
98
+ }
99
+ if (child.stderr) {
100
+ child.stderr.on("data", (data) => {
101
+ stderr += data.toString();
102
+ });
103
+ }
104
+ const exitCode = await new Promise((resolve, reject) => {
105
+ child.on("close", (code) => {
106
+ if (timeoutId) clearTimeout(timeoutId);
107
+ this.unregisterProcess(child.pid);
108
+ resolve(code ?? 0);
109
+ });
110
+ child.on("error", (error) => {
111
+ if (timeoutId) clearTimeout(timeoutId);
112
+ this.unregisterProcess(child.pid);
113
+ reject(error);
114
+ });
35
115
  });
36
- child.on("close", () => {
37
- const match = output.match(/aico-cli@(\d+\.\d+\.\d+)/);
38
- resolve(match ? match[1] : null);
116
+ const duration = Date.now() - startTime;
117
+ const success = exitCode === 0 && !isTimeout;
118
+ this.log(
119
+ `\u8FDB\u7A0B ${processName} \u6267\u884C${success ? "\u6210\u529F" : "\u5931\u8D25"} (${duration}ms)`,
120
+ success ? "success" : "error"
121
+ );
122
+ return {
123
+ exitCode,
124
+ stdout: stdout || void 0,
125
+ stderr: stderr || void 0,
126
+ success,
127
+ duration
128
+ };
129
+ } catch (error) {
130
+ const duration = Date.now() - startTime;
131
+ this.log(`\u8FDB\u7A0B ${processName} \u6267\u884C\u5931\u8D25: ${error}`, "error");
132
+ return {
133
+ exitCode: 1,
134
+ stderr: error instanceof Error ? error.message : String(error),
135
+ success: false,
136
+ duration
137
+ };
138
+ }
139
+ }
140
+ /**
141
+ * 注册进程
142
+ */
143
+ registerProcess(child, name) {
144
+ if (!child.pid) {
145
+ this.log(`\u65E0\u6CD5\u6CE8\u518C\u8FDB\u7A0B ${name}: \u6CA1\u6709PID`, "error");
146
+ return;
147
+ }
148
+ if (this.processes.has(child.pid)) {
149
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u7ECF\u6CE8\u518C\u8FC7`, "warning");
150
+ return;
151
+ }
152
+ const processInfo = {
153
+ pid: child.pid,
154
+ name,
155
+ startTime: /* @__PURE__ */ new Date(),
156
+ status: "running",
157
+ process: child
158
+ };
159
+ this.processes.set(child.pid, processInfo);
160
+ let isCleanedUp = false;
161
+ const cleanup = () => {
162
+ if (!isCleanedUp) {
163
+ isCleanedUp = true;
164
+ this.unregisterProcess(child.pid);
165
+ }
166
+ };
167
+ child.on("close", (code) => {
168
+ const info = this.processes.get(child.pid);
169
+ if (info && !isCleanedUp) {
170
+ info.status = code === 0 ? "completed" : "failed";
171
+ info.exitCode = code ?? void 0;
172
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u5173\u95ED\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
173
+ }
174
+ cleanup();
175
+ });
176
+ child.on("error", (error) => {
177
+ const info = this.processes.get(child.pid);
178
+ if (info && !isCleanedUp) {
179
+ info.status = "failed";
180
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u53D1\u751F\u9519\u8BEF: ${error}`, "error");
181
+ }
182
+ cleanup();
183
+ });
184
+ }
185
+ /**
186
+ * 注销进程
187
+ */
188
+ unregisterProcess(pid) {
189
+ const info = this.processes.get(pid);
190
+ if (info) {
191
+ this.log(`\u6CE8\u9500\u8FDB\u7A0B: ${info.name} (PID: ${pid})`, "info");
192
+ this.processes.delete(pid);
193
+ } else {
194
+ this.log(`\u5C1D\u8BD5\u6CE8\u9500\u4E0D\u5B58\u5728\u7684\u8FDB\u7A0B (PID: ${pid})`, "warning");
195
+ }
196
+ }
197
+ /**
198
+ * 公开的注销进程方法(用于测试)
199
+ */
200
+ unregisterProcessForTesting(pid) {
201
+ this.unregisterProcess(pid);
202
+ }
203
+ /**
204
+ * 优雅关闭
205
+ */
206
+ async shutdown(signal) {
207
+ if (this.isShuttingDown) return;
208
+ this.isShuttingDown = true;
209
+ this.log(`\u6B63\u5728\u5173\u95ED ${this.processes.size} \u4E2A\u8FDB\u7A0B...`, "warning");
210
+ const firstSignalTime = Date.now();
211
+ let forceShutdownRequested = false;
212
+ const forceShutdownHandler = () => {
213
+ forceShutdownRequested = true;
214
+ this.log("\u7528\u6237\u8BF7\u6C42\u5F3A\u5236\u5173\u95ED\uFF0C\u7ACB\u5373\u7EC8\u6B62\u6240\u6709\u8FDB\u7A0B...", "warning");
215
+ };
216
+ process.once("SIGINT", forceShutdownHandler);
217
+ let runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
218
+ if (runningProcesses.length > 0) {
219
+ this.log(`\u5411 ${runningProcesses.length} \u4E2A\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B\u53D1\u9001SIGTERM\u4FE1\u53F7...`, "info");
220
+ for (const info of runningProcesses) {
221
+ try {
222
+ info.process.kill("SIGTERM");
223
+ info.status = "killed";
224
+ } catch (error) {
225
+ this.log(`\u65E0\u6CD5\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
226
+ }
227
+ }
228
+ const maxWaitTime = 1e3;
229
+ const checkInterval = 100;
230
+ let waitedTime = 0;
231
+ while (waitedTime < maxWaitTime && !forceShutdownRequested) {
232
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
233
+ waitedTime += checkInterval;
234
+ runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
235
+ if (runningProcesses.length === 0) {
236
+ break;
237
+ }
238
+ }
239
+ }
240
+ process.removeListener("SIGINT", forceShutdownHandler);
241
+ runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
242
+ if (runningProcesses.length > 0) {
243
+ this.log(`\u5F3A\u5236\u7EC8\u6B62 ${runningProcesses.length} \u4E2A\u4ECD\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B...`, "warning");
244
+ for (const info of runningProcesses) {
245
+ try {
246
+ info.process.kill("SIGKILL");
247
+ this.log(`\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid})`, "warning");
248
+ } catch (error) {
249
+ this.log(`\u65E0\u6CD5\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
250
+ }
251
+ }
252
+ }
253
+ const totalTime = Date.now() - firstSignalTime;
254
+ this.log(`\u5173\u95ED\u5B8C\u6210\uFF0C\u8017\u65F6 ${totalTime}ms`, "success");
255
+ this.cleanup();
256
+ process.exit(0);
257
+ }
258
+ /**
259
+ * 清理所有进程
260
+ */
261
+ cleanup() {
262
+ const isWindows = process.platform === "win32";
263
+ if (isWindows) {
264
+ this.cleanupWindowsProcesses();
265
+ }
266
+ for (const [pid, info] of this.processes) {
267
+ if (info.status === "running") {
268
+ try {
269
+ const signal = isWindows ? "SIGTERM" : "SIGKILL";
270
+ info.process.kill(signal);
271
+ } catch {
272
+ }
273
+ }
274
+ }
275
+ this.cleanupZombieProcesses();
276
+ this.processes.clear();
277
+ }
278
+ /**
279
+ * 获取当前运行的进程数量
280
+ */
281
+ getProcessCount() {
282
+ return this.processes.size;
283
+ }
284
+ /**
285
+ * 获取运行中的进程数量
286
+ */
287
+ getRunningProcessCount() {
288
+ return Array.from(this.processes.values()).filter((p) => p.status === "running").length;
289
+ }
290
+ /**
291
+ * 获取进程列表
292
+ */
293
+ getProcessList() {
294
+ return Array.from(this.processes.values());
295
+ }
296
+ /**
297
+ * 获取运行中的进程列表
298
+ */
299
+ getRunningProcessList() {
300
+ return Array.from(this.processes.values()).filter((p) => p.status === "running");
301
+ }
302
+ /**
303
+ * 检查是否存在进程泄漏(运行中的进程过多)
304
+ */
305
+ checkForProcessLeaks(maxAllowed = 10) {
306
+ const runningCount = this.getRunningProcessCount();
307
+ if (runningCount > maxAllowed) {
308
+ this.log(`\u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u8FDB\u7A0B\u6CC4\u6F0F: ${runningCount} \u4E2A\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B (\u8D85\u8FC7\u9650\u5236 ${maxAllowed})`, "warning");
309
+ return true;
310
+ }
311
+ return false;
312
+ }
313
+ /**
314
+ * 获取进程管理器状态报告
315
+ */
316
+ getStatusReport() {
317
+ const total = this.getProcessCount();
318
+ const running = this.getRunningProcessCount();
319
+ const completed = Array.from(this.processes.values()).filter((p) => p.status === "completed").length;
320
+ const failed = Array.from(this.processes.values()).filter((p) => p.status === "failed").length;
321
+ const killed = Array.from(this.processes.values()).filter((p) => p.status === "killed").length;
322
+ let report = `\u8FDB\u7A0B\u7BA1\u7406\u5668\u72B6\u6001\u62A5\u544A
323
+ `;
324
+ report += `==================
325
+ `;
326
+ report += `\u603B\u8FDB\u7A0B\u6570: ${total}
327
+ `;
328
+ report += `\u8FD0\u884C\u4E2D: ${running}
329
+ `;
330
+ report += `\u5DF2\u5B8C\u6210: ${completed}
331
+ `;
332
+ report += `\u5931\u8D25: ${failed}
333
+ `;
334
+ report += `\u5DF2\u7EC8\u6B62: ${killed}
335
+ `;
336
+ report += `\u5173\u95ED\u72B6\u6001: ${this.isShuttingDown ? "\u6B63\u5728\u5173\u95ED" : "\u6B63\u5E38\u8FD0\u884C"}
337
+ `;
338
+ if (running > 0) {
339
+ report += `
340
+ \u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B:
341
+ `;
342
+ this.getRunningProcessList().forEach((process2) => {
343
+ const duration = Date.now() - process2.startTime.getTime();
344
+ const durationSeconds = Math.floor(duration / 1e3);
345
+ report += ` - ${process2.name} (PID: ${process2.pid}) - \u8FD0\u884C\u65F6\u95F4: ${durationSeconds}s
346
+ `;
39
347
  });
40
- child.on("error", () => {
41
- resolve(null);
348
+ }
349
+ return report;
350
+ }
351
+ /**
352
+ * 强制清理僵尸进程(状态为运行中但实际已退出的进程)
353
+ */
354
+ cleanupZombieProcesses() {
355
+ let cleanedCount = 0;
356
+ const runningProcesses = this.getRunningProcessList();
357
+ const isWindows = process.platform === "win32";
358
+ this.log(`\u5F00\u59CB\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B\uFF0C\u5F53\u524D\u8FD0\u884C\u4E2D\u8FDB\u7A0B\u6570: ${runningProcesses.length}`, "info");
359
+ for (const process2 of runningProcesses) {
360
+ try {
361
+ process2.process.kill(0);
362
+ if (isWindows) {
363
+ if (process2.process.exitCode !== null) {
364
+ this.log(`\u6E05\u7406Windows\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
365
+ this.unregisterProcess(process2.pid);
366
+ cleanedCount++;
367
+ }
368
+ }
369
+ } catch (error) {
370
+ this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
371
+ this.unregisterProcess(process2.pid);
372
+ cleanedCount++;
373
+ }
374
+ }
375
+ if (cleanedCount > 0) {
376
+ this.log(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u50F5\u5C38\u8FDB\u7A0B`, "info");
377
+ }
378
+ return cleanedCount;
379
+ }
380
+ /**
381
+ * 深度清理进程,包括可能的残留进程
382
+ */
383
+ deepCleanup() {
384
+ const isWindows = process.platform === "win32";
385
+ let totalCleaned = 0;
386
+ this.log("\u5F00\u59CB\u6DF1\u5EA6\u6E05\u7406\u8FDB\u7A0B...", "info");
387
+ totalCleaned += this.cleanupZombieProcesses();
388
+ if (isWindows) {
389
+ totalCleaned += this.cleanupWindowsProcesses();
390
+ }
391
+ const remainingProcesses = this.getProcessCount();
392
+ if (remainingProcesses > 0) {
393
+ this.log(`\u5F3A\u5236\u6E05\u7406\u5269\u4F59\u7684 ${remainingProcesses} \u4E2A\u8FDB\u7A0B`, "warning");
394
+ this.cleanup();
395
+ totalCleaned += remainingProcesses;
396
+ }
397
+ this.log(`\u6DF1\u5EA6\u6E05\u7406\u5B8C\u6210\uFF0C\u603B\u5171\u6E05\u7406\u4E86 ${totalCleaned} \u4E2A\u8FDB\u7A0B`, "info");
398
+ return totalCleaned;
399
+ }
400
+ /**
401
+ * Windows平台特定的进程清理
402
+ * 针对Windows的进程管理特点进行优化
403
+ */
404
+ cleanupWindowsProcesses() {
405
+ const isWindows = process.platform === "win32";
406
+ if (!isWindows) {
407
+ return 0;
408
+ }
409
+ let cleanedCount = 0;
410
+ const runningProcesses = this.getRunningProcessList();
411
+ this.log("\u6267\u884CWindows\u5E73\u53F0\u7279\u5B9A\u8FDB\u7A0B\u6E05\u7406...", "info");
412
+ for (const process2 of runningProcesses) {
413
+ try {
414
+ if (process2.process.exitCode !== null) {
415
+ this.log(`\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
416
+ this.unregisterProcess(process2.pid);
417
+ cleanedCount++;
418
+ }
419
+ } catch (error) {
420
+ this.log(`\u5F3A\u5236\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "error");
421
+ this.unregisterProcess(process2.pid);
422
+ cleanedCount++;
423
+ }
424
+ }
425
+ if (cleanedCount > 0) {
426
+ this.log(`Windows\u5E73\u53F0\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u8FDB\u7A0B`, "info");
427
+ }
428
+ return cleanedCount;
429
+ }
430
+ /**
431
+ * 添加进程到列表(用于测试)
432
+ */
433
+ addProcessForTesting(processInfo) {
434
+ this.processes.set(processInfo.pid, processInfo);
435
+ }
436
+ /**
437
+ * 替换当前进程(exec 模式)
438
+ * 这个方法会完全替换当前进程,不会返回
439
+ */
440
+ async replaceProcess(command, args = [], options = {}) {
441
+ const processName = options.name || command;
442
+ const isWindows = process.platform === "win32";
443
+ try {
444
+ this.cleanup();
445
+ const { spawn: spawn2 } = await import('node:child_process');
446
+ let finalCommand = command;
447
+ let finalArgs = args;
448
+ if (isWindows) {
449
+ if (command === "npx") {
450
+ finalCommand = "npx.cmd";
451
+ } else if (command === "claude") {
452
+ finalCommand = "claude.cmd";
453
+ }
454
+ }
455
+ const child = spawn2(finalCommand, finalArgs, {
456
+ stdio: "inherit",
457
+ // 继承所有 stdio
458
+ cwd: options.cwd || process.cwd(),
459
+ env: { ...process.env, ...options.env },
460
+ shell: false,
461
+ // 禁用shell模式,避免进程泄漏
462
+ // Windows上使用更严格的进程控制
463
+ windowsHide: isWindows
464
+ // Windows上隐藏子进程窗口
465
+ });
466
+ child.on("error", (error) => {
467
+ this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
468
+ this.cleanup();
469
+ process.exit(1);
470
+ });
471
+ child.on("exit", (code) => {
472
+ this.log(`\u66FF\u6362\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
473
+ this.cleanup();
474
+ process.exit(code || 0);
42
475
  });
476
+ this.registerProcess(child, processName);
477
+ if (isWindows) {
478
+ const checkInterval = setInterval(() => {
479
+ if (child.exitCode !== null) {
480
+ clearInterval(checkInterval);
481
+ this.cleanup();
482
+ process.exit(child.exitCode || 0);
483
+ }
484
+ }, 1e3);
485
+ child.on("exit", () => {
486
+ clearInterval(checkInterval);
487
+ });
488
+ }
489
+ await new Promise(() => {
490
+ });
491
+ process.exit(0);
492
+ } catch (error) {
493
+ this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
494
+ this.cleanup();
495
+ process.exit(1);
496
+ }
497
+ }
498
+ /**
499
+ * 终止特定进程
500
+ */
501
+ killProcess(pid, signal = "SIGTERM") {
502
+ const info = this.processes.get(pid);
503
+ if (info && info.status === "running") {
504
+ try {
505
+ info.process.kill(signal);
506
+ info.status = "killed";
507
+ return true;
508
+ } catch {
509
+ return false;
510
+ }
511
+ }
512
+ return false;
513
+ }
514
+ /**
515
+ * 日志输出
516
+ */
517
+ log(message, level = "info") {
518
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
519
+ const prefix = `[${timestamp}] [ProcessManager]`;
520
+ switch (level) {
521
+ case "info":
522
+ console.log(ansis.gray(`${prefix} ${message}`));
523
+ break;
524
+ case "warning":
525
+ console.log(ansis.yellow(`${prefix} ${message}`));
526
+ break;
527
+ case "error":
528
+ console.error(ansis.red(`${prefix} ${message}`));
529
+ break;
530
+ case "success":
531
+ console.log(ansis.green(`${prefix} ${message}`));
532
+ break;
533
+ }
534
+ }
535
+ }
536
+ const processManager = new ProcessManager();
537
+
538
+ const processManager$1 = {
539
+ __proto__: null,
540
+ ProcessManager: ProcessManager,
541
+ processManager: processManager
542
+ };
543
+
544
+ async function getCurrentVersion() {
545
+ try {
546
+ const result = await processManager.spawn("npm", ["list", "-g", "aico-cli", "--depth=0"], {
547
+ name: "npm list",
548
+ stdio: "pipe",
549
+ shell: process.platform === "win32",
550
+ timeout: 1e4
551
+ // 10秒超时
43
552
  });
553
+ if (result.success && result.stdout) {
554
+ const match = result.stdout.match(/aico-cli@(\d+\.\d+\.\d+)/);
555
+ return match ? match[1] : null;
556
+ }
557
+ return null;
44
558
  } catch {
45
559
  return null;
46
560
  }
@@ -55,48 +569,22 @@ async function updateAicoCli() {
55
569
  console.log(ansis.gray(`\u{1F4E6} \u5F53\u524D\u7248\u672C: ${currentVersion}`));
56
570
  }
57
571
  console.log(ansis.cyan("\u23F3 \u6B63\u5728\u6267\u884C\u81EA\u52A8\u66F4\u65B0..."));
58
- const isWindows = process.platform === "win32";
59
- const child = spawn("npm", ["install", "-g", "aico-cli"], {
572
+ const result = await processManager.spawn("npm", ["install", "-g", "aico-cli"], {
573
+ name: "npm install",
60
574
  stdio: "inherit",
61
575
  // 继承父进程的 stdio,显示安装进度
62
- cwd: process.cwd(),
63
- shell: isWindows
64
- // Windows 上使用 shell
65
- });
66
- const timeout = setTimeout(() => {
67
- console.log(ansis.yellow("\n\u26A0\uFE0F \u66F4\u65B0\u65F6\u95F4\u8F83\u957F\uFF0C\u53EF\u80FD\u9047\u5230\u7F51\u7EDC\u95EE\u9898"));
68
- console.log(ansis.cyan("\u{1F4A1} \u5EFA\u8BAE\u6309 Ctrl+C \u53D6\u6D88\uFF0C\u7136\u540E\u624B\u52A8\u6267\u884C:"));
69
- console.log(ansis.white(" npm install -g aico-cli"));
70
- }, 15e3);
71
- await new Promise((resolve, reject) => {
72
- child.on("close", (code) => {
73
- clearTimeout(timeout);
74
- if (code === 0) {
75
- console.log(ansis.green("\n\u2705 aico-cli \u66F4\u65B0\u5B8C\u6210"));
76
- resolve();
77
- } else {
78
- reject(new Error(`npm \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`));
79
- }
80
- });
81
- child.on("error", (error) => {
82
- clearTimeout(timeout);
83
- reject(error);
84
- });
85
- const handleInterrupt = () => {
86
- clearTimeout(timeout);
87
- child.kill("SIGINT");
88
- console.log(ansis.yellow("\n\n\u26A0\uFE0F \u66F4\u65B0\u5DF2\u53D6\u6D88"));
89
- console.log(ansis.cyan("\u{1F4A1} \u8BF7\u624B\u52A8\u6267\u884C: npm install -g aico-cli"));
90
- process.exit(0);
91
- };
92
- process.once("SIGINT", handleInterrupt);
93
- child.on("close", () => {
94
- process.removeListener("SIGINT", handleInterrupt);
95
- });
576
+ timeout: 3e5,
577
+ // 5分钟超时(npm安装可能较慢)
578
+ signalHandling: "graceful"
96
579
  });
580
+ if (result.success) {
581
+ console.log(ansis.green("\n\u2705 aico-cli \u66F4\u65B0\u5B8C\u6210"));
582
+ } else {
583
+ throw new Error(`npm \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${result.exitCode}`);
584
+ }
97
585
  } catch (error) {
98
586
  console.error(ansis.red("\u274C \u66F4\u65B0 aico-cli \u5931\u8D25:"));
99
- if (error.code === "ENOENT") {
587
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
100
588
  console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 npm"));
101
589
  console.error(
102
590
  ansis.gray(" \u8BF7\u8BBF\u95EE https://nodejs.org/ \u4E0B\u8F7D\u5E76\u5B89\u88C5 Node.js")
@@ -106,6 +594,7 @@ async function updateAicoCli() {
106
594
  }
107
595
  console.error(ansis.cyan("\n\u{1F4A1} \u6216\u8005\u8BF7\u590D\u5236\u4EE5\u4E0B\u547D\u4EE4\u5728\u547D\u4EE4\u884C\u4E2D\u624B\u52A8\u6267\u884C:"));
108
596
  console.error(ansis.white(" npm install -g aico-cli"));
597
+ processManager.cleanup();
109
598
  throw error;
110
599
  }
111
600
  }
@@ -116,39 +605,15 @@ promisify$1(exec$1);
116
605
 
117
606
  promisify$1(exec$1);
118
607
 
119
- const execAsync = promisify(exec);
120
- async function run(command) {
121
- try {
122
- const result = await execAsync(command);
123
- return result;
124
- } catch (error) {
125
- if (error instanceof Error) {
126
- throw new Error(`Command failed: ${command}
127
- Error: ${error.message}`);
128
- }
129
- throw error;
130
- }
131
- }
132
-
133
- const runCommand = {
134
- __proto__: null,
135
- run: run
136
- };
137
-
138
608
  async function tryStartClaude(command, args, options) {
139
- return new Promise((resolve, reject) => {
140
- const child = spawn(command, args, options);
141
- child.on("close", (code) => {
142
- if (code === 0) {
143
- console.log(ansis.green("\u2705 \u4EE3\u7801\u7F16\u8F91\u5668\u5DF2\u9000\u51FA"));
144
- resolve();
145
- } else {
146
- reject(new Error(`Claude \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`));
147
- }
148
- });
149
- child.on("error", (error) => {
150
- reject(error);
151
- });
609
+ const isWindows = process.platform === "win32";
610
+ await processManager.replaceProcess(command, args, {
611
+ name: "Claude Code",
612
+ cwd: options.cwd,
613
+ stdio: "inherit",
614
+ // 必须继承 stdio 才能正确交互
615
+ shell: isWindows ? false : options.shell
616
+ // Windows上禁用shell避免进程泄漏
152
617
  });
153
618
  }
154
619
  async function startClaudeCodeEditor(_lang) {
@@ -160,18 +625,28 @@ async function startClaudeCodeEditor(_lang) {
160
625
  // 继承父进程的 stdio,这样可以在当前终端中交互
161
626
  cwd: process.cwd(),
162
627
  // 使用当前工作目录
163
- shell: isWindows
164
- // Windows 上使用 shell 来解析命令
628
+ shell: false
629
+ // 禁用shell模式,避免进程泄漏(Windows和Unix都适用)
165
630
  };
166
631
  try {
167
632
  const command = isWindows ? "claude.cmd" : "claude";
168
- await tryStartClaude(command, ["--dangerously-skip-permissions"], spawnOptions);
633
+ const args = ["--dangerously-skip-permissions"];
634
+ if (isWindows) {
635
+ await tryStartClaude(command, args, spawnOptions);
636
+ } else {
637
+ await tryStartClaude(command, args, spawnOptions);
638
+ }
169
639
  return;
170
640
  } catch (firstError) {
171
641
  if (firstError.code === "ENOENT") {
172
642
  console.log(ansis.yellow("\u26A0\uFE0F \u76F4\u63A5\u8C03\u7528 claude \u5931\u8D25\uFF0C\u5C1D\u8BD5\u4F7F\u7528 npx..."));
173
643
  try {
174
- await tryStartClaude("npx", ["@anthropic-ai/claude-code", "--dangerously-skip-permissions"], spawnOptions);
644
+ const npxArgs = ["@anthropic-ai/claude-code", "--dangerously-skip-permissions"];
645
+ if (isWindows) {
646
+ await tryStartClaude("npx.cmd", npxArgs, spawnOptions);
647
+ } else {
648
+ await tryStartClaude("npx", npxArgs, spawnOptions);
649
+ }
175
650
  return;
176
651
  } catch (secondError) {
177
652
  throw firstError;
@@ -182,20 +657,50 @@ async function startClaudeCodeEditor(_lang) {
182
657
  }
183
658
  } catch (error) {
184
659
  console.error(ansis.red("\u274C \u542F\u52A8\u4EE3\u7801\u7F16\u8F91\u5668\u5931\u8D25:"));
185
- if (error.code === "ENOENT" || error.message?.includes("command not found")) {
660
+ const isWindows = process.platform === "win32";
661
+ if (error.code === "ENOENT" || error.message?.includes("command not found") || error.message?.includes("ENOENT")) {
186
662
  console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code:"));
187
663
  console.error(ansis.gray(" npm install -g @anthropic-ai/claude-code"));
188
- if (process.platform === "win32") {
664
+ if (isWindows) {
189
665
  console.error(ansis.yellow("\u{1F4A1} Windows \u7528\u6237\u989D\u5916\u63D0\u793A:"));
190
666
  console.error(ansis.gray(" 1. \u91CD\u542F\u547D\u4EE4\u884C\u7A97\u53E3\u4EE5\u5237\u65B0 PATH \u73AF\u5883\u53D8\u91CF"));
191
667
  console.error(ansis.gray(" 2. \u786E\u8BA4 npm \u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u5728 PATH \u4E2D"));
192
- console.error(ansis.gray(" 3. \u5C1D\u8BD5\u4F7F\u7528 npx @anthropic-ai/claude-code --dangerously-skip-permissions"));
668
+ console.error(ansis.gray(" 3. \u5C1D\u8BD5\u4F7F\u7528 npx.cmd @anthropic-ai/claude-code --dangerously-skip-permissions"));
669
+ console.error(ansis.gray(" 4. \u68C0\u67E5\u662F\u5426\u5B89\u88C5\u4E86 Node.js \u548C npm"));
670
+ }
671
+ } else if (error.message?.includes("spawn") || error.message?.includes("\u8FDB\u7A0B")) {
672
+ console.error(ansis.yellow("\u{1F4A1} \u8FDB\u7A0B\u542F\u52A8\u5931\u8D25\uFF0C\u53EF\u80FD\u662F\u8FDB\u7A0B\u7BA1\u7406\u95EE\u9898:"));
673
+ console.error(ansis.gray(" 1. \u68C0\u67E5\u7CFB\u7EDF\u662F\u5426\u6709\u8DB3\u591F\u7684\u8D44\u6E90"));
674
+ console.error(ansis.gray(" 2. \u5C1D\u8BD5\u5173\u95ED\u5176\u4ED6\u5E94\u7528\u7A0B\u5E8F\u91CA\u653E\u5185\u5B58"));
675
+ if (isWindows) {
676
+ console.error(ansis.gray(" 3. Windows\u7528\u6237: \u5C1D\u8BD5\u4EE5\u7BA1\u7406\u5458\u6743\u9650\u8FD0\u884C"));
677
+ console.error(ansis.gray(" 4. Windows\u7528\u6237: \u68C0\u67E5\u9632\u75C5\u6BD2\u8F6F\u4EF6\u662F\u5426\u963B\u6B62\u4E86\u8FDB\u7A0B\u542F\u52A8"));
193
678
  }
194
679
  } else {
195
680
  console.error(ansis.gray(` \u9519\u8BEF\u4FE1\u606F: ${error.message || error}`));
681
+ console.error(ansis.gray(` \u9519\u8BEF\u4EE3\u7801: ${error.code || "\u65E0"}`));
682
+ }
683
+ try {
684
+ const processCount = processManager.getProcessCount();
685
+ const runningCount = processManager.getRunningProcessCount();
686
+ console.error(ansis.yellow(`\u{1F4CA} \u5F53\u524D\u8FDB\u7A0B\u72B6\u6001: \u603B\u8FDB\u7A0B\u6570 ${processCount}, \u8FD0\u884C\u4E2D ${runningCount}`));
687
+ if (runningCount > 0) {
688
+ console.error(ansis.yellow("\u{1F4A1} \u68C0\u6D4B\u5230\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B\uFF0C\u6B63\u5728\u6E05\u7406..."));
689
+ }
690
+ } catch (reportError) {
196
691
  }
197
692
  console.error(ansis.cyan("\n\u{1F4A1} \u6216\u8005\u8BF7\u590D\u5236\u4EE5\u4E0B\u547D\u4EE4\u5728\u547D\u4EE4\u884C\u4E2D\u624B\u52A8\u6267\u884C:"));
198
- console.error(ansis.white(" claude --dangerously-skip-permissions"));
693
+ if (isWindows) {
694
+ console.error(ansis.white(" claude.cmd --dangerously-skip-permissions"));
695
+ } else {
696
+ console.error(ansis.white(" claude --dangerously-skip-permissions"));
697
+ }
698
+ processManager.cleanup();
699
+ if (isWindows) {
700
+ setTimeout(() => {
701
+ processManager.cleanup();
702
+ }, 100);
703
+ }
199
704
  throw error;
200
705
  }
201
706
  }
@@ -212,7 +717,7 @@ async function launchCodeEditor() {
212
717
  }
213
718
  async function launchCUI() {
214
719
  try {
215
- const { run } = await Promise.resolve().then(function () { return runCommand; });
720
+ const { run } = await import('./chunks/run-command.mjs');
216
721
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5PM2...\x1B[0m");
217
722
  await run("npm install -g pm2");
218
723
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5AICO\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
@@ -504,6 +1009,8 @@ async function startCodeEditor() {
504
1009
  await startClaudeCodeEditor();
505
1010
  } catch (error) {
506
1011
  console.error(ansis.red(`\u2716 \u542F\u52A8\u4EE3\u7801\u7F16\u8F91\u5668\u5931\u8D25: ${error}`));
1012
+ const { processManager } = await Promise.resolve().then(function () { return processManager$1; });
1013
+ processManager.cleanup();
507
1014
  process.exit(1);
508
1015
  }
509
1016
  }
@@ -529,14 +1036,22 @@ function customizeHelp(sections) {
529
1036
  ` ${ansis.cyan("aico c")} (--company) \u516C\u53F8\u914D\u7F6E`,
530
1037
  ` ${ansis.cyan("aico p")} (--personal) \u4E2A\u4EBA\u914D\u7F6E`,
531
1038
  ` ${ansis.cyan("aico ui")} (--ui) \u542F\u52A8\u53EF\u89C6\u5316\u754C\u9762`,
532
- ` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9`
1039
+ ` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9`,
1040
+ "",
1041
+ ansis.gray(" \u8FDB\u7A0B\u7BA1\u7406:"),
1042
+ ` ${ansis.cyan("aico test-process")} \u6D4B\u8BD5\u8FDB\u7A0B\u7BA1\u7406\u529F\u80FD`,
1043
+ ` ${ansis.cyan("aico monitor-process")} \u542F\u52A8\u8FDB\u7A0B\u76D1\u63A7`,
1044
+ ` ${ansis.cyan("aico cleanup-process")} \u6E05\u7406\u6240\u6709\u8FDB\u7A0B`,
1045
+ ` ${ansis.cyan("aico test-replacement")} \u6D4B\u8BD5\u8FDB\u7A0B\u66FF\u6362\u529F\u80FD`,
1046
+ ` ${ansis.cyan("aico test-claude-start")} \u6D4B\u8BD5Claude Code\u542F\u52A8\u6D41\u7A0B`,
1047
+ ` ${ansis.cyan("aico test-start-flow")} \u6D4B\u8BD5\u5B9E\u9645\u542F\u52A8\u6D41\u7A0B\uFF08\u6A21\u62DF\uFF09`
533
1048
  ].join("\n")
534
1049
  });
535
1050
  return sections;
536
1051
  }
537
1052
  async function startUI() {
538
1053
  try {
539
- const { run } = await Promise.resolve().then(function () { return runCommand; });
1054
+ const { run } = await import('./chunks/run-command.mjs');
540
1055
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5\u667A\u80FD\u8F6F\u4EF6\u661F\u5DE5\u5382\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
541
1056
  await run("npm install -g pm2");
542
1057
  await run("npm install -g aico-cui");
@@ -572,4 +1087,20 @@ async function main() {
572
1087
  setupCommands(cli);
573
1088
  cli.parse();
574
1089
  }
575
- main().catch(console.error);
1090
+ process.on("uncaughtException", (error) => {
1091
+ console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error);
1092
+ processManager.cleanup();
1093
+ process.exit(1);
1094
+ });
1095
+ process.on("unhandledRejection", (reason, promise) => {
1096
+ console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason);
1097
+ processManager.cleanup();
1098
+ process.exit(1);
1099
+ });
1100
+ main().catch((error) => {
1101
+ console.error("CLI \u542F\u52A8\u5931\u8D25:", error);
1102
+ processManager.cleanup();
1103
+ process.exit(1);
1104
+ });
1105
+
1106
+ export { processManager as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aico-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
4
4
  "packageManager": "pnpm@9.15.9",
5
5
  "description": "AI CLI",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: 管理 Git worktree,在项目平级的 ../.zcf/项目名/ 目录下创建,支持智能默认、IDE 集成和内容迁移
2
+ description: 管理 Git worktree,在项目平级的 ../.aico/项目名/ 目录下创建,支持智能默认、IDE 集成和内容迁移
3
3
  allowed-tools: Read(**), Exec(git worktree add, git worktree list, git worktree remove, git worktree prune, git branch, git checkout, git rev-parse, git stash, git cp, detect-ide, open-ide, which, command, basename, dirname)
4
4
  argument-hint: <add|list|remove|prune|migrate> [path] [-b <branch>] [-o|--open] [--track] [--guess-remote] [--detach] [--checkout] [--lock] [--migrate-from <source-path>] [--migrate-stash]
5
5
  # examples:
@@ -12,7 +12,7 @@ argument-hint: <add|list|remove|prune|migrate> [path] [-b <branch>] [-o|--open]
12
12
 
13
13
  # Claude Command: Git Worktree
14
14
 
15
- 管理 Git worktree,支持智能默认、IDE 集成和内容迁移,使用结构化的 `../.zcf/项目名/` 路径。
15
+ 管理 Git worktree,支持智能默认、IDE 集成和内容迁移,使用结构化的 `../.aico/项目名/` 路径。
16
16
 
17
17
  直接执行命令并提供简洁结果。
18
18
 
@@ -38,7 +38,7 @@ argument-hint: <add|list|remove|prune|migrate> [path] [-b <branch>] [-o|--open]
38
38
 
39
39
  | 选项 | 说明 |
40
40
  | ------------------ | -------------------------------------------- |
41
- | `add [<path>]` | 在 `../.zcf/项目名/<path>` 添加新的 worktree |
41
+ | `add [<path>]` | 在 `../.aico/项目名/<path>` 添加新的 worktree |
42
42
  | `migrate <target>` | 迁移内容到指定 worktree |
43
43
  | `list` | 列出所有 worktree 及其状态 |
44
44
  | `remove <path>` | 删除指定路径的 worktree |
@@ -63,7 +63,7 @@ argument-hint: <add|list|remove|prune|migrate> [path] [-b <branch>] [-o|--open]
63
63
 
64
64
  2. **智能路径管理**
65
65
  - 使用 worktree 检测自动从主仓库路径计算项目名
66
- - 在结构化的 `../.zcf/项目名/<path>` 目录创建 worktree
66
+ - 在结构化的 `../.aico/项目名/<path>` 目录创建 worktree
67
67
  - 正确处理主仓库和 worktree 执行上下文
68
68
 
69
69
  ```bash
@@ -84,13 +84,13 @@ get_main_repo_path() {
84
84
 
85
85
  MAIN_REPO_PATH=$(get_main_repo_path)
86
86
  PROJECT_NAME=$(basename "$MAIN_REPO_PATH")
87
- WORKTREE_BASE="$MAIN_REPO_PATH/../.zcf/$PROJECT_NAME"
87
+ WORKTREE_BASE="$MAIN_REPO_PATH/../.aico/$PROJECT_NAME"
88
88
 
89
89
  # 始终使用绝对路径防止嵌套问题
90
90
  ABSOLUTE_WORKTREE_PATH="$WORKTREE_BASE/<path>"
91
91
  ```
92
92
 
93
- **关键修复**: 在现有 worktree 内创建新 worktree 时,始终使用绝对路径以防止出现类似 `../.zcf/project/.zcf/project/path` 的路径嵌套问题。
93
+ **关键修复**: 在现有 worktree 内创建新 worktree 时,始终使用绝对路径以防止出现类似 `../.aico/project/.aico/project/path` 的路径嵌套问题。
94
94
 
95
95
  3. **Worktree 操作**
96
96
  - **add**: 使用智能分支/路径默认创建新 worktree
@@ -112,7 +112,7 @@ ABSOLUTE_WORKTREE_PATH="$WORKTREE_BASE/<path>"
112
112
  6. **安全特性**
113
113
  - **路径冲突防护**: 创建前检查目录是否已存在
114
114
  - **分支检出验证**: 确保分支未被其他地方使用
115
- - **绝对路径强制**: 防止在 worktree 内创建嵌套的 `.zcf` 目录
115
+ - **绝对路径强制**: 防止在 worktree 内创建嵌套的 `.aico` 目录
116
116
  - **删除时自动清理**: 同时清理目录和 git 引用
117
117
  - **清晰的状态报告**: 显示 worktree 位置和分支状态
118
118
 
@@ -216,12 +216,12 @@ copy_environment_files() {
216
216
  **示例输出**:
217
217
 
218
218
  ```
219
- ✅ Worktree created at ../.zcf/项目名/feature-ui
219
+ ✅ Worktree created at ../.aico/项目名/feature-ui
220
220
  ✅ 已复制 .env
221
221
  ✅ 已复制 .env.local
222
222
  📋 已从 .gitignore 复制 2 个环境文件
223
- 🖥️ 是否在 IDE 中打开 ../.zcf/项目名/feature-ui?[y/n]: y
224
- 🚀 正在用 VS Code 打开 ../.zcf/项目名/feature-ui...
223
+ 🖥️ 是否在 IDE 中打开 ../.aico/项目名/feature-ui?[y/n]: y
224
+ 🚀 正在用 VS Code 打开 ../.aico/项目名/feature-ui...
225
225
  ```
226
226
 
227
227
  ---
@@ -233,7 +233,7 @@ parent-directory/
233
233
  ├── your-project/ # 主项目
234
234
  │ ├── .git/
235
235
  │ └── src/
236
- └── .zcf/ # worktree 管理
236
+ └── .aico/ # worktree 管理
237
237
  └── your-project/ # 项目 worktree
238
238
  ├── feature-ui/ # 功能分支
239
239
  ├── hotfix/ # 修复分支