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.
Files changed (2) hide show
  1. package/bin/index.js +213 -14
  2. 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')} 更新已安装的框架文件(跳过用户自定义文件)\n`);
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 # 强制更新,包括保留文件\n');
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', '请选择要初始化的 AI 工具:'));
647
+ console.log(fmt('cyan', '请选择操作:'));
456
648
  console.log('');
457
- console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} — 初始化 .claude/ + CLAUDE.md`);
458
- console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} — 初始化 .cursor/(Skills + Agents + MCP)`);
459
- console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} — 初始化 .codex/ + AGENTS.md`);
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-4]: '), (answer) => {
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
- run(selected);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-engineering-init",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "AI 工程化配置初始化工具 — 一键为 Claude Code、OpenAI Codex 等 AI 工具初始化 Skills 和项目规范",
5
5
  "keywords": [
6
6
  "claude-code",