ai-engineering-init 1.2.7 → 1.2.9
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/bin/index.js +213 -14
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
* npx ai-engineering-init --tool all
|
|
8
8
|
* npx ai-engineering-init update # 自动检测已安装工具并更新
|
|
9
9
|
* npx ai-engineering-init update --tool claude
|
|
10
|
+
* npx ai-engineering-init global # 全局安装(对所有项目生效)
|
|
11
|
+
* npx ai-engineering-init global --tool claude
|
|
10
12
|
*/
|
|
11
13
|
'use strict';
|
|
12
14
|
|
|
13
15
|
const fs = require('fs');
|
|
14
16
|
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
15
18
|
const readline = require('readline');
|
|
16
19
|
|
|
17
20
|
// ── ANSI 颜色(Windows CMD/PowerShell 兼容)────────────────────────────────
|
|
@@ -43,7 +46,7 @@ console.log('');
|
|
|
43
46
|
|
|
44
47
|
// ── 参数解析 ───────────────────────────────────────────────────────────────
|
|
45
48
|
const args = process.argv.slice(2);
|
|
46
|
-
let command = ''; // 'update' | ''
|
|
49
|
+
let command = ''; // 'update' | 'global' | ''
|
|
47
50
|
let tool = '';
|
|
48
51
|
let targetDir = process.cwd();
|
|
49
52
|
let force = false;
|
|
@@ -54,6 +57,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
54
57
|
case 'update':
|
|
55
58
|
command = 'update';
|
|
56
59
|
break;
|
|
60
|
+
case 'global':
|
|
61
|
+
command = 'global';
|
|
62
|
+
break;
|
|
57
63
|
case '--tool': case '-t':
|
|
58
64
|
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
59
65
|
console.error(fmt('red', `错误:${arg} 需要一个值(claude | cursor | codex | all)`));
|
|
@@ -88,19 +94,22 @@ for (let i = 0; i < args.length; i++) {
|
|
|
88
94
|
function printHelp() {
|
|
89
95
|
console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [命令] [选项]\n`);
|
|
90
96
|
console.log('命令:');
|
|
91
|
-
console.log(` ${fmt('bold', '(无)')}
|
|
92
|
-
console.log(` ${fmt('bold', 'update')}
|
|
97
|
+
console.log(` ${fmt('bold', '(无)')} 交互式初始化(安装到当前项目目录)`);
|
|
98
|
+
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)`);
|
|
99
|
+
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor 等,对所有项目生效\n`);
|
|
93
100
|
console.log('选项:');
|
|
94
101
|
console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
|
|
95
|
-
console.log(' --dir, -d <目录>
|
|
96
|
-
console.log(' --force,-f 强制覆盖(init 时覆盖已有文件;update 时同时更新保留文件)');
|
|
102
|
+
console.log(' --dir, -d <目录> 目标目录(默认:当前目录,仅 init/update 有效)');
|
|
103
|
+
console.log(' --force,-f 强制覆盖(init 时覆盖已有文件;update/global 时同时更新保留文件)');
|
|
97
104
|
console.log(' --help, -h 显示此帮助\n');
|
|
98
105
|
console.log('示例:');
|
|
99
106
|
console.log(' npx ai-engineering-init --tool claude');
|
|
100
107
|
console.log(' npx ai-engineering-init --tool all --dir /path/to/project');
|
|
101
108
|
console.log(' npx ai-engineering-init update # 自动检测已安装工具');
|
|
102
109
|
console.log(' npx ai-engineering-init update --tool claude # 只更新 Claude');
|
|
103
|
-
console.log(' npx ai-engineering-init update --force #
|
|
110
|
+
console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件');
|
|
111
|
+
console.log(' npx ai-engineering-init global # 全局安装所有工具');
|
|
112
|
+
console.log(' npx ai-engineering-init global --tool claude # 只全局安装 Claude\n');
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
// ── 工具定义(init 用)────────────────────────────────────────────────────
|
|
@@ -172,6 +181,187 @@ const UPDATE_RULES = {
|
|
|
172
181
|
},
|
|
173
182
|
};
|
|
174
183
|
|
|
184
|
+
// ── 全局安装规则(global 用)─────────────────────────────────────────────
|
|
185
|
+
// 安装到 ~/.claude / ~/.cursor / ~/.codex,对当前用户所有项目生效
|
|
186
|
+
const HOME_DIR = os.homedir();
|
|
187
|
+
|
|
188
|
+
const GLOBAL_RULES = {
|
|
189
|
+
claude: {
|
|
190
|
+
label: 'Claude Code',
|
|
191
|
+
targetDir: path.join(HOME_DIR, '.claude'),
|
|
192
|
+
files: [
|
|
193
|
+
{ src: '.claude/skills', dest: 'skills', label: 'Skills(全局技能库)', isDir: true },
|
|
194
|
+
{ src: '.claude/commands', dest: 'commands', label: 'Commands(全局命令)', isDir: true },
|
|
195
|
+
{ src: '.claude/agents', dest: 'agents', label: 'Agents(全局子代理)', isDir: true },
|
|
196
|
+
{ src: '.claude/hooks', dest: 'hooks', label: 'Hooks(全局钩子)', isDir: true },
|
|
197
|
+
{ src: '.claude/framework-config.json', dest: 'framework-config.json', label: 'framework-config.json' },
|
|
198
|
+
],
|
|
199
|
+
preserve: [
|
|
200
|
+
{ dest: 'settings.json', reason: '包含用户 MCP 配置和权限设置' },
|
|
201
|
+
],
|
|
202
|
+
note: `Skills/Commands/Hooks 已安装到 ~/.claude,对所有项目自动生效`,
|
|
203
|
+
},
|
|
204
|
+
cursor: {
|
|
205
|
+
label: 'Cursor',
|
|
206
|
+
targetDir: path.join(HOME_DIR, '.cursor'),
|
|
207
|
+
files: [
|
|
208
|
+
{ src: '.cursor/skills', dest: 'skills', label: 'Skills(全局技能库)', isDir: true },
|
|
209
|
+
{ src: '.cursor/agents', dest: 'agents', label: 'Agents(全局子代理)', isDir: true },
|
|
210
|
+
{ src: '.cursor/hooks', dest: 'hooks', label: 'Hooks(全局钩子脚本)', isDir: true },
|
|
211
|
+
{ src: '.cursor/hooks.json', dest: 'hooks.json', label: 'hooks.json(Hooks 触发配置)' },
|
|
212
|
+
{ src: '.cursor/mcp.json', dest: 'mcp.json', label: 'mcp.json(MCP 服务器配置)', merge: true },
|
|
213
|
+
],
|
|
214
|
+
preserve: [],
|
|
215
|
+
note: `Skills/Hooks/MCP 已安装到 ~/.cursor,重启 Cursor 后生效`,
|
|
216
|
+
},
|
|
217
|
+
codex: {
|
|
218
|
+
label: 'OpenAI Codex',
|
|
219
|
+
targetDir: path.join(HOME_DIR, '.codex'),
|
|
220
|
+
files: [
|
|
221
|
+
{ src: '.codex/skills', dest: 'skills', label: 'Skills(全局技能库)', isDir: true },
|
|
222
|
+
],
|
|
223
|
+
preserve: [],
|
|
224
|
+
note: `Skills 已安装到 ~/.codex`,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** 合并 JSON 文件:将 src 的键补充到 dest,已有的键保留不覆盖 */
|
|
229
|
+
function mergeJsonFile(srcPath, destPath, label) {
|
|
230
|
+
try {
|
|
231
|
+
const srcData = JSON.parse(fs.readFileSync(srcPath, 'utf8'));
|
|
232
|
+
let destData = {};
|
|
233
|
+
if (fs.existsSync(destPath)) {
|
|
234
|
+
try { destData = JSON.parse(fs.readFileSync(destPath, 'utf8')); } catch { destData = {}; }
|
|
235
|
+
}
|
|
236
|
+
// 深度合并第一层对象键(如 mcpServers),已有的不覆盖
|
|
237
|
+
let added = 0;
|
|
238
|
+
for (const [key, value] of Object.entries(srcData)) {
|
|
239
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
240
|
+
if (!destData[key]) destData[key] = {};
|
|
241
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
|
242
|
+
if (!(subKey in destData[key])) {
|
|
243
|
+
destData[key][subKey] = subValue;
|
|
244
|
+
added++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else if (!(key in destData)) {
|
|
248
|
+
destData[key] = value;
|
|
249
|
+
added++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
fs.writeFileSync(destPath, JSON.stringify(destData, null, 2) + '\n');
|
|
253
|
+
console.log(` ${fmt('green', '✓')} ${label} ${fmt('magenta', `(合并 +${added} 项,已有配置保留)`)}`);
|
|
254
|
+
return added > 0 ? 1 : 0;
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.log(` ${fmt('red', '✗')} ${label} 合并失败: ${e.message}`);
|
|
257
|
+
return -1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 全局安装单个工具 */
|
|
262
|
+
function globalInstallTool(toolKey) {
|
|
263
|
+
const rule = GLOBAL_RULES[toolKey];
|
|
264
|
+
const globalDest = rule.targetDir;
|
|
265
|
+
console.log(fmt('cyan', `[${rule.label}]`) + fmt('blue', ` → ${globalDest}`));
|
|
266
|
+
|
|
267
|
+
let installed = 0, failed = 0;
|
|
268
|
+
|
|
269
|
+
try { fs.mkdirSync(globalDest, { recursive: true }); } catch { /* 已存在忽略 */ }
|
|
270
|
+
|
|
271
|
+
for (const item of rule.files) {
|
|
272
|
+
const srcPath = path.join(SOURCE_DIR, item.src);
|
|
273
|
+
const destPath = path.join(globalDest, item.dest);
|
|
274
|
+
|
|
275
|
+
if (!fs.existsSync(srcPath)) {
|
|
276
|
+
console.log(` ${fmt('yellow', '⚠')} ${item.label} 源文件不存在,跳过`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// merge 模式:合并 JSON 而非覆盖(保留用户已有配置)
|
|
281
|
+
if (item.merge) {
|
|
282
|
+
const result = mergeJsonFile(srcPath, destPath, item.label);
|
|
283
|
+
if (result >= 0) installed++; else failed++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (fs.existsSync(destPath) && !force) {
|
|
288
|
+
console.log(` ${fmt('yellow', '⚠')} ${item.label} 已存在,跳过(--force 可强制覆盖)`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
if (item.isDir) {
|
|
293
|
+
const n = copyDir(srcPath, destPath);
|
|
294
|
+
console.log(` ${fmt('green', '✓')} ${item.label} ${fmt('magenta', `(${n} 个文件)`)}`);
|
|
295
|
+
installed += n;
|
|
296
|
+
} else {
|
|
297
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
298
|
+
fs.copyFileSync(srcPath, destPath);
|
|
299
|
+
console.log(` ${fmt('green', '✓')} ${item.label}`);
|
|
300
|
+
installed++;
|
|
301
|
+
}
|
|
302
|
+
} catch (e) {
|
|
303
|
+
console.log(` ${fmt('red', '✗')} ${item.label} 安装失败: ${e.message}`);
|
|
304
|
+
failed++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (const item of rule.preserve) {
|
|
309
|
+
const destPath = path.join(globalDest, item.dest);
|
|
310
|
+
if (fs.existsSync(destPath)) {
|
|
311
|
+
console.log(` ${fmt('yellow', '⊘')} ${item.dest} 已保留 — ${item.reason}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { installed, failed };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** global 命令主流程 */
|
|
319
|
+
function runGlobal(selectedTool) {
|
|
320
|
+
const validKeys = Object.keys(GLOBAL_RULES);
|
|
321
|
+
const toolsToInstall = (!selectedTool || selectedTool === 'all')
|
|
322
|
+
? validKeys
|
|
323
|
+
: [selectedTool];
|
|
324
|
+
|
|
325
|
+
if (selectedTool && selectedTool !== 'all' && !GLOBAL_RULES[selectedTool]) {
|
|
326
|
+
console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(` 安装模式: ${fmt('green', fmt('bold', '全局安装(当前用户所有项目生效)'))}`);
|
|
331
|
+
console.log(` 安装工具: ${fmt('bold', toolsToInstall.join(', '))}`);
|
|
332
|
+
if (force) console.log(` ${fmt('yellow', '⚠ --force 模式:强制覆盖已有文件')}`);
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(fmt('bold', '正在安装到系统目录...'));
|
|
335
|
+
console.log('');
|
|
336
|
+
|
|
337
|
+
let totalInstalled = 0, totalFailed = 0;
|
|
338
|
+
for (let i = 0; i < toolsToInstall.length; i++) {
|
|
339
|
+
const { installed, failed } = globalInstallTool(toolsToInstall[i]);
|
|
340
|
+
totalInstalled += installed;
|
|
341
|
+
totalFailed += failed;
|
|
342
|
+
if (i < toolsToInstall.length - 1) console.log('');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log('');
|
|
346
|
+
console.log(fmt('green', fmt('bold', '✅ 全局安装完成!')));
|
|
347
|
+
console.log('');
|
|
348
|
+
console.log(` ${fmt('green', `✓ 安装文件: ${totalInstalled} 个`)}`);
|
|
349
|
+
if (totalFailed > 0) {
|
|
350
|
+
console.log(` ${fmt('red', `✗ 失败文件: ${totalFailed} 个`)}(请检查目录权限)`);
|
|
351
|
+
}
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(fmt('cyan', '安装位置说明:'));
|
|
354
|
+
for (const key of toolsToInstall) {
|
|
355
|
+
const rule = GLOBAL_RULES[key];
|
|
356
|
+
console.log(` ${fmt('bold', rule.label + ':')} ${rule.note}`);
|
|
357
|
+
}
|
|
358
|
+
console.log('');
|
|
359
|
+
console.log(fmt('yellow', '提示:项目级配置(.claude/ 等)优先级高于全局配置,两者可同时使用。'));
|
|
360
|
+
console.log('');
|
|
361
|
+
|
|
362
|
+
if (totalFailed > 0) process.exitCode = 1;
|
|
363
|
+
}
|
|
364
|
+
|
|
175
365
|
// ── 公共工具函数 ──────────────────────────────────────────────────────────
|
|
176
366
|
const SOURCE_DIR = path.join(__dirname, '..');
|
|
177
367
|
|
|
@@ -442,6 +632,8 @@ function runUpdate(selectedTool) {
|
|
|
442
632
|
// ── 主入口 ────────────────────────────────────────────────────────────────
|
|
443
633
|
if (command === 'update') {
|
|
444
634
|
runUpdate(tool);
|
|
635
|
+
} else if (command === 'global') {
|
|
636
|
+
runGlobal(tool);
|
|
445
637
|
} else if (tool) {
|
|
446
638
|
run(tool);
|
|
447
639
|
} else {
|
|
@@ -452,19 +644,26 @@ if (command === 'update') {
|
|
|
452
644
|
process.exit(1);
|
|
453
645
|
}
|
|
454
646
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
455
|
-
console.log(fmt('cyan', '
|
|
647
|
+
console.log(fmt('cyan', '请选择操作:'));
|
|
456
648
|
console.log('');
|
|
457
|
-
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} —
|
|
458
|
-
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} —
|
|
459
|
-
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} —
|
|
460
|
-
console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex
|
|
649
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} — 初始化到当前项目 .claude/ + CLAUDE.md`);
|
|
650
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} — 初始化到当前项目 .cursor/(Skills + Agents)`);
|
|
651
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} — 初始化到当前项目 .codex/ + AGENTS.md`);
|
|
652
|
+
console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex 到当前项目`);
|
|
653
|
+
console.log(` ${fmt('bold', '5')}) ${fmt('magenta', '全局安装')} — 安装到 ~/.claude / ~/.cursor,对所有项目生效`);
|
|
461
654
|
console.log('');
|
|
462
|
-
rl.question(fmt('bold', '请输入选项 [1-
|
|
655
|
+
rl.question(fmt('bold', '请输入选项 [1-5]: '), (answer) => {
|
|
463
656
|
rl.close();
|
|
464
657
|
const map = { '1': 'claude', '2': 'cursor', '3': 'codex', '4': 'all' };
|
|
465
658
|
const selected = map[answer.trim()];
|
|
466
|
-
if (!selected) { console.error(fmt('red', '无效选项,退出。')); process.exit(1); }
|
|
467
659
|
console.log('');
|
|
468
|
-
|
|
660
|
+
if (answer.trim() === '5') {
|
|
661
|
+
runGlobal('all');
|
|
662
|
+
} else if (selected) {
|
|
663
|
+
run(selected);
|
|
664
|
+
} else {
|
|
665
|
+
console.error(fmt('red', '无效选项,退出。'));
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
469
668
|
});
|
|
470
669
|
}
|