ai-engineering-init 1.2.0 → 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.
@@ -37,5 +37,11 @@
37
37
  }
38
38
  ]
39
39
  },
40
- "mcpServers": {}
40
+ "mcpServers": {
41
+ "codex": {
42
+ "command": "/Users/xujiajun/.nvm/versions/node/v20.19.6/bin/codex",
43
+ "args": ["mcp-server"],
44
+ "description": "OpenAI Codex CLI - 代码分析/算法实现/代码审查"
45
+ }
46
+ }
41
47
  }
package/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  > 一键初始化 AI 工程化配置,支持 Claude Code、Cursor、OpenAI Codex 等主流 AI 开发工具。
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/ai-engineering-init)](https://www.npmjs.com/package/ai-engineering-init)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+
5
8
  ## 快速开始
6
9
 
7
10
  ### 方式一:npx(推荐,无需克隆)
@@ -84,12 +87,21 @@ cd ai-engineering-init
84
87
  | `skills/` | Codex 技能配置 |
85
88
  | `AGENTS.md` | AI Agent 项目规范说明 |
86
89
 
87
- ## Skills 列表(68个)
90
+ > **MCP Server 支持**:Codex CLI 可通过 `codex mcp-server` 作为 MCP Server 暴露给 Claude Code,
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` 命令查询实际路径。
98
+
99
+ ## Skills 列表(69个)
88
100
 
89
101
  <details>
90
102
  <summary>展开查看完整列表</summary>
91
103
 
92
- **通用后端技能(33个)**
104
+ **通用后端技能(34个)**
93
105
 
94
106
  | 技能 | 触发场景 |
95
107
  |------|---------|
@@ -120,7 +132,7 @@ cd ai-engineering-init
120
132
  | `tech-decision` | 技术选型、方案对比 |
121
133
  | `task-tracker` | 任务进度跟踪 |
122
134
  | `project-navigator` | 项目结构导航、文件定位 |
123
- | `collaborating-with-codex` | 与 Codex 协同开发 |
135
+ | `collaborating-with-codex` | 与 Codex 协同开发(支持 MCP Server 直调) |
124
136
  | `collaborating-with-gemini` | 与 Gemini 协同开发 |
125
137
  | `banana-image` | AI 图片生成、海报、缩略图 |
126
138
  | `add-skill` | 创建新技能 |
@@ -139,6 +151,7 @@ cd ai-engineering-init
139
151
  | `leniu-backend-annotations` | `@RequiresAuthentication`、分组校验 |
140
152
  | `leniu-utils-toolkit` | BeanUtil、CollUtil、StrUtil、RedisUtil |
141
153
  | `leniu-code-patterns` | net.xnzn 包名规范、禁止事项 |
154
+ | `leniu-brainstorm` | 云食堂方案头脑风暴 |
142
155
  | `leniu-data-permission` | 云食堂数据权限控制 |
143
156
  | `leniu-java-entity` | Entity/VO/DTO/Param 数据类规范 |
144
157
  | `leniu-java-mybatis` | MyBatis Plus、LambdaQueryWrapper、XML 映射 |
@@ -184,39 +197,14 @@ cd ai-engineering-init
184
197
 
185
198
  1. 修改 `AGENTS.md` 中的项目说明
186
199
  2. 使用 `.codex/skills/` 下的技能辅助开发
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`)
187
202
 
188
203
  ## 更新日志
189
204
 
