ai-engineering-init 1.2.1 → 1.2.2
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/README.md +8 -1
- package/bin/index.js +192 -110
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,12 @@ cd ai-engineering-init
|
|
|
89
89
|
|
|
90
90
|
> **MCP Server 支持**:Codex CLI 可通过 `codex mcp-server` 作为 MCP Server 暴露给 Claude Code,
|
|
91
91
|
> 配置写入 `.claude/settings.json` 的 `mcpServers` 后,Claude 可直接调用 `codex` / `codex-reply` 工具进行代码审查。
|
|
92
|
+
>
|
|
93
|
+
> **Windows 用户注意**:初始化后需将 `.claude/settings.json` 中 `mcpServers.codex.command` 的路径改为 Windows 上的实际路径,例如:
|
|
94
|
+
> ```json
|
|
95
|
+
> "command": "C:\\Users\\YourName\\AppData\\Roaming\\npm\\codex.cmd"
|
|
96
|
+
> ```
|
|
97
|
+
> 可通过 `where codex` 命令查询实际路径。
|
|
92
98
|
|
|
93
99
|
## Skills 列表(69个)
|
|
94
100
|
|
|
@@ -192,12 +198,13 @@ cd ai-engineering-init
|
|
|
192
198
|
1. 修改 `AGENTS.md` 中的项目说明
|
|
193
199
|
2. 使用 `.codex/skills/` 下的技能辅助开发
|
|
194
200
|
3. (可选)以 MCP Server 接入 Claude Code:`.claude/settings.json` → `mcpServers.codex`,重启后 Claude 可直接调用 `codex` / `codex-reply` 工具
|
|
201
|
+
- **Windows 用户**:将 `command` 路径改为 `where codex` 查询到的实际路径(如 `C:\Users\YourName\AppData\Roaming\npm\codex.cmd`)
|
|
195
202
|
|
|
196
203
|
## 更新日志
|
|
197
204
|
|
|
198
205
|
查看完整更新历史:[CHANGELOG.md](./CHANGELOG.md)
|
|
199
206
|
|
|
200
|
-
**v1.2.
|
|
207
|
+
**v1.2.2 新增**:README 补充 Windows MCP Server 路径配置说明;Windows 兼容性分析与说明。
|
|
201
208
|
|
|
202
209
|
## License
|
|
203
210
|
|
package/bin/index.js
CHANGED
|
@@ -14,13 +14,22 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const readline = require('readline');
|
|
16
16
|
|
|
17
|
-
// ── ANSI
|
|
18
|
-
|
|
17
|
+
// ── ANSI 颜色(Windows CMD/PowerShell 兼容)────────────────────────────────
|
|
18
|
+
// Windows Terminal 设置 WT_SESSION,ConEmu/Cmder 设置 COLORTERM,VSCode 设置 TERM_PROGRAM
|
|
19
|
+
const supportsColor = !!process.stdout.isTTY && (
|
|
20
|
+
process.platform !== 'win32' ||
|
|
21
|
+
!!process.env.WT_SESSION ||
|
|
22
|
+
!!process.env.COLORTERM ||
|
|
23
|
+
process.env.TERM_PROGRAM === 'vscode'
|
|
24
|
+
);
|
|
25
|
+
const ESC = supportsColor ? {
|
|
19
26
|
reset: '\x1b[0m', bold: '\x1b[1m',
|
|
20
27
|
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
|
|
21
28
|
blue: '\x1b[34m', cyan: '\x1b[36m', magenta: '\x1b[35m',
|
|
22
|
-
}
|
|
23
|
-
|
|
29
|
+
} : Object.fromEntries(
|
|
30
|
+
['reset','bold','red','green','yellow','blue','cyan','magenta'].map(k => [k, ''])
|
|
31
|
+
);
|
|
32
|
+
const fmt = (color, text) => `${ESC[color]}${text}${ESC.reset}`;
|
|
24
33
|
|
|
25
34
|
// ── 版本 ───────────────────────────────────────────────────────────────────
|
|
26
35
|
const PKG_VERSION = require('../package.json').version;
|
|
@@ -34,27 +43,52 @@ console.log('');
|
|
|
34
43
|
|
|
35
44
|
// ── 参数解析 ───────────────────────────────────────────────────────────────
|
|
36
45
|
const args = process.argv.slice(2);
|
|
37
|
-
let command = ''; // '
|
|
46
|
+
let command = ''; // 'update' | ''
|
|
38
47
|
let tool = '';
|
|
39
48
|
let targetDir = process.cwd();
|
|
40
49
|
let force = false;
|
|
41
50
|
|
|
42
51
|
for (let i = 0; i < args.length; i++) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
case '
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
switch (arg) {
|
|
54
|
+
case 'update':
|
|
55
|
+
command = 'update';
|
|
56
|
+
break;
|
|
57
|
+
case '--tool': case '-t':
|
|
58
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
59
|
+
console.error(fmt('red', `错误:${arg} 需要一个值(claude | cursor | codex | all)`));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
tool = args[++i];
|
|
63
|
+
break;
|
|
64
|
+
case '--dir': case '-d':
|
|
65
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
66
|
+
console.error(fmt('red', `错误:${arg} 需要一个目录路径`));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
targetDir = path.resolve(args[++i]);
|
|
70
|
+
break;
|
|
71
|
+
case '--force': case '-f':
|
|
72
|
+
force = true;
|
|
73
|
+
break;
|
|
48
74
|
case '--help': case '-h':
|
|
49
75
|
printHelp();
|
|
50
76
|
process.exit(0);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
// 拒绝未知选项,避免静默忽略导致行为不符预期
|
|
80
|
+
if (arg.startsWith('-')) {
|
|
81
|
+
console.error(fmt('red', `错误:未知选项 "${arg}",运行 --help 查看用法`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
51
85
|
}
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
function printHelp() {
|
|
55
89
|
console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [命令] [选项]\n`);
|
|
56
90
|
console.log('命令:');
|
|
57
|
-
console.log(` ${fmt('bold', '(无)')
|
|
91
|
+
console.log(` ${fmt('bold', '(无)')} 交互式初始化`);
|
|
58
92
|
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)\n`);
|
|
59
93
|
console.log('选项:');
|
|
60
94
|
console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
|
|
@@ -69,7 +103,7 @@ function printHelp() {
|
|
|
69
103
|
console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件\n');
|
|
70
104
|
}
|
|
71
105
|
|
|
72
|
-
// ── 工具定义(init
|
|
106
|
+
// ── 工具定义(init 用)────────────────────────────────────────────────────
|
|
73
107
|
const TOOLS = {
|
|
74
108
|
claude: {
|
|
75
109
|
label: 'Claude Code',
|
|
@@ -93,19 +127,19 @@ const TOOLS = {
|
|
|
93
127
|
},
|
|
94
128
|
};
|
|
95
129
|
|
|
96
|
-
// ── 更新规则(update
|
|
97
|
-
// update:
|
|
98
|
-
// preserve: 用户自定义文件,默认跳过(--force
|
|
130
|
+
// ── 更新规则(update 用)──────────────────────────────────────────────────
|
|
131
|
+
// update: 框架文件,从本机安装版本覆盖
|
|
132
|
+
// preserve: 用户自定义文件,默认跳过(--force 时强制覆盖)
|
|
99
133
|
const UPDATE_RULES = {
|
|
100
134
|
claude: {
|
|
101
135
|
label: 'Claude Code',
|
|
102
|
-
detect: '.claude',
|
|
136
|
+
detect: '.claude',
|
|
103
137
|
update: [
|
|
104
|
-
{ src: '.claude/skills',
|
|
105
|
-
{ src: '.claude/commands',
|
|
106
|
-
{ src: '.claude/agents',
|
|
107
|
-
{ src: '.claude/hooks',
|
|
108
|
-
{ src: '.claude/templates',
|
|
138
|
+
{ src: '.claude/skills', dest: '.claude/skills', label: 'Skills(技能库)', isDir: true },
|
|
139
|
+
{ src: '.claude/commands', dest: '.claude/commands', label: 'Commands(快捷命令)', isDir: true },
|
|
140
|
+
{ src: '.claude/agents', dest: '.claude/agents', label: 'Agents(子代理)', isDir: true },
|
|
141
|
+
{ src: '.claude/hooks', dest: '.claude/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
142
|
+
{ src: '.claude/templates', dest: '.claude/templates', label: 'Templates', isDir: true },
|
|
109
143
|
{ src: '.claude/framework-config.json', dest: '.claude/framework-config.json', label: 'framework-config.json' },
|
|
110
144
|
],
|
|
111
145
|
preserve: [
|
|
@@ -117,9 +151,9 @@ const UPDATE_RULES = {
|
|
|
117
151
|
label: 'Cursor',
|
|
118
152
|
detect: '.cursor',
|
|
119
153
|
update: [
|
|
120
|
-
{ src: '.cursor/skills',
|
|
121
|
-
{ src: '.cursor/agents',
|
|
122
|
-
{ src: '.cursor/hooks',
|
|
154
|
+
{ src: '.cursor/skills', dest: '.cursor/skills', label: 'Skills(技能库)', isDir: true },
|
|
155
|
+
{ src: '.cursor/agents', dest: '.cursor/agents', label: 'Agents(子代理)', isDir: true },
|
|
156
|
+
{ src: '.cursor/hooks', dest: '.cursor/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
123
157
|
{ src: '.cursor/hooks.json', dest: '.cursor/hooks.json', label: 'hooks.json(Hooks 配置)' },
|
|
124
158
|
],
|
|
125
159
|
preserve: [
|
|
@@ -138,32 +172,50 @@ const UPDATE_RULES = {
|
|
|
138
172
|
},
|
|
139
173
|
};
|
|
140
174
|
|
|
141
|
-
// ── 公共工具函数
|
|
175
|
+
// ── 公共工具函数 ──────────────────────────────────────────────────────────
|
|
142
176
|
const SOURCE_DIR = path.join(__dirname, '..');
|
|
143
177
|
|
|
144
|
-
/**
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
let count = 0;
|
|
148
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
149
|
-
const full = path.join(dir, entry);
|
|
150
|
-
count += fs.statSync(full).isDirectory() ? countFiles(full) : 1;
|
|
151
|
-
}
|
|
152
|
-
return count;
|
|
178
|
+
/** 安全判断路径是否为真实目录(避免 existsSync 将文件误判为已安装目录) */
|
|
179
|
+
function isRealDir(p) {
|
|
180
|
+
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
|
153
181
|
}
|
|
154
182
|
|
|
155
|
-
/**
|
|
183
|
+
/** 递归复制目录,返回实际写入的文件数;单文件失败不中断整体 */
|
|
156
184
|
function copyDir(src, dest) {
|
|
157
|
-
|
|
158
|
-
|
|
185
|
+
let written = 0;
|
|
186
|
+
try {
|
|
187
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.log(` ${fmt('red', '✗')} 无法创建目录 ${dest}: ${e.message}`);
|
|
190
|
+
return written;
|
|
191
|
+
}
|
|
192
|
+
let entries;
|
|
193
|
+
try {
|
|
194
|
+
entries = fs.readdirSync(src);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.log(` ${fmt('red', '✗')} 无法读取源目录 ${src}: ${e.message}`);
|
|
197
|
+
return written;
|
|
198
|
+
}
|
|
199
|
+
for (const entry of entries) {
|
|
159
200
|
const s = path.join(src, entry);
|
|
160
201
|
const d = path.join(dest, entry);
|
|
161
|
-
|
|
202
|
+
try {
|
|
203
|
+
fs.statSync(s).isDirectory() ? (written += copyDir(s, d)) : (fs.copyFileSync(s, d), written++);
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.log(` ${fmt('yellow', '⚠')} 跳过文件 ${d}: ${e.message}`);
|
|
206
|
+
}
|
|
162
207
|
}
|
|
208
|
+
return written;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** 构建包含 --dir 上下文的提示命令(方便用户直接复制执行) */
|
|
212
|
+
function hintCmd(subCmd) {
|
|
213
|
+
const dirPart = targetDir !== process.cwd() ? ` --dir "${targetDir}"` : '';
|
|
214
|
+
return `npx ai-engineering-init${dirPart} ${subCmd}`;
|
|
163
215
|
}
|
|
164
216
|
|
|
165
|
-
// ── INIT 逻辑
|
|
166
|
-
function copyItem({ src, dest, label, isDir }) {
|
|
217
|
+
// ── INIT 逻辑 ─────────────────────────────────────────────────────────────
|
|
218
|
+
function copyItem({ src, dest, label, isDir: srcIsDir }) {
|
|
167
219
|
const srcPath = path.join(SOURCE_DIR, src);
|
|
168
220
|
const destPath = path.join(targetDir, dest);
|
|
169
221
|
|
|
@@ -175,13 +227,25 @@ function copyItem({ src, dest, label, isDir }) {
|
|
|
175
227
|
console.log(` ${fmt('yellow', '⚠')} ${label} 已存在,跳过(--force 可强制覆盖)`);
|
|
176
228
|
return;
|
|
177
229
|
}
|
|
178
|
-
|
|
179
|
-
|
|
230
|
+
try {
|
|
231
|
+
if (srcIsDir) {
|
|
232
|
+
const n = copyDir(srcPath, destPath);
|
|
233
|
+
console.log(` ${fmt('green', '✓')} ${label} ${fmt('magenta', `(${n} 个文件)`)}`);
|
|
234
|
+
} else {
|
|
235
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
236
|
+
fs.copyFileSync(srcPath, destPath);
|
|
237
|
+
console.log(` ${fmt('green', '✓')} ${label}`);
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.log(` ${fmt('red', '✗')} ${label} 复制失败: ${e.message}`);
|
|
241
|
+
}
|
|
180
242
|
}
|
|
181
243
|
|
|
182
244
|
function initTool(toolKey) {
|
|
183
245
|
const t = TOOLS[toolKey];
|
|
184
246
|
console.log(fmt('cyan', `[${t.label}]`));
|
|
247
|
+
// 确保目标根目录存在(兼容 --dir 指向尚不存在的路径)
|
|
248
|
+
try { fs.mkdirSync(targetDir, { recursive: true }); } catch { /* 已存在忽略 */ }
|
|
185
249
|
for (const f of t.files) copyItem(f);
|
|
186
250
|
}
|
|
187
251
|
|
|
@@ -212,8 +276,8 @@ function showDoneHint(toolKey) {
|
|
|
212
276
|
}
|
|
213
277
|
|
|
214
278
|
function run(selectedTool) {
|
|
215
|
-
if (!
|
|
216
|
-
console.error(fmt('red', `无效工具: ${selectedTool}。有效选项: claude | cursor | codex | all`));
|
|
279
|
+
if (!Object.keys(TOOLS).concat('all').includes(selectedTool)) {
|
|
280
|
+
console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
|
|
217
281
|
process.exit(1);
|
|
218
282
|
}
|
|
219
283
|
console.log(` 目标目录: ${fmt('bold', targetDir)}`);
|
|
@@ -223,30 +287,28 @@ function run(selectedTool) {
|
|
|
223
287
|
console.log('');
|
|
224
288
|
|
|
225
289
|
if (selectedTool === 'all') {
|
|
226
|
-
|
|
290
|
+
Object.keys(TOOLS).forEach((k, i) => { if (i) console.log(''); initTool(k); });
|
|
227
291
|
} else {
|
|
228
292
|
initTool(selectedTool);
|
|
229
293
|
}
|
|
230
294
|
showDoneHint(selectedTool);
|
|
231
295
|
}
|
|
232
296
|
|
|
233
|
-
// ── UPDATE 逻辑
|
|
297
|
+
// ── UPDATE 逻辑 ───────────────────────────────────────────────────────────
|
|
234
298
|
|
|
235
|
-
/**
|
|
299
|
+
/** 检测当前目录已安装了哪些工具(用 isRealDir 排除误判) */
|
|
236
300
|
function detectInstalledTools() {
|
|
237
|
-
return Object.keys(UPDATE_RULES).filter(key =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
301
|
+
return Object.keys(UPDATE_RULES).filter(key =>
|
|
302
|
+
isRealDir(path.join(targetDir, UPDATE_RULES[key].detect))
|
|
303
|
+
);
|
|
241
304
|
}
|
|
242
305
|
|
|
243
|
-
/**
|
|
306
|
+
/** 更新单个工具,返回 { updated, failed, preserved } 文件数 */
|
|
244
307
|
function updateTool(toolKey) {
|
|
245
308
|
const rule = UPDATE_RULES[toolKey];
|
|
246
309
|
console.log(fmt('cyan', `[${rule.label}]`));
|
|
247
310
|
|
|
248
|
-
let
|
|
249
|
-
let skippedCount = 0;
|
|
311
|
+
let updated = 0, failed = 0, preserved = 0;
|
|
250
312
|
|
|
251
313
|
// 更新框架文件
|
|
252
314
|
for (const item of rule.update) {
|
|
@@ -257,16 +319,21 @@ function updateTool(toolKey) {
|
|
|
257
319
|
console.log(` ${fmt('yellow', '⚠')} ${item.label} 源文件不存在,跳过`);
|
|
258
320
|
continue;
|
|
259
321
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
322
|
+
try {
|
|
323
|
+
if (item.isDir) {
|
|
324
|
+
const n = copyDir(srcPath, destPath);
|
|
325
|
+
console.log(` ${fmt('green', '✓')} ${item.label} ${fmt('magenta', `(${n} 个文件)`)}`);
|
|
326
|
+
updated += n;
|
|
327
|
+
} else {
|
|
328
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
329
|
+
fs.copyFileSync(srcPath, destPath);
|
|
330
|
+
console.log(` ${fmt('green', '✓')} ${item.label}`);
|
|
331
|
+
updated++;
|
|
332
|
+
}
|
|
333
|
+
} catch (e) {
|
|
334
|
+
console.log(` ${fmt('red', '✗')} ${item.label} 失败: ${e.message}`);
|
|
335
|
+
failed++;
|
|
267
336
|
}
|
|
268
|
-
console.log(` ${fmt('green', '✓')} ${item.label} ${fmt('magenta', `(${fileCount} 个文件)`)}`);
|
|
269
|
-
updatedCount += fileCount;
|
|
270
337
|
}
|
|
271
338
|
|
|
272
339
|
// 处理保留文件
|
|
@@ -275,100 +342,115 @@ function updateTool(toolKey) {
|
|
|
275
342
|
const srcPath = path.join(SOURCE_DIR, item.dest);
|
|
276
343
|
|
|
277
344
|
if (force) {
|
|
278
|
-
// --force 时强制更新保留文件
|
|
279
345
|
if (fs.existsSync(srcPath)) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
346
|
+
try {
|
|
347
|
+
if (isRealDir(srcPath)) {
|
|
348
|
+
const n = copyDir(srcPath, destPath);
|
|
349
|
+
updated += n;
|
|
350
|
+
} else {
|
|
351
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
352
|
+
fs.copyFileSync(srcPath, destPath);
|
|
353
|
+
updated++;
|
|
354
|
+
}
|
|
355
|
+
console.log(` ${fmt('green', '✓')} ${item.dest} ${fmt('yellow', '(强制更新)')}`);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.log(` ${fmt('red', '✗')} ${item.dest} 强制更新失败: ${e.message}`);
|
|
358
|
+
failed++;
|
|
286
359
|
}
|
|
287
|
-
console.log(` ${fmt('green', '✓')} ${item.dest} ${fmt('yellow', '(强制更新)')}`);
|
|
288
|
-
updatedCount++;
|
|
289
360
|
}
|
|
290
361
|
} else {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
362
|
+
const exists = fs.existsSync(destPath);
|
|
363
|
+
const mark = exists ? fmt('yellow', '已保留') : fmt('yellow', '不存在,跳过');
|
|
364
|
+
console.log(` ${fmt('yellow', '⊘')} ${item.dest} ${mark} — ${item.reason}`);
|
|
365
|
+
if (exists) preserved++;
|
|
295
366
|
}
|
|
296
367
|
}
|
|
297
368
|
|
|
298
|
-
return {
|
|
369
|
+
return { updated, failed, preserved };
|
|
299
370
|
}
|
|
300
371
|
|
|
301
372
|
/** update 命令主流程 */
|
|
302
373
|
function runUpdate(selectedTool) {
|
|
303
374
|
console.log(` 目标目录: ${fmt('bold', targetDir)}`);
|
|
304
|
-
console.log(`
|
|
305
|
-
if (force) console.log(` ${fmt('yellow', '⚠ --force
|
|
375
|
+
console.log(` 本机版本: ${fmt('bold', `v${PKG_VERSION}`)}`);
|
|
376
|
+
if (force) console.log(` ${fmt('yellow', '⚠ --force 模式:将同时更新保留文件')}`);
|
|
306
377
|
console.log('');
|
|
307
378
|
|
|
308
|
-
// 确定要更新的工具列表
|
|
309
379
|
let toolsToUpdate = [];
|
|
310
|
-
|
|
380
|
+
|
|
381
|
+
if (!selectedTool || selectedTool === 'all') {
|
|
382
|
+
// 无参数 或 all:只更新已检测到的工具(不主动创建新目录)
|
|
383
|
+
toolsToUpdate = detectInstalledTools();
|
|
384
|
+
if (toolsToUpdate.length === 0) {
|
|
385
|
+
console.log(fmt('yellow', '⚠ 当前目录未检测到已安装的 AI 工具配置。'));
|
|
386
|
+
console.log(` 请先运行: ${fmt('bold', hintCmd('--tool claude'))}\n`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
console.log(` 检测到已安装: ${fmt('bold', toolsToUpdate.join(', '))}`);
|
|
390
|
+
if (selectedTool === 'all') {
|
|
391
|
+
console.log(` ${fmt('yellow', '提示')}:--tool all 只更新已安装工具,如需初始化新工具请用 --tool <name>`);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// 指定单个工具
|
|
311
395
|
if (!UPDATE_RULES[selectedTool]) {
|
|
312
|
-
console.error(fmt('red', `无效工具: ${selectedTool}。有效选项: claude | cursor | codex | all`));
|
|
396
|
+
console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
|
|
313
397
|
process.exit(1);
|
|
314
398
|
}
|
|
315
|
-
|
|
316
|
-
if (!fs.existsSync(detectPath)) {
|
|
399
|
+
if (!isRealDir(path.join(targetDir, UPDATE_RULES[selectedTool].detect))) {
|
|
317
400
|
console.log(fmt('yellow', `⚠ ${selectedTool} 未在当前目录初始化,请先运行:`));
|
|
318
|
-
console.log(` ${fmt('bold',
|
|
401
|
+
console.log(` ${fmt('bold', hintCmd(`--tool ${selectedTool}`))}\n`);
|
|
319
402
|
process.exit(1);
|
|
320
403
|
}
|
|
321
404
|
toolsToUpdate = [selectedTool];
|
|
322
|
-
} else if (selectedTool === 'all') {
|
|
323
|
-
toolsToUpdate = Object.keys(UPDATE_RULES);
|
|
324
|
-
} else {
|
|
325
|
-
// 自动检测
|
|
326
|
-
toolsToUpdate = detectInstalledTools();
|
|
327
|
-
if (toolsToUpdate.length === 0) {
|
|
328
|
-
console.log(fmt('yellow', '⚠ 当前目录未检测到已安装的 AI 工具配置。'));
|
|
329
|
-
console.log(` 请先运行 ${fmt('bold', 'npx ai-engineering-init')} 进行初始化。\n`);
|
|
330
|
-
process.exit(1);
|
|
331
|
-
}
|
|
332
|
-
console.log(` 检测到已安装: ${fmt('bold', toolsToUpdate.join(', '))}`);
|
|
333
|
-
console.log('');
|
|
334
405
|
}
|
|
335
406
|
|
|
407
|
+
console.log('');
|
|
336
408
|
console.log(fmt('bold', '正在更新框架文件...'));
|
|
337
409
|
console.log('');
|
|
338
410
|
|
|
339
|
-
let totalUpdated = 0;
|
|
340
|
-
let totalSkipped = 0;
|
|
341
|
-
|
|
411
|
+
let totalUpdated = 0, totalFailed = 0, totalPreserved = 0;
|
|
342
412
|
for (let i = 0; i < toolsToUpdate.length; i++) {
|
|
343
|
-
const {
|
|
344
|
-
totalUpdated
|
|
345
|
-
|
|
413
|
+
const { updated, failed, preserved } = updateTool(toolsToUpdate[i]);
|
|
414
|
+
totalUpdated += updated;
|
|
415
|
+
totalFailed += failed;
|
|
416
|
+
totalPreserved += preserved;
|
|
346
417
|
if (i < toolsToUpdate.length - 1) console.log('');
|
|
347
418
|
}
|
|
348
419
|
|
|
349
|
-
// 汇总
|
|
350
420
|
console.log('');
|
|
351
421
|
console.log(fmt('green', fmt('bold', '✅ 更新完成!')));
|
|
352
422
|
console.log('');
|
|
353
|
-
console.log(` ${fmt('green',
|
|
354
|
-
if (
|
|
355
|
-
console.log(` ${fmt('
|
|
423
|
+
console.log(` ${fmt('green', `✓ 更新文件: ${totalUpdated} 个`)}`);
|
|
424
|
+
if (totalFailed > 0) {
|
|
425
|
+
console.log(` ${fmt('red', `✗ 失败文件: ${totalFailed} 个`)}(请检查目录权限)`);
|
|
426
|
+
}
|
|
427
|
+
if (totalPreserved > 0) {
|
|
428
|
+
console.log(` ${fmt('yellow', `⊘ 已保留文件: ${totalPreserved} 个`)}(--force 可强制更新)`);
|
|
356
429
|
}
|
|
357
430
|
console.log('');
|
|
358
431
|
console.log(fmt('cyan', '提示:'));
|
|
359
432
|
console.log(' 重启 Claude Code / Cursor 使新技能生效');
|
|
360
|
-
|
|
361
|
-
|
|
433
|
+
console.log(` ${fmt('yellow', '注意')}:update 只新增/覆盖文件,不删除旧版本已移除的文件`);
|
|
434
|
+
if (!force && totalPreserved > 0) {
|
|
435
|
+
console.log(` 强制更新保留文件: ${fmt('bold', hintCmd('update --force'))}`);
|
|
362
436
|
}
|
|
363
437
|
console.log('');
|
|
438
|
+
|
|
439
|
+
if (totalFailed > 0) process.exitCode = 1;
|
|
364
440
|
}
|
|
365
441
|
|
|
366
|
-
// ── 主入口
|
|
442
|
+
// ── 主入口 ────────────────────────────────────────────────────────────────
|
|
367
443
|
if (command === 'update') {
|
|
368
444
|
runUpdate(tool);
|
|
369
445
|
} else if (tool) {
|
|
370
446
|
run(tool);
|
|
371
447
|
} else {
|
|
448
|
+
// 非 TTY 环境(CI/管道)无法交互,强制要求显式指定 --tool
|
|
449
|
+
if (!process.stdin.isTTY) {
|
|
450
|
+
console.error(fmt('red', '错误:非交互环境下必须指定 --tool 参数'));
|
|
451
|
+
console.error(` 示例: ${fmt('bold', hintCmd('--tool claude'))}`);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
372
454
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
373
455
|
console.log(fmt('cyan', '请选择要初始化的 AI 工具:'));
|
|
374
456
|
console.log('');
|