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.
- package/dist/chunks/run-command.mjs +47 -0
- package/dist/chunks/simple-config.mjs +1 -1
- package/dist/cli.mjs +626 -95
- package/package.json +1 -1
- package/templates/commands/git/worktree.md +11 -11
|
@@ -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.
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
59
|
-
|
|
572
|
+
const result = await processManager.spawn("npm", ["install", "-g", "aico-cli"], {
|
|
573
|
+
name: "npm install",
|
|
60
574
|
stdio: "inherit",
|
|
61
575
|
// 继承父进程的 stdio,显示安装进度
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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:
|
|
164
|
-
// Windows
|
|
628
|
+
shell: false
|
|
629
|
+
// 禁用shell模式,避免进程泄漏(Windows和Unix都适用)
|
|
165
630
|
};
|
|
166
631
|
try {
|
|
167
632
|
const command = isWindows ? "claude.cmd" : "claude";
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: 管理 Git worktree,在项目平级的 ../.
|
|
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 集成和内容迁移,使用结构化的 `../.
|
|
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>]` | 在 `../.
|
|
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
|
-
- 在结构化的 `../.
|
|
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/../.
|
|
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 时,始终使用绝对路径以防止出现类似 `../.
|
|
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 内创建嵌套的 `.
|
|
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 ../.
|
|
219
|
+
✅ Worktree created at ../.aico/项目名/feature-ui
|
|
220
220
|
✅ 已复制 .env
|
|
221
221
|
✅ 已复制 .env.local
|
|
222
222
|
📋 已从 .gitignore 复制 2 个环境文件
|
|
223
|
-
🖥️ 是否在 IDE 中打开 ../.
|
|
224
|
-
🚀 正在用 VS Code 打开 ../.
|
|
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
|
-
└── .
|
|
236
|
+
└── .aico/ # worktree 管理
|
|
237
237
|
└── your-project/ # 项目 worktree
|
|
238
238
|
├── feature-ui/ # 功能分支
|
|
239
239
|
├── hotfix/ # 修复分支
|