190
- ### v1.2.0(2026-03-01)
191
-
192
- - 新增 **Cursor Hooks 支持**(`.cursor/hooks.json` + `.cursor/hooks/`)
193
- - `beforeSubmitPrompt`:检测用户意图,自动注入相关技能文档路径引导 Agent 阅读(对应 Claude 的 `UserPromptSubmit`)
194
- - `preToolUse`:拦截危险 Shell 命令(`rm -rf /`、`drop database` 等),兼容 Cursor `Shell` 工具名
195
- - `stop`:复用 Claude 的 `stop.js`,支持 nul 文件清理和完成音效
196
- - 修复 **leniu-java-export** 技能示例代码:移除错误的 `Executors.readInSystem()` 包装(业务查询默认在商户库执行)
197
- - 修复 **leniu-java-total-line** 技能:同步修正双库架构说明,`Executors.doInSystem()` 仅用于访问系统库
198
-
199
- ### v1.1.1(2026-02-24)
200
-
201
- - 优化 **npm 发布流程**
202
- - 预发布版本(含 `-`)自动发布到 `test` 标签
203
- - 正式版本自动发布到 `latest` 标签
204
- - GitHub Release 自动标记预发布版本(`prerelease: true`)
205
-
206
- ### v1.1.0(2026-02-24)
207
-
208
- - 新增 **Cursor** 工具支持(`.cursor/` 目录)
209
- - 同步 68 个 Skills 到 `.cursor/skills/`
210
- - 新增 Subagents 配置(`code-reviewer`、`project-manager`)
211
- - 新增 MCP 服务器配置(`sequential-thinking`、`context7`、`github`)
212
- - 新增 **25 个 leniu 云食堂专项技能**
213
- - 覆盖金额处理、并发、MQ、定时任务、报表、餐次、营销规则等场景
214
-
215
- ### v1.0.0
205
+ 查看完整更新历史:[CHANGELOG.md](./CHANGELOG.md)
216
206
 
217
- - 初始版本,支持 Claude Code OpenAI Codex
218
- - 内置通用后端技能、OpenSpec 工作流技能
219
- - 自动化 Hooks(技能强制评估、代码规范检查)
207
+ **v1.2.2 新增**:README 补充 Windows MCP Server 路径配置说明;Windows 兼容性分析与说明。
220
208
 
221
209
  ## License
222
210
 
package/bin/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * AI Engineering 初始化 CLI
3
+ * AI Engineering 初始化 / 更新 CLI
4
4
  * 用法:
5
- * npx ai-engineering-init # 交互式
5
+ * npx ai-engineering-init # 交互式初始化
6
6
  * npx ai-engineering-init --tool claude
7
- * npx ai-engineering-init --tool codex
8
7
  * npx ai-engineering-init --tool all
8
+ * npx ai-engineering-init update # 自动检测已安装工具并更新
9
+ * npx ai-engineering-init update --tool claude
9
10
  */
10
11
  'use strict';
11
12
 
@@ -13,47 +14,96 @@ const fs = require('fs');
13
14
  const path = require('path');
14
15
  const readline = require('readline');
15
16
 
16
- // ── ANSI 颜色 ──────────────────────────────────────────────────────────────
17
- const c = {
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 ? {
18
26
  reset: '\x1b[0m', bold: '\x1b[1m',
19
27
  red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
20
- blue: '\x1b[34m', cyan: '\x1b[36m',
21
- };
22
- const fmt = (color, text) => `${c[color]}${text}${c.reset}`;
28
+ blue: '\x1b[34m', cyan: '\x1b[36m', magenta: '\x1b[35m',
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}`;
33
+
34
+ // ── 版本 ───────────────────────────────────────────────────────────────────
35
+ const PKG_VERSION = require('../package.json').version;
23
36
 
24
37
  // ── Banner ─────────────────────────────────────────────────────────────────
25
38
  console.log('');
26
39
  console.log(fmt('blue', fmt('bold', '┌─────────────────────────────────────────┐')));
27
- console.log(fmt('blue', fmt('bold', '│ AI Engineering 初始化工具 v1.1 │')));
40
+ console.log(fmt('blue', fmt('bold', `│ AI Engineering 工具 v${PKG_VERSION} │`)));
28
41
  console.log(fmt('blue', fmt('bold', '└─────────────────────────────────────────┘')));
29
42
  console.log('');
30
43
 
31
44
  // ── 参数解析 ───────────────────────────────────────────────────────────────
32
45
  const args = process.argv.slice(2);
46
+ let command = ''; // 'update' | ''
33
47
  let tool = '';
34
48
  let targetDir = process.cwd();
35
49
  let force = false;
36
50
 
37
51
  for (let i = 0; i < args.length; i++) {
38
- switch (args[i]) {
39
- case '--tool': case '-t': tool = args[++i]; break;
40
- case '--dir': case '-d': targetDir = path.resolve(args[++i]); break;
41
- case '--force':case '-f': force = true; break;
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;
42
74
  case '--help': case '-h':
43
- console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [选项]\n`);
44
- console.log('选项:');
45
- console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
46
- console.log(' --dir, -d <目录> 目标目录(默认:当前目录)');
47
- console.log(' --force,-f 强制覆盖已有文件');
48
- console.log(' --help, -h 显示此帮助\n');
49
- console.log('示例:');
50
- console.log(' npx ai-engineering-init --tool claude');
51
- console.log(' npx ai-engineering-init --tool all --dir /path/to/project\n');
75
+ printHelp();
52
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;
53
85
  }
