aico-cli 0.4.8 → 0.5.0

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.
@@ -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.8";
16
+ const version = "0.5.0";
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
@@ -47,39 +47,6 @@ class ProcessManager extends EventEmitter {
47
47
  this.cleanup();
48
48
  });
49
49
  }
50
- /**
51
- * 获取适用于当前平台的命令路径
52
- */
53
- getPlatformCommand(command) {
54
- const isWindows = process.platform === "win32";
55
- if (isWindows) {
56
- const commandMappings = {
57
- "npm": "npm.cmd",
58
- "npx": "npx.cmd",
59
- "node": "node.exe",
60
- "claude": "claude.cmd"
61
- };
62
- return commandMappings[command] || command;
63
- }
64
- return command;
65
- }
66
- /**
67
- * 检查命令是否存在
68
- */
69
- async checkCommandExists(command) {
70
- try {
71
- const { execSync } = await import('node:child_process');
72
- const isWindows = process.platform === "win32";
73
- if (isWindows) {
74
- execSync(`where ${command}`, { stdio: "ignore" });
75
- } else {
76
- execSync(`which ${command}`, { stdio: "ignore" });
77
- }
78
- return true;
79
- } catch {
80
- return false;
81
- }
82
- }
83
50
  /**
84
51
  * 启动进程
85
52
  */
