aico-cli 0.4.2 → 0.4.4

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.4";
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,434 @@ 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
+ this.log(`\u542F\u52A8\u8FDB\u7A0B: ${processName}`, "info");
57
+ try {
58
+ const child = spawn(command, args, {
59
+ cwd: options.cwd || process.cwd(),
60
+ env: { ...process.env, ...options.env },
61
+ stdio: options.stdio || "pipe",
62
+ shell: options.shell || process.platform === "win32"
31
63
  });
32
- let output = "";
33
- child.stdout?.on("data", (data) => {
34
- output += data.toString();
64
+ this.registerProcess(child, processName);
65
+ let timeoutId;
66
+ let isTimeout = false;
67
+ if (options.timeout) {
68
+ timeoutId = setTimeout(() => {
69
+ isTimeout = true;
70
+ this.log(`\u8FDB\u7A0B ${processName} \u8D85\u65F6\uFF0C\u6B63\u5728\u7EC8\u6B62...`, "warning");
71
+ child.kill("SIGTERM");
72
+ setTimeout(() => {
73
+ if (child.pid && !child.killed) {
74
+ child.kill("SIGKILL");
75
+ }
76
+ }, 1e3);
77
+ }, options.timeout);
78
+ }
79
+ let stdout = "";
80
+ let stderr = "";
81
+ if (child.stdout) {
82
+ child.stdout.on("data", (data) => {
83
+ stdout += data.toString();
84
+ });
85
+ }
86
+ if (child.stderr) {
87
+ child.stderr.on("data", (data) => {
88
+ stderr += data.toString();
89
+ });
90
+ }
91
+ const exitCode = await new Promise((resolve, reject) => {
92
+ child.on("close", (code) => {
93
+ if (timeoutId) clearTimeout(timeoutId);
94
+ this.unregisterProcess(child.pid);
95
+ resolve(code ?? 0);
96
+ });
97
+ child.on("error", (error) => {
98
+ if (timeoutId) clearTimeout(timeoutId);
99
+ this.unregisterProcess(child.pid);
100
+ reject(error);
101
+ });
35
102
  });
36
- child.on("close", () => {
37
- const match = output.match(/aico-cli@(\d+\.\d+\.\d+)/);
38
- resolve(match ? match[1] : null);
103
+ const duration = Date.now() - startTime;
104
+ const success = exitCode === 0 && !isTimeout;
105
+ this.log(
106
+ `\u8FDB\u7A0B ${processName} \u6267\u884C${success ? "\u6210\u529F" : "\u5931\u8D25"} (${duration}ms)`,
107
+ success ? "success" : "error"
108
+ );
109
+ return {
110
+ exitCode,
111
+ stdout: stdout || void 0,
112
+ stderr: stderr || void 0,
113
+ success,
114
+ duration
115
+ };
116
+ } catch (error) {
117
+ const duration = Date.now() - startTime;
118
+ this.log(`\u8FDB\u7A0B ${processName} \u6267\u884C\u5931\u8D25: ${error}`, "error");
119
+ return {
120
+ exitCode: 1,
121
+ stderr: error instanceof Error ? error.message : String(error),
122
+ success: false,
123
+ duration
124
+ };
125
+ }
126
+ }
127
+ /**
128
+ * 注册进程
129
+ */
130
+ registerProcess(child, name) {
131
+ if (!child.pid) {
132
+ this.log(`\u65E0\u6CD5\u6CE8\u518C\u8FDB\u7A0B ${name}: \u6CA1\u6709PID`, "error");
133
+ return;
134
+ }
135
+ if (this.processes.has(child.pid)) {
136
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u7ECF\u6CE8\u518C\u8FC7`, "warning");
137
+ return;
138
+ }
139
+ const processInfo = {
140
+ pid: child.pid,
141
+ name,
142
+ startTime: /* @__PURE__ */ new Date(),
143
+ status: "running",
144
+ process: child
145
+ };
146
+ this.processes.set(child.pid, processInfo);
147
+ let isCleanedUp = false;
148
+ const cleanup = () => {
149
+ if (!isCleanedUp) {
150
+ isCleanedUp = true;
151
+ this.unregisterProcess(child.pid);
152
+ }
153
+ };
154
+ child.on("close", (code) => {
155
+ const info = this.processes.get(child.pid);
156
+ if (info && !isCleanedUp) {
157
+ info.status = code === 0 ? "completed" : "failed";
158
+ info.exitCode = code ?? void 0;
159
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u5DF2\u5173\u95ED\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
160
+ }
161
+ cleanup();
162
+ });
163
+ child.on("error", (error) => {
164
+ const info = this.processes.get(child.pid);
165
+ if (info && !isCleanedUp) {
166
+ info.status = "failed";
167
+ this.log(`\u8FDB\u7A0B ${name} (PID: ${child.pid}) \u53D1\u751F\u9519\u8BEF: ${error}`, "error");
168
+ }
169
+ cleanup();
170
+ });
171
+ }
172
+ /**
173
+ * 注销进程
174
+ */
175
+ unregisterProcess(pid) {
176
+ const info = this.processes.get(pid);
177
+ if (info) {
178
+ this.log(`\u6CE8\u9500\u8FDB\u7A0B: ${info.name} (PID: ${pid})`, "info");
179
+ this.processes.delete(pid);
180
+ } else {
181
+ this.log(`\u5C1D\u8BD5\u6CE8\u9500\u4E0D\u5B58\u5728\u7684\u8FDB\u7A0B (PID: ${pid})`, "warning");
182
+ }
183
+ }
184
+ /**
185
+ * 公开的注销进程方法(用于测试)
186
+ */
187
+ unregisterProcessForTesting(pid) {
188
+ this.unregisterProcess(pid);
189
+ }
190
+ /**
191
+ * 优雅关闭
192
+ */
193
+ async shutdown(signal) {
194
+ if (this.isShuttingDown) return;
195
+ this.isShuttingDown = true;
196
+ this.log(`\u6B63\u5728\u5173\u95ED ${this.processes.size} \u4E2A\u8FDB\u7A0B...`, "warning");
197
+ const firstSignalTime = Date.now();
198
+ let forceShutdownRequested = false;
199
+ const forceShutdownHandler = () => {
200
+ forceShutdownRequested = true;
201
+ this.log("\u7528\u6237\u8BF7\u6C42\u5F3A\u5236\u5173\u95ED\uFF0C\u7ACB\u5373\u7EC8\u6B62\u6240\u6709\u8FDB\u7A0B...", "warning");
202
+ };
203
+ process.once("SIGINT", forceShutdownHandler);
204
+ let runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
205
+ if (runningProcesses.length > 0) {
206
+ this.log(`\u5411 ${runningProcesses.length} \u4E2A\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B\u53D1\u9001SIGTERM\u4FE1\u53F7...`, "info");
207
+ for (const info of runningProcesses) {
208
+ try {
209
+ info.process.kill("SIGTERM");
210
+ info.status = "killed";
211
+ } catch (error) {
212
+ this.log(`\u65E0\u6CD5\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
213
+ }
214
+ }
215
+ const maxWaitTime = 1e3;
216
+ const checkInterval = 100;
217
+ let waitedTime = 0;
218
+ while (waitedTime < maxWaitTime && !forceShutdownRequested) {
219
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
220
+ waitedTime += checkInterval;
221
+ runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
222
+ if (runningProcesses.length === 0) {
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ process.removeListener("SIGINT", forceShutdownHandler);
228
+ runningProcesses = Array.from(this.processes.values()).filter((p) => p.status === "running");
229
+ if (runningProcesses.length > 0) {
230
+ this.log(`\u5F3A\u5236\u7EC8\u6B62 ${runningProcesses.length} \u4E2A\u4ECD\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B...`, "warning");
231
+ for (const info of runningProcesses) {
232
+ try {
233
+ info.process.kill("SIGKILL");
234
+ this.log(`\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid})`, "warning");
235
+ } catch (error) {
236
+ this.log(`\u65E0\u6CD5\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${info.name} (PID: ${info.pid}): ${error}`, "error");
237
+ }
238
+ }
239
+ }
240
+ const totalTime = Date.now() - firstSignalTime;
241
+ this.log(`\u5173\u95ED\u5B8C\u6210\uFF0C\u8017\u65F6 ${totalTime}ms`, "success");
242
+ this.cleanup();
243
+ process.exit(0);
244
+ }
245
+ /**
246
+ * 清理所有进程
247
+ */
248
+ cleanup() {
249
+ for (const [pid, info] of this.processes) {
250
+ if (info.status === "running") {
251
+ try {
252
+ info.process.kill("SIGKILL");
253
+ } catch {
254
+ }
255
+ }
256
+ }
257
+ this.processes.clear();
258
+ }
259
+ /**
260
+ * 获取当前运行的进程数量
261
+ */
262
+ getProcessCount() {
263
+ return this.processes.size;
264
+ }
265
+ /**
266
+ * 获取运行中的进程数量
267
+ */
268
+ getRunningProcessCount() {
269
+ return Array.from(this.processes.values()).filter((p) => p.status === "running").length;
270
+ }
271
+ /**
272
+ * 获取进程列表
273
+ */
274
+ getProcessList() {
275
+ return Array.from(this.processes.values());
276
+ }
277
+ /**
278
+ * 获取运行中的进程列表
279
+ */
280
+ getRunningProcessList() {
281
+ return Array.from(this.processes.values()).filter((p) => p.status === "running");
282
+ }
283
+ /**
284
+ * 检查是否存在进程泄漏(运行中的进程过多)
285
+ */
286
+ checkForProcessLeaks(maxAllowed = 10) {
287
+ const runningCount = this.getRunningProcessCount();
288
+ if (runningCount > maxAllowed) {
289
+ 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");
290
+ return true;
291
+ }
292
+ return false;
293
+ }
294
+ /**
295
+ * 获取进程管理器状态报告
296
+ */
297
+ getStatusReport() {
298
+ const total = this.getProcessCount();
299
+ const running = this.getRunningProcessCount();
300
+ const completed = Array.from(this.processes.values()).filter((p) => p.status === "completed").length;
301
+ const failed = Array.from(this.processes.values()).filter((p) => p.status === "failed").length;
302
+ const killed = Array.from(this.processes.values()).filter((p) => p.status === "killed").length;
303
+ let report = `\u8FDB\u7A0B\u7BA1\u7406\u5668\u72B6\u6001\u62A5\u544A
304
+ `;
305
+ report += `==================
306
+ `;
307
+ report += `\u603B\u8FDB\u7A0B\u6570: ${total}
308
+ `;
309
+ report += `\u8FD0\u884C\u4E2D: ${running}
310
+ `;
311
+ report += `\u5DF2\u5B8C\u6210: ${completed}
312
+ `;
313
+ report += `\u5931\u8D25: ${failed}
314
+ `;
315
+ report += `\u5DF2\u7EC8\u6B62: ${killed}
316
+ `;
317
+ report += `\u5173\u95ED\u72B6\u6001: ${this.isShuttingDown ? "\u6B63\u5728\u5173\u95ED" : "\u6B63\u5E38\u8FD0\u884C"}
318
+ `;
319
+ if (running > 0) {
320
+ report += `
321
+ \u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B:
322
+ `;
323
+ this.getRunningProcessList().forEach((process2) => {
324
+ const duration = Date.now() - process2.startTime.getTime();
325
+ const durationSeconds = Math.floor(duration / 1e3);
326
+ report += ` - ${process2.name} (PID: ${process2.pid}) - \u8FD0\u884C\u65F6\u95F4: ${durationSeconds}s
327
+ `;
39
328
  });
40
- child.on("error", () => {
41
- resolve(null);
329
+ }
330
+ return report;
331
+ }
332
+ /**
333
+ * 强制清理僵尸进程(状态为运行中但实际已退出的进程)
334
+ */
335
+ cleanupZombieProcesses() {
336
+ let cleanedCount = 0;
337
+ const runningProcesses = this.getRunningProcessList();
338
+ for (const process2 of runningProcesses) {
339
+ try {
340
+ process2.process.kill(0);
341
+ } catch (error) {
342
+ this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
343
+ this.unregisterProcess(process2.pid);
344
+ cleanedCount++;
345
+ }
346
+ }
347
+ if (cleanedCount > 0) {
348
+ this.log(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u50F5\u5C38\u8FDB\u7A0B`, "info");
349
+ }
350
+ return cleanedCount;
351
+ }
352
+ /**
353
+ * 添加进程到列表(用于测试)
354
+ */
355
+ addProcessForTesting(processInfo) {
356
+ this.processes.set(processInfo.pid, processInfo);
357
+ }
358
+ /**
359
+ * 替换当前进程(exec 模式)
360
+ * 这个方法会完全替换当前进程,不会返回
361
+ */
362
+ async replaceProcess(command, args = [], options = {}) {
363
+ const processName = options.name || command;
364
+ try {
365
+ this.log(`\u6E05\u7406 ${this.processes.size} \u4E2A\u7BA1\u7406\u8FDB\u7A0B...`, "info");
366
+ this.cleanup();
367
+ const { spawn: spawn2 } = await import('node:child_process');
368
+ const child = spawn2(command, args, {
369
+ stdio: "inherit",
370
+ // 继承所有 stdio
371
+ cwd: options.cwd || process.cwd(),
372
+ env: { ...process.env, ...options.env },
373
+ shell: options.shell || process.platform === "win32"
374
+ // 移除 detached: true,避免子进程脱离管理
375
+ // detached: false 是默认值,子进程会随着父进程退出而退出
376
+ });
377
+ child.on("error", (error) => {
378
+ this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
379
+ process.exit(1);
42
380
  });
381
+ child.on("exit", (code) => {
382
+ this.log(`\u66FF\u6362\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
383
+ process.exit(code || 0);
384
+ });
385
+ this.registerProcess(child, processName);
386
+ await new Promise(() => {
387
+ });
388
+ process.exit(0);
389
+ } catch (error) {
390
+ this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
391
+ process.exit(1);
392
+ }
393
+ }
394
+ /**
395
+ * 终止特定进程
396
+ */
397
+ killProcess(pid, signal = "SIGTERM") {
398
+ const info = this.processes.get(pid);
399
+ if (info && info.status === "running") {
400
+ try {
401
+ info.process.kill(signal);
402
+ info.status = "killed";
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+ return false;
409
+ }
410
+ /**
411
+ * 日志输出
412
+ */
413
+ log(message, level = "info") {
414
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
415
+ const prefix = `[${timestamp}] [ProcessManager]`;
416
+ switch (level) {
417
+ case "info":
418
+ console.log(ansis.gray(`${prefix} ${message}`));
419
+ break;
420
+ case "warning":
421
+ console.log(ansis.yellow(`${prefix} ${message}`));
422
+ break;
423
+ case "error":
424
+ console.error(ansis.red(`${prefix} ${message}`));
425
+ break;
426
+ case "success":
427
+ console.log(ansis.green(`${prefix} ${message}`));
428
+ break;
429
+ }
430
+ }
431
+ }
432
+ const processManager = new ProcessManager();
433
+
434
+ const processManager$1 = {
435
+ __proto__: null,
436
+ ProcessManager: ProcessManager,
437
+ processManager: processManager
438
+ };
439
+
440
+ async function getCurrentVersion() {
441
+ try {
442
+ const result = await processManager.spawn("npm", ["list", "-g", "aico-cli", "--depth=0"], {
443
+ name: "npm list",
444
+ stdio: "pipe",
445
+ shell: process.platform === "win32",
446
+ timeout: 1e4
447
+ // 10秒超时
43
448
  });
449
+ if (result.success && result.stdout) {
450
+ const match = result.stdout.match(/aico-cli@(\d+\.\d+\.\d+)/);
451
+ return match ? match[1] : null;
452
+ }
453
+ return null;
44
454
  } catch {
45
455
  return null;
46
456
  }
@@ -55,48 +465,22 @@ async function updateAicoCli() {
55
465
  console.log(ansis.gray(`\u{1F4E6} \u5F53\u524D\u7248\u672C: ${currentVersion}`));
56
466
  }
57
467
  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"], {
468
+ const result = await processManager.spawn("npm", ["install", "-g", "aico-cli"], {
469
+ name: "npm install",
60
470
  stdio: "inherit",
61
471
  // 继承父进程的 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
- });
472
+ timeout: 3e5,
473
+ // 5分钟超时(npm安装可能较慢)
474
+ signalHandling: "graceful"
96
475
  });
476
+ if (result.success) {
477
+ console.log(ansis.green("\n\u2705 aico-cli \u66F4\u65B0\u5B8C\u6210"));
478
+ } else {
479
+ throw new Error(`npm \u8FDB\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${result.exitCode}`);
480
+ }
97
481
  } catch (error) {
98
482
  console.error(ansis.red("\u274C \u66F4\u65B0 aico-cli \u5931\u8D25:"));
99
- if (error.code === "ENOENT") {
483
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
100
484
  console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 npm"));
101
485
  console.error(
102
486
  ansis.gray(" \u8BF7\u8BBF\u95EE https://nodejs.org/ \u4E0B\u8F7D\u5E76\u5B89\u88C5 Node.js")
@@ -106,6 +490,7 @@ async function updateAicoCli() {
106
490
  }
107
491
  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
492
  console.error(ansis.white(" npm install -g aico-cli"));
493
+ processManager.cleanup();
109
494
  throw error;
110
495
  }
111
496
  }
@@ -116,39 +501,13 @@ promisify$1(exec$1);
116
501
 
117
502
  promisify$1(exec$1);
118
503
 
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
504
  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
- });
505
+ await processManager.replaceProcess(command, args, {
506
+ name: "Claude Code",
507
+ cwd: options.cwd,
508
+ stdio: "inherit",
509
+ // 必须继承 stdio 才能正确交互
510
+ shell: options.shell
152
511
  });
153
512
  }
154
513
  async function startClaudeCodeEditor(_lang) {
@@ -182,7 +541,7 @@ async function startClaudeCodeEditor(_lang) {
182
541
  }
183
542
  } catch (error) {
184
543
  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")) {
544
+ if (error.code === "ENOENT" || error.message?.includes("command not found") || error.message?.includes("ENOENT")) {
186
545
  console.error(ansis.yellow("\u{1F4A1} \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code:"));
187
546
  console.error(ansis.gray(" npm install -g @anthropic-ai/claude-code"));
188
547
  if (process.platform === "win32") {
@@ -196,6 +555,7 @@ async function startClaudeCodeEditor(_lang) {
196
555
  }
197
556
  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
557
  console.error(ansis.white(" claude --dangerously-skip-permissions"));
558
+ processManager.cleanup();
199
559
  throw error;
200
560
  }
201
561
  }
@@ -212,7 +572,7 @@ async function launchCodeEditor() {
212
572
  }
213
573
  async function launchCUI() {
214
574
  try {
215
- const { run } = await Promise.resolve().then(function () { return runCommand; });
575
+ const { run } = await import('./chunks/run-command.mjs');
216
576
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5PM2...\x1B[0m");
217
577
  await run("npm install -g pm2");
218
578
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5AICO\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
@@ -504,6 +864,8 @@ async function startCodeEditor() {
504
864
  await startClaudeCodeEditor();
505
865
  } catch (error) {
506
866
  console.error(ansis.red(`\u2716 \u542F\u52A8\u4EE3\u7801\u7F16\u8F91\u5668\u5931\u8D25: ${error}`));
867
+ const { processManager } = await Promise.resolve().then(function () { return processManager$1; });
868
+ processManager.cleanup();
507
869
  process.exit(1);
508
870
  }
509
871
  }
@@ -529,14 +891,22 @@ function customizeHelp(sections) {
529
891
  ` ${ansis.cyan("aico c")} (--company) \u516C\u53F8\u914D\u7F6E`,
530
892
  ` ${ansis.cyan("aico p")} (--personal) \u4E2A\u4EBA\u914D\u7F6E`,
531
893
  ` ${ansis.cyan("aico ui")} (--ui) \u542F\u52A8\u53EF\u89C6\u5316\u754C\u9762`,
532
- ` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9`
894
+ ` ${ansis.cyan("aico h")} (--help) \u663E\u793A\u5E2E\u52A9`,
895
+ "",
896
+ ansis.gray(" \u8FDB\u7A0B\u7BA1\u7406:"),
897
+ ` ${ansis.cyan("aico test-process")} \u6D4B\u8BD5\u8FDB\u7A0B\u7BA1\u7406\u529F\u80FD`,
898
+ ` ${ansis.cyan("aico monitor-process")} \u542F\u52A8\u8FDB\u7A0B\u76D1\u63A7`,
899
+ ` ${ansis.cyan("aico cleanup-process")} \u6E05\u7406\u6240\u6709\u8FDB\u7A0B`,
900
+ ` ${ansis.cyan("aico test-replacement")} \u6D4B\u8BD5\u8FDB\u7A0B\u66FF\u6362\u529F\u80FD`,
901
+ ` ${ansis.cyan("aico test-claude-start")} \u6D4B\u8BD5Claude Code\u542F\u52A8\u6D41\u7A0B`,
902
+ ` ${ansis.cyan("aico test-start-flow")} \u6D4B\u8BD5\u5B9E\u9645\u542F\u52A8\u6D41\u7A0B\uFF08\u6A21\u62DF\uFF09`
533
903
  ].join("\n")
534
904
  });
535
905
  return sections;
536
906
  }
537
907
  async function startUI() {
538
908
  try {
539
- const { run } = await Promise.resolve().then(function () { return runCommand; });
909
+ const { run } = await import('./chunks/run-command.mjs');
540
910
  console.log("\x1B[36m\u6B63\u5728\u5B89\u88C5\u667A\u80FD\u8F6F\u4EF6\u661F\u5DE5\u5382\u53EF\u89C6\u5316\u754C\u9762...\x1B[0m");
541
911
  await run("npm install -g pm2");
542
912
  await run("npm install -g aico-cui");
@@ -572,4 +942,20 @@ async function main() {
572
942
  setupCommands(cli);
573
943
  cli.parse();
574
944
  }
575
- main().catch(console.error);
945
+ process.on("uncaughtException", (error) => {
946
+ console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error);
947
+ processManager.cleanup();
948
+ process.exit(1);
949
+ });
950
+ process.on("unhandledRejection", (reason, promise) => {
951
+ console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason);
952
+ processManager.cleanup();
953
+ process.exit(1);
954
+ });
955
+ main().catch((error) => {
956
+ console.error("CLI \u542F\u52A8\u5931\u8D25:", error);
957
+ processManager.cleanup();
958
+ process.exit(1);
959
+ });
960
+
961
+ 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.4",
4
4
  "packageManager": "pnpm@9.15.9",
5
5
  "description": "AI CLI",
6
6
  "repository": {