54
86
  }
55
87
 
56
- // ── 工具定义 ───────────────────────────────────────────────────────────────
88
+ function printHelp() {
89
+ console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [命令] [选项]\n`);
90
+ console.log('命令:');
91
+ console.log(` ${fmt('bold', '(无)')} 交互式初始化`);
92
+ console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)\n`);
93
+ console.log('选项:');
94
+ console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
95
+ console.log(' --dir, -d <目录> 目标目录(默认:当前目录)');
96
+ console.log(' --force,-f 强制覆盖(init 时覆盖已有文件;update 时同时更新保留文件)');
97
+ console.log(' --help, -h 显示此帮助\n');
98
+ console.log('示例:');
99
+ console.log(' npx ai-engineering-init --tool claude');
100
+ console.log(' npx ai-engineering-init --tool all --dir /path/to/project');
101
+ console.log(' npx ai-engineering-init update # 自动检测已安装工具');
102
+ console.log(' npx ai-engineering-init update --tool claude # 只更新 Claude');
103
+ console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件\n');
104
+ }
105
+
106
+ // ── 工具定义(init 用)────────────────────────────────────────────────────
57
107
  const TOOLS = {
58
108
  claude: {
59
109
  label: 'Claude Code',
@@ -77,19 +127,95 @@ const TOOLS = {
77
127
  },
78
128
  };
79
129
 
80
- // ── 工具函数 ───────────────────────────────────────────────────────────────
130
+ // ── 更新规则(update 用)──────────────────────────────────────────────────
131
+ // update: 框架文件,从本机安装版本覆盖
132
+ // preserve: 用户自定义文件,默认跳过(--force 时强制覆盖)
133
+ const UPDATE_RULES = {
134
+ claude: {
135
+ label: 'Claude Code',
136
+ detect: '.claude',
137
+ update: [
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 },
143
+ { src: '.claude/framework-config.json', dest: '.claude/framework-config.json', label: 'framework-config.json' },
144
+ ],
145
+ preserve: [
146
+ { dest: '.claude/settings.json', reason: '包含用户 MCP 配置和权限设置' },
147
+ { dest: 'CLAUDE.md', reason: '包含项目自定义规范' },
148
+ ],
149
+ },
150
+ cursor: {
151
+ label: 'Cursor',
152
+ detect: '.cursor',
153
+ update: [
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 },
157
+ { src: '.cursor/hooks.json', dest: '.cursor/hooks.json', label: 'hooks.json(Hooks 配置)' },
158
+ ],
159
+ preserve: [
160
+ { dest: '.cursor/mcp.json', reason: '包含用户 MCP 服务器配置' },
161
+ ],
162
+ },
163
+ codex: {
164
+ label: 'OpenAI Codex',
165
+ detect: '.codex',
166
+ update: [
167
+ { src: '.codex/skills', dest: '.codex/skills', label: 'Skills(技能库)', isDir: true },
168
+ ],
169
+ preserve: [
170
+ { dest: 'AGENTS.md', reason: '包含项目自定义 Agent 规范' },
171
+ ],
172
+ },
173
+ };
174
+
175
+ // ── 公共工具函数 ──────────────────────────────────────────────────────────
81
176
  const SOURCE_DIR = path.join(__dirname, '..');
82
177
 