@@ -88,12 +55,7 @@ class ProcessManager extends EventEmitter {
88
55
  const processName = options.name || command;
89
56
  this.log(`\u542F\u52A8\u8FDB\u7A0B: ${processName}`, "info");
90
57
  try {
91
- const platformCommand = this.getPlatformCommand(command);
92
- const commandExists = await this.checkCommandExists(platformCommand);
93
- if (!commandExists) {
94
- throw new Error(`\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF`);
95
- }
96
- const child = spawn(platformCommand, args, {
58
+ const child = spawn(command, args, {
97
59
  cwd: options.cwd || process.cwd(),
98
60
  env: { ...process.env, ...options.env },
99
61
  stdio: options.stdio || "pipe",
@@ -129,25 +91,13 @@ class ProcessManager extends EventEmitter {
129
91
  const exitCode = await new Promise((resolve, reject) => {
130
92
  child.on("close", (code) => {
131
93
  if (timeoutId) clearTimeout(timeoutId);
132
- if (child.pid) {
133
- this.unregisterProcess(child.pid);
134
- }
94
+ this.unregisterProcess(child.pid);
135
95
  resolve(code ?? 0);
136
96
  });
137
97
  child.on("error", (error) => {
138
98
  if (timeoutId) clearTimeout(timeoutId);
139
- if (child.pid) {
140
- this.unregisterProcess(child.pid);
141
- }
142
- let enhancedError = error;
143
- if (error.message?.includes("ENOENT")) {
144
- const isWindows = process.platform === "win32";
145
- const commandInfo = isWindows ? `\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\u3002\u5728Windows\u4E0A\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5Node.js\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF\u3002\u5C1D\u8BD5\u4F7F\u7528\u5B8C\u6574\u8DEF\u5F84\u6216\u68C0\u67E5\u547D\u4EE4\u662F\u5426\u6B63\u786E\u3002` : `\u547D\u4EE4 '${platformCommand}' \u4E0D\u5B58\u5728\u3002\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5\u5E76\u6DFB\u52A0\u5230PATH\u73AF\u5883\u53D8\u91CF\u3002`;
146
- enhancedError = new Error(`${error.message}
147
- ${commandInfo}`);
148
- enhancedError.stack = error.stack;
149
- }
150
- reject(enhancedError);
99
+ this.unregisterProcess(child.pid);
100
+ reject(error);
151
101
  });
152
102
  });
153
103
  const duration = Date.now() - startTime;
@@ -180,12 +130,6 @@ ${commandInfo}`);
180
130
  registerProcess(child, name) {
181
131
  if (!child.pid) {
182
132
  this.log(`\u65E0\u6CD5\u6CE8\u518C\u8FDB\u7A0B ${name}: \u6CA1\u6709PID`, "error");
183
- child.on("close", (code) => {
184
- this.log(`\u8FDB\u7A0B ${name} (\u65E0PID) \u5DF2\u5173\u95ED\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
185
- });
186
- child.on("error", (error) => {
187
- this.log(`\u8FDB\u7A0B ${name} (\u65E0PID) \u53D1\u751F\u9519\u8BEF: ${error}`, "error");
188
- });
189
133
  return;
190
134
  }
191
135
  if (this.processes.has(child.pid)) {
@@ -229,10 +173,6 @@ ${commandInfo}`);
229
173
  * 注销进程
230
174
  */
231
175
  unregisterProcess(pid) {
232
- if (!pid) {
233
- this.log(`\u5C1D\u8BD5\u6CE8\u9500\u65E0\u6548\u7684\u8FDB\u7A0B (PID: undefined)`, "warning");
234
- return;
235
- }
236
176
  const info = this.processes.get(pid);
237
177
  if (info) {
238
178
  this.log(`\u6CE8\u9500\u8FDB\u7A0B: ${info.name} (PID: ${pid})`, "info");
@@ -306,20 +246,14 @@ ${commandInfo}`);
306
246
  * 清理所有进程
307
247
  */
308
248
  cleanup() {
309
- const isWindows = process.platform === "win32";
310
- if (isWindows) {
311
- this.cleanupWindowsProcesses();
312
- }
313
249
  for (const [pid, info] of this.processes) {
314
250
  if (info.status === "running") {
315
251
  try {
316
- const signal = isWindows ? "SIGTERM" : "SIGKILL";
317
- info.process.kill(signal);
252
+ info.process.kill("SIGKILL");
318
253
  } catch {
319
254
  }
320
255
  }
321
256
  }
322
- this.cleanupZombieProcesses();
323
257
  this.processes.clear();
324
258
  }
325
259
  /**
@@ -396,81 +330,38 @@ ${commandInfo}`);
396
330
  return report;
397
331
  }
398
332
  /**
399
- * 强制清理僵尸进程(状态为运行中但实际已退出的进程)
400
- */
401
- cleanupZombieProcesses() {
402
- let cleanedCount = 0;
403
- const runningProcesses = this.getRunningProcessList();
404
- const isWindows = process.platform === "win32";
405
- this.log(`\u5F00\u59CB\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B\uFF0C\u5F53\u524D\u8FD0\u884C\u4E2D\u8FDB\u7A0B\u6570: ${runningProcesses.length}`, "info");
406
- for (const process2 of runningProcesses) {
407
- try {
408
- process2.process.kill(0);
409
- if (isWindows) {
410
- if (process2.process.exitCode !== null) {
411
- this.log(`\u6E05\u7406Windows\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
412
- this.unregisterProcess(process2.pid);
413
- cleanedCount++;
414
- }
415
- }
416
- } catch (error) {
417
- this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
418
- this.unregisterProcess(process2.pid);
419
- cleanedCount++;
420
- }
421
- }
422
- if (cleanedCount > 0) {
423
- this.log(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u50F5\u5C38\u8FDB\u7A0B`, "info");
424
- }
425
- return cleanedCount;
426
- }
427
- /**
428
- * 深度清理进程,包括可能的残留进程
333
+ * 获取适用于当前平台的命令路径
429
334
  */
430
- deepCleanup() {
335
+ getPlatformCommand(command) {
431
336
  const isWindows = process.platform === "win32";
432
- let totalCleaned = 0;
433
- this.log("\u5F00\u59CB\u6DF1\u5EA6\u6E05\u7406\u8FDB\u7A0B...", "info");
434
- totalCleaned += this.cleanupZombieProcesses();
435
337
  if (isWindows) {
436
- totalCleaned += this.cleanupWindowsProcesses();
437
- }
438
- const remainingProcesses = this.getProcessCount();
439
- if (remainingProcesses > 0) {
440
- this.log(`\u5F3A\u5236\u6E05\u7406\u5269\u4F59\u7684 ${remainingProcesses} \u4E2A\u8FDB\u7A0B`, "warning");
441
- this.cleanup();
442
- totalCleaned += remainingProcesses;
338
+ const commandMappings = {
339
+ "npm": "npm.cmd",
340
+ "npx": "npx.cmd",
341
+ "node": "node.exe",
342
+ "claude": "claude.cmd"
343
+ };
344
+ return commandMappings[command] || command;
443
345
  }
444
- this.log(`\u6DF1\u5EA6\u6E05\u7406\u5B8C\u6210\uFF0C\u603B\u5171\u6E05\u7406\u4E86 ${totalCleaned} \u4E2A\u8FDB\u7A0B`, "info");
445
- return totalCleaned;
346
+ return command;
446
347
  }
447
348
  /**
448
- * Windows平台特定的进程清理
449
- * 针对Windows的进程管理特点进行优化
349
+ * 强制清理僵尸进程(状态为运行中但实际已退出的进程)
450
350
  */
451
- cleanupWindowsProcesses() {
452
- const isWindows = process.platform === "win32";
453
- if (!isWindows) {
454
- return 0;
455
- }
351
+ cleanupZombieProcesses() {
456
352
  let cleanedCount = 0;
457
353
  const runningProcesses = this.getRunningProcessList();
458
- this.log("\u6267\u884CWindows\u5E73\u53F0\u7279\u5B9A\u8FDB\u7A0B\u6E05\u7406...", "info");
459
354
  for (const process2 of runningProcesses) {
460
355
  try {
461
- if (process2.process.exitCode !== null) {
462
- this.log(`\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
463
- this.unregisterProcess(process2.pid);
464
- cleanedCount++;
465
- }
356
+ process2.process.kill(0);
466
357
  } catch (error) {
467
- this.log(`\u5F3A\u5236\u6E05\u7406Windows\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "error");
358
+ this.log(`\u6E05\u7406\u50F5\u5C38\u8FDB\u7A0B: ${process2.name} (PID: ${process2.pid})`, "warning");
468
359
  this.unregisterProcess(process2.pid);
469
360
  cleanedCount++;
470
361
  }
471
362
  }
472
363
  if (cleanedCount > 0) {
473
- this.log(`Windows\u5E73\u53F0\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u8FDB\u7A0B`, "info");
364
+ this.log(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u50F5\u5C38\u8FDB\u7A0B`, "info");
474
365
  }
475
366
  return cleanedCount;
476
367
  }
@@ -486,52 +377,47 @@ ${commandInfo}`);
486
377
  */
487
378
  async replaceProcess(command, args = [], options = {}) {
488
379
  const processName = options.name || command;
489
- const isWindows = process.platform === "win32";
490
380
  try {
491
381
  this.cleanup();
492
382
  const { spawn: spawn2 } = await import('node:child_process');
493
- const finalCommand = this.getPlatformCommand(command);
494
- const finalArgs = args;
495
- const child = spawn2(finalCommand, finalArgs, {
383
+ const isWindows = process.platform === "win32";
384
+ let finalCommand = this.getPlatformCommand(command);
385
+ let finalArgs = args;
386
+ const spawnOptions = {
496
387
  stdio: "inherit",
497
388
  // 继承所有 stdio
498
389
  cwd: options.cwd || process.cwd(),
499
390
  env: { ...process.env, ...options.env },
500
- shell: false,
501
- // 禁用shell模式,避免进程泄漏
502
- // Windows上使用更严格的进程控制
503
391
  windowsHide: isWindows
504
392
  // Windows上隐藏子进程窗口
505
- });
393
+ };
394
+ if (isWindows) {
395
+ if (finalCommand.endsWith(".cmd")) {
396
+ spawnOptions.shell = true;
397
+ const fullCommand = `${finalCommand} ${finalArgs.join(" ")}`;
398
+ finalCommand = fullCommand;
399
+ finalArgs = [];
400
+ } else {
401
+ spawnOptions.shell = false;
402
+ }
403
+ } else {
404
+ spawnOptions.shell = false;
405
+ }
406
+ const child = spawn2(finalCommand, finalArgs, spawnOptions);
506
407
  child.on("error", (error) => {
507
408
  this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
508
- this.cleanup();
509
409
  process.exit(1);
510
410
  });
511
411
  child.on("exit", (code) => {
512
412
  this.log(`\u66FF\u6362\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`, "info");
513
- this.cleanup();
514
413
  process.exit(code || 0);
515
414
  });
516
415
  this.registerProcess(child, processName);
517
- if (isWindows) {
518
- const checkInterval = setInterval(() => {
519
- if (child.exitCode !== null) {
520
- clearInterval(checkInterval);
521
- this.cleanup();
522
- process.exit(child.exitCode || 0);
523
- }
524
- }, 1e3);
525
- child.on("exit", () => {
526
- clearInterval(checkInterval);
527
- });
528
- }
529
416
  await new Promise(() => {
530
417
  });
531
418
  process.exit(0);
532
419
  } catch (error) {
533
420
  this.log(`\u8FDB\u7A0B\u66FF\u6362\u5931\u8D25: ${error}`, "error");
534
- this.cleanup();
535
421
  process.exit(1);
536
422
  }
537
423
  }
@@ -646,15 +532,15 @@ promisify$1(exec$1);
646
532
  promisify$1(exec$1);
647
533
 
648
534
  async function tryStartClaude(command, args, options) {
649
- const isWindows = process.platform === "win32";
650
- await processManager.replaceProcess(command, args, {
535
+ process.platform === "win32";
536
+ const spawnOptions = {
651
537
  name: "Claude Code",
652
- cwd: options.cwd,
653
- stdio: "inherit",
538
+ cwd: options.cwd || process.cwd(),
539
+ stdio: "inherit"
654
540
  // 必须继承 stdio 才能正确交互
655
- shell: isWindows ? false : options.shell
656
- // Windows上禁用shell避免进程泄漏
657
- });
541
+ // shell选项由processManager内部处理
542
+ };
543
+ await processManager.replaceProcess(command, args, spawnOptions);
658
544
  }
659
545
  async function startClaudeCodeEditor(_lang) {
660
546
  try {
@@ -670,7 +556,7 @@ async function startClaudeCodeEditor(_lang) {
670
556
  };
671
557
  try {
672
558
  const command = isWindows ? "claude.cmd" : "claude";
673
- const args = ["--dangerously-skip-permissions"];
559
+ const args = [];
674
560
  if (isWindows) {
675
561
  await tryStartClaude(command, args, spawnOptions);
676
562
  } else {
@@ -681,7 +567,7 @@ async function startClaudeCodeEditor(_lang) {
681
567
  if (firstError.code === "ENOENT") {
682
568
  console.log(ansis.yellow("\u26A0\uFE0F \u76F4\u63A5\u8C03\u7528 claude \u5931\u8D25\uFF0C\u5C1D\u8BD5\u4F7F\u7528 npx..."));
683
569
  try {
684
- const npxArgs = ["@anthropic-ai/claude-code", "--dangerously-skip-permissions"];
570
+ const npxArgs = ["@anthropic-ai/claude-code"];
685
571
  if (isWindows) {
686
572
  await tryStartClaude("npx.cmd", npxArgs, spawnOptions);
687
573
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aico-cli",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "packageManager": "pnpm@9.15.9",
5
5
  "description": "AI CLI",
6
6
  "repository": {