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.
- package/.claude/settings.json +7 -1
- package/README.md +20 -32
- package/bin/index.js +330 -39
- package/package.json +1 -1
package/.claude/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> 一键初始化 AI 工程化配置,支持 Claude Code、Cursor、OpenAI Codex 等主流 AI 开发工具。
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/ai-engineering-init)
|
|
6
|
+
[](./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
|
-
|
|
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
|
-
**通用后端技能(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
case '
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
167
|
-
console.log(` ${fmt('bold', '2')}) ${fmt('cyan',
|
|
168
|
-
console.log(` ${fmt('bold', '3')}) ${fmt('yellow',
|
|
169
|
-
console.log(` ${fmt('bold', '4')}) ${fmt('blue',
|
|
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();
|