178
+ /** 安全判断路径是否为真实目录(避免 existsSync 将文件误判为已安装目录) */
179
+ function isRealDir(p) {
180
+ try { return fs.statSync(p).isDirectory(); } catch { return false; }
181
+ }
182
+
183
+ /** 递归复制目录,返回实际写入的文件数;单文件失败不中断整体 */
83
184
  function copyDir(src, dest) {
84
- if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
85
- for (const entry of fs.readdirSync(src)) {
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) {
86
200
  const s = path.join(src, entry);
87
201
  const d = path.join(dest, entry);
88
- fs.statSync(s).isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
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
+ }
89
207
  }
208
+ return written;
90
209
  }
91
210
 
92
- function copyItem({ src, dest, label, isDir }) {
211
+ /** 构建包含 --dir 上下文的提示命令(方便用户直接复制执行) */
212
+ function hintCmd(subCmd) {
213
+ const dirPart = targetDir !== process.cwd() ? ` --dir "${targetDir}"` : '';
214
+ return `npx ai-engineering-init${dirPart} ${subCmd}`;
215
+ }
216
+
217
+ // ── INIT 逻辑 ─────────────────────────────────────────────────────────────
218
+ function copyItem({ src, dest, label, isDir: srcIsDir }) {
93
219
  const srcPath = path.join(SOURCE_DIR, src);
94
220
  const destPath = path.join(targetDir, dest);
95
221
 
@@ -101,13 +227,25 @@ function copyItem({ src, dest, label, isDir }) {
101
227
  console.log(` ${fmt('yellow', '⚠')} ${label} 已存在,跳过(--force 可强制覆盖)`);
102
228
  return;
103
229
  }
104
- isDir ? copyDir(srcPath, destPath) : fs.copyFileSync(srcPath, destPath);
105
- console.log(` ${fmt('green', '✓')} ${label}`);
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
+ }
106
242
  }
107
243
 
108
244
  function initTool(toolKey) {
109
245
  const t = TOOLS[toolKey];
110
246
  console.log(fmt('cyan', `[${t.label}]`));
247
+ // 确保目标根目录存在(兼容 --dir 指向尚不存在的路径)
248
+ try { fs.mkdirSync(targetDir, { recursive: true }); } catch { /* 已存在忽略 */ }
111
249
  for (const f of t.files) copyItem(f);
112
250
  }
113
251
 
@@ -137,10 +275,9 @@ function showDoneHint(toolKey) {
137
275
  }
138
276
  }
139
277
 
140
- // ── 主逻辑 ────────────────────────────────────────────────────────────────
141
278
  function run(selectedTool) {
142
- if (!['claude', 'cursor', 'codex', 'all'].includes(selectedTool)) {
143
- 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`));
144
281
  process.exit(1);
145
282
  }
146
283
  console.log(` 目标目录: ${fmt('bold', targetDir)}`);
@@ -150,23 +287,177 @@ function run(selectedTool) {
150
287
  console.log('');
151
288
 
152
289
  if (selectedTool === 'all') {
153
- initTool('claude'); console.log(''); initTool('cursor'); console.log(''); initTool('codex');
290
+ Object.keys(TOOLS).forEach((k, i) => { if (i) console.log(''); initTool(k); });
154
291
  } else {
155
292
  initTool(selectedTool);
156
293
  }
157
294
  showDoneHint(selectedTool);
158
295
  }
159
296
 
160
- if (tool) {
297
+ // ── UPDATE 逻辑 ───────────────────────────────────────────────────────────
298
+
299
+ /** 检测当前目录已安装了哪些工具(用 isRealDir 排除误判) */
300
+ function detectInstalledTools() {
301
+ return Object.keys(UPDATE_RULES).filter(key =>
302
+ isRealDir(path.join(targetDir, UPDATE_RULES[key].detect))
303
+ );
304
+ }
305
+
306
+ /** 更新单个工具,返回 { updated, failed, preserved } 文件数 */
307
+ function updateTool(toolKey) {
308
+ const rule = UPDATE_RULES[toolKey];
309
+ console.log(fmt('cyan', `[${rule.label}]`));
310
+
311
+ let updated = 0, failed = 0, preserved = 0;
312
+
313
+ // 更新框架文件
314
+ for (const item of rule.update) {
315
+ const srcPath = path.join(SOURCE_DIR, item.src);
316
+ const destPath = path.join(targetDir, item.dest);
317
+
318
+ if (!fs.existsSync(srcPath)) {
319
+ console.log(` ${fmt('yellow', '⚠')} ${item.label} 源文件不存在,跳过`);
320
+ continue;
321
+ }
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++;
336
+ }
337
+ }
338
+
339
+ // 处理保留文件
340
+ for (const item of rule.preserve) {
341
+ const destPath = path.join(targetDir, item.dest);
342
+ const srcPath = path.join(SOURCE_DIR, item.dest);
343
+
344
+ if (force) {
345
+ if (fs.existsSync(srcPath)) {
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++;
359
+ }
360
+ }
361
+ } else {
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++;
366
+ }
367
+ }
368
+
369
+ return { updated, failed, preserved };
370
+ }
371
+
372
+ /** update 命令主流程 */
373
+ function runUpdate(selectedTool) {
374
+ console.log(` 目标目录: ${fmt('bold', targetDir)}`);
375
+ console.log(` 本机版本: ${fmt('bold', `v${PKG_VERSION}`)}`);
376
+ if (force) console.log(` ${fmt('yellow', '⚠ --force 模式:将同时更新保留文件')}`);
377
+ console.log('');
378
+
379
+ let toolsToUpdate = [];
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
+ // 指定单个工具
395
+ if (!UPDATE_RULES[selectedTool]) {
396
+ console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
397
+ process.exit(1);
398
+ }
399
+ if (!isRealDir(path.join(targetDir, UPDATE_RULES[selectedTool].detect))) {
400
+ console.log(fmt('yellow', `⚠ ${selectedTool} 未在当前目录初始化,请先运行:`));
401
+ console.log(` ${fmt('bold', hintCmd(`--tool ${selectedTool}`))}\n`);
402
+ process.exit(1);
403
+ }
404
+ toolsToUpdate = [selectedTool];
405
+ }
406
+
407
+ console.log('');
408
+ console.log(fmt('bold', '正在更新框架文件...'));
409
+ console.log('');
410
+
411
+ let totalUpdated = 0, totalFailed = 0, totalPreserved = 0;
412
+ for (let i = 0; i < toolsToUpdate.length; i++) {
413
+ const { updated, failed, preserved } = updateTool(toolsToUpdate[i]);
414
+ totalUpdated += updated;
415
+ totalFailed += failed;
416
+ totalPreserved += preserved;
417
+ if (i < toolsToUpdate.length - 1) console.log('');
418
+ }
419
+
420
+ console.log('');
421
+ console.log(fmt('green', fmt('bold', '✅ 更新完成!')));
422
+ console.log('');
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 可强制更新)`);
429
+ }
430
+ console.log('');
431
+ console.log(fmt('cyan', '提示:'));
432
+ console.log(' 重启 Claude Code / Cursor 使新技能生效');
433
+ console.log(` ${fmt('yellow', '注意')}:update 只新增/覆盖文件,不删除旧版本已移除的文件`);
434
+ if (!force && totalPreserved > 0) {
435
+ console.log(` 强制更新保留文件: ${fmt('bold', hintCmd('update --force'))}`);
436
+ }
437
+ console.log('');
438
+
439
+ if (totalFailed > 0) process.exitCode = 1;
440
+ }
441
+
442
+ // ── 主入口 ────────────────────────────────────────────────────────────────
443
+ if (command === 'update') {
444
+ runUpdate(tool);
445
+ } else if (tool) {
161
446
  run(tool);
162
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
+ }
163
454
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
164
455
  console.log(fmt('cyan', '请选择要初始化的 AI 工具:'));
165
456
  console.log('');
166
- console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} — 初始化 .claude/ + CLAUDE.md`);
167
- console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} — 初始化 .cursor/(Skills + Agents + MCP)`);
168
- console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} — 初始化 .codex/ + AGENTS.md`);
169
- console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex`);
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`);
170
461
  console.log('');
171
462
  rl.question(fmt('bold', '请输入选项 [1-4]: '), (answer) => {
172
463
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-engineering-init",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "AI 工程化配置初始化工具 — 一键为 Claude Code、OpenAI Codex 等 AI 工具初始化 Skills 和项目规范",
5
5
  "keywords": [
6
6
  "claude-code",