ai-engineering-init 1.1.1 → 1.2.1
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/hooks/skill-forced-eval.js +1 -0
- package/.claude/settings.json +7 -1
- package/.claude/skills/leniu-brainstorm/SKILL.md +651 -0
- package/.claude/skills/leniu-java-export/SKILL.md +23 -22
- package/.claude/skills/leniu-java-total-line/SKILL.md +20 -19
- package/.codex/skills/leniu-brainstorm/SKILL.md +651 -0
- package/.cursor/hooks/cursor-pre-tool-use.js +122 -0
- package/.cursor/hooks/cursor-skill-eval.js +355 -0
- package/.cursor/hooks.json +39 -0
- package/AGENTS.md +1 -0
- package/README.md +15 -16
- package/bin/index.js +233 -24
- package/package.json +1 -1
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
|
|
|
@@ -17,43 +18,58 @@ const readline = require('readline');
|
|
|
17
18
|
const c = {
|
|
18
19
|
reset: '\x1b[0m', bold: '\x1b[1m',
|
|
19
20
|
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
|
|
20
|
-
blue: '\x1b[34m', cyan: '\x1b[36m',
|
|
21
|
+
blue: '\x1b[34m', cyan: '\x1b[36m', magenta: '\x1b[35m',
|
|
21
22
|
};
|
|
22
23
|
const fmt = (color, text) => `${c[color]}${text}${c.reset}`;
|
|
23
24
|
|
|
25
|
+
// ── 版本 ───────────────────────────────────────────────────────────────────
|
|
26
|
+
const PKG_VERSION = require('../package.json').version;
|
|
27
|
+
|
|
24
28
|
// ── Banner ─────────────────────────────────────────────────────────────────
|
|
25
29
|
console.log('');
|
|
26
30
|
console.log(fmt('blue', fmt('bold', '┌─────────────────────────────────────────┐')));
|
|
27
|
-
console.log(fmt('blue', fmt('bold',
|
|
31
|
+
console.log(fmt('blue', fmt('bold', `│ AI Engineering 工具 v${PKG_VERSION} │`)));
|
|
28
32
|
console.log(fmt('blue', fmt('bold', '└─────────────────────────────────────────┘')));
|
|
29
33
|
console.log('');
|
|
30
34
|
|
|
31
35
|
// ── 参数解析 ───────────────────────────────────────────────────────────────
|
|
32
36
|
const args = process.argv.slice(2);
|
|
37
|
+
let command = ''; // 'init' | 'update'
|
|
33
38
|
let tool = '';
|
|
34
39
|
let targetDir = process.cwd();
|
|
35
40
|
let force = false;
|
|
36
41
|
|
|
37
42
|
for (let i = 0; i < args.length; i++) {
|
|
38
43
|
switch (args[i]) {
|
|
39
|
-
case '
|
|
44
|
+
case 'update': command = 'update'; break;
|
|
45
|
+
case '--tool': case '-t': tool = args[++i]; break;
|
|
40
46
|
case '--dir': case '-d': targetDir = path.resolve(args[++i]); break;
|
|
41
|
-
case '--force':case '-f': force
|
|
47
|
+
case '--force':case '-f': force = true; break;
|
|
42
48
|
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');
|
|
49
|
+
printHelp();
|
|
52
50
|
process.exit(0);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
function printHelp() {
|
|
55
|
+
console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [命令] [选项]\n`);
|
|
56
|
+
console.log('命令:');
|
|
57
|
+
console.log(` ${fmt('bold', '(无)') } 交互式初始化`);
|
|
58
|
+
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)\n`);
|
|
59
|
+
console.log('选项:');
|
|
60
|
+
console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
|
|
61
|
+
console.log(' --dir, -d <目录> 目标目录(默认:当前目录)');
|
|
62
|
+
console.log(' --force,-f 强制覆盖(init 时覆盖已有文件;update 时同时更新保留文件)');
|
|
63
|
+
console.log(' --help, -h 显示此帮助\n');
|
|
64
|
+
console.log('示例:');
|
|
65
|
+
console.log(' npx ai-engineering-init --tool claude');
|
|
66
|
+
console.log(' npx ai-engineering-init --tool all --dir /path/to/project');
|
|
67
|
+
console.log(' npx ai-engineering-init update # 自动检测已安装工具');
|
|
68
|
+
console.log(' npx ai-engineering-init update --tool claude # 只更新 Claude');
|
|
69
|
+
console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── 工具定义(init 用) ──────────────────────────────────────────────────────
|
|
57
73
|
const TOOLS = {
|
|
58
74
|
claude: {
|
|
59
75
|
label: 'Claude Code',
|
|
@@ -77,9 +93,66 @@ const TOOLS = {
|
|
|
77
93
|
},
|
|
78
94
|
};
|
|
79
95
|
|
|
80
|
-
// ──
|
|
96
|
+
// ── 更新规则(update 用) ────────────────────────────────────────────────────
|
|
97
|
+
// update: 框架文件,始终从 npm 包最新版本覆盖
|
|
98
|
+
// preserve: 用户自定义文件,默认跳过(--force 时强制更新)
|
|
99
|
+
const UPDATE_RULES = {
|
|
100
|
+
claude: {
|
|
101
|
+
label: 'Claude Code',
|
|
102
|
+
detect: '.claude', // 检测目录,判断是否已安装
|
|
103
|
+
update: [
|
|
104
|
+
{ src: '.claude/skills', dest: '.claude/skills', label: 'Skills(技能库)', isDir: true },
|
|
105
|
+
{ src: '.claude/commands', dest: '.claude/commands', label: 'Commands(快捷命令)', isDir: true },
|
|
106
|
+
{ src: '.claude/agents', dest: '.claude/agents', label: 'Agents(子代理)', isDir: true },
|
|
107
|
+
{ src: '.claude/hooks', dest: '.claude/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
108
|
+
{ src: '.claude/templates', dest: '.claude/templates', label: 'Templates', isDir: true },
|
|
109
|
+
{ src: '.claude/framework-config.json', dest: '.claude/framework-config.json', label: 'framework-config.json' },
|
|
110
|
+
],
|
|
111
|
+
preserve: [
|
|
112
|
+
{ dest: '.claude/settings.json', reason: '包含用户 MCP 配置和权限设置' },
|
|
113
|
+
{ dest: 'CLAUDE.md', reason: '包含项目自定义规范' },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
cursor: {
|
|
117
|
+
label: 'Cursor',
|
|
118
|
+
detect: '.cursor',
|
|
119
|
+
update: [
|
|
120
|
+
{ src: '.cursor/skills', dest: '.cursor/skills', label: 'Skills(技能库)', isDir: true },
|
|
121
|
+
{ src: '.cursor/agents', dest: '.cursor/agents', label: 'Agents(子代理)', isDir: true },
|
|
122
|
+
{ src: '.cursor/hooks', dest: '.cursor/hooks', label: 'Hooks(钩子脚本)', isDir: true },
|
|
123
|
+
{ src: '.cursor/hooks.json', dest: '.cursor/hooks.json', label: 'hooks.json(Hooks 配置)' },
|
|
124
|
+
],
|
|
125
|
+
preserve: [
|
|
126
|
+
{ dest: '.cursor/mcp.json', reason: '包含用户 MCP 服务器配置' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
codex: {
|
|
130
|
+
label: 'OpenAI Codex',
|
|
131
|
+
detect: '.codex',
|
|
132
|
+
update: [
|
|
133
|
+
{ src: '.codex/skills', dest: '.codex/skills', label: 'Skills(技能库)', isDir: true },
|
|
134
|
+
],
|
|
135
|
+
preserve: [
|
|
136
|
+
{ dest: 'AGENTS.md', reason: '包含项目自定义 Agent 规范' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ── 公共工具函数 ───────────────────────────────────────────────────────────
|
|
81
142
|
const SOURCE_DIR = path.join(__dirname, '..');
|
|
82
143
|
|
|
144
|
+
/** 统计目录中的文件总数 */
|
|
145
|
+
function countFiles(dir) {
|
|
146
|
+
if (!fs.existsSync(dir)) return 0;
|
|
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;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 递归复制目录 */
|
|
83
156
|
function copyDir(src, dest) {
|
|
84
157
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
85
158
|
for (const entry of fs.readdirSync(src)) {
|
|
@@ -89,6 +162,7 @@ function copyDir(src, dest) {
|
|
|
89
162
|
}
|
|
90
163
|
}
|
|
91
164
|
|
|
165
|
+
// ── INIT 逻辑 ──────────────────────────────────────────────────────────────
|
|
92
166
|
function copyItem({ src, dest, label, isDir }) {
|
|
93
167
|
const srcPath = path.join(SOURCE_DIR, src);
|
|
94
168
|
const destPath = path.join(targetDir, dest);
|
|
@@ -137,7 +211,6 @@ function showDoneHint(toolKey) {
|
|
|
137
211
|
}
|
|
138
212
|
}
|
|
139
213
|
|
|
140
|
-
// ── 主逻辑 ────────────────────────────────────────────────────────────────
|
|
141
214
|
function run(selectedTool) {
|
|
142
215
|
if (!['claude', 'cursor', 'codex', 'all'].includes(selectedTool)) {
|
|
143
216
|
console.error(fmt('red', `无效工具: ${selectedTool}。有效选项: claude | cursor | codex | all`));
|
|
@@ -157,16 +230,152 @@ function run(selectedTool) {
|
|
|
157
230
|
showDoneHint(selectedTool);
|
|
158
231
|
}
|
|
159
232
|
|
|
160
|
-
|
|
233
|
+
// ── UPDATE 逻辑 ─────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
/** 检测当前目录已安装了哪些工具 */
|
|
236
|
+
function detectInstalledTools() {
|
|
237
|
+
return Object.keys(UPDATE_RULES).filter(key => {
|
|
238
|
+
const detectPath = path.join(targetDir, UPDATE_RULES[key].detect);
|
|
239
|
+
return fs.existsSync(detectPath);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 更新单个工具的框架文件 */
|
|
244
|
+
function updateTool(toolKey) {
|
|
245
|
+
const rule = UPDATE_RULES[toolKey];
|
|
246
|
+
console.log(fmt('cyan', `[${rule.label}]`));
|
|
247
|
+
|
|
248
|
+
let updatedCount = 0;
|
|
249
|
+
let skippedCount = 0;
|
|
250
|
+
|
|
251
|
+
// 更新框架文件
|
|
252
|
+
for (const item of rule.update) {
|
|
253
|
+
const srcPath = path.join(SOURCE_DIR, item.src);
|
|
254
|
+
const destPath = path.join(targetDir, item.dest);
|
|
255
|
+
|
|
256
|
+
if (!fs.existsSync(srcPath)) {
|
|
257
|
+
console.log(` ${fmt('yellow', '⚠')} ${item.label} 源文件不存在,跳过`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const fileCount = item.isDir ? countFiles(srcPath) : 1;
|
|
262
|
+
if (item.isDir) {
|
|
263
|
+
copyDir(srcPath, destPath);
|
|
264
|
+
} else {
|
|
265
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
266
|
+
fs.copyFileSync(srcPath, destPath);
|
|
267
|
+
}
|
|
268
|
+
console.log(` ${fmt('green', '✓')} ${item.label} ${fmt('magenta', `(${fileCount} 个文件)`)}`);
|
|
269
|
+
updatedCount += fileCount;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 处理保留文件
|
|
273
|
+
for (const item of rule.preserve) {
|
|
274
|
+
const destPath = path.join(targetDir, item.dest);
|
|
275
|
+
const srcPath = path.join(SOURCE_DIR, item.dest);
|
|
276
|
+
|
|
277
|
+
if (force) {
|
|
278
|
+
// --force 时强制更新保留文件
|
|
279
|
+
if (fs.existsSync(srcPath)) {
|
|
280
|
+
const stat = fs.statSync(srcPath);
|
|
281
|
+
if (stat.isDirectory()) {
|
|
282
|
+
copyDir(srcPath, destPath);
|
|
283
|
+
} else {
|
|
284
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
285
|
+
fs.copyFileSync(srcPath, destPath);
|
|
286
|
+
}
|
|
287
|
+
console.log(` ${fmt('green', '✓')} ${item.dest} ${fmt('yellow', '(强制更新)')}`);
|
|
288
|
+
updatedCount++;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
if (fs.existsSync(destPath)) {
|
|
292
|
+
console.log(` ${fmt('yellow', '⊘')} ${item.dest} ${fmt('yellow', `已保留`)} — ${item.reason}`);
|
|
293
|
+
skippedCount++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { updatedCount, skippedCount };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** update 命令主流程 */
|
|
302
|
+
function runUpdate(selectedTool) {
|
|
303
|
+
console.log(` 目标目录: ${fmt('bold', targetDir)}`);
|
|
304
|
+
console.log(` 来源版本: ${fmt('bold', `v${PKG_VERSION}`)} (npm latest)`);
|
|
305
|
+
if (force) console.log(` ${fmt('yellow', '⚠ --force 模式:将同时更新用户保留文件')}`);
|
|
306
|
+
console.log('');
|
|
307
|
+
|
|
308
|
+
// 确定要更新的工具列表
|
|
309
|
+
let toolsToUpdate = [];
|
|
310
|
+
if (selectedTool && selectedTool !== 'all') {
|
|
311
|
+
if (!UPDATE_RULES[selectedTool]) {
|
|
312
|
+
console.error(fmt('red', `无效工具: ${selectedTool}。有效选项: claude | cursor | codex | all`));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
const detectPath = path.join(targetDir, UPDATE_RULES[selectedTool].detect);
|
|
316
|
+
if (!fs.existsSync(detectPath)) {
|
|
317
|
+
console.log(fmt('yellow', `⚠ ${selectedTool} 未在当前目录初始化,请先运行:`));
|
|
318
|
+
console.log(` ${fmt('bold', `npx ai-engineering-init --tool ${selectedTool}`)}\n`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
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
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(fmt('bold', '正在更新框架文件...'));
|
|
337
|
+
console.log('');
|
|
338
|
+
|
|
339
|
+
let totalUpdated = 0;
|
|
340
|
+
let totalSkipped = 0;
|
|
341
|
+
|
|
342
|
+
for (let i = 0; i < toolsToUpdate.length; i++) {
|
|
343
|
+
const { updatedCount, skippedCount } = updateTool(toolsToUpdate[i]);
|
|
344
|
+
totalUpdated += updatedCount;
|
|
345
|
+
totalSkipped += skippedCount;
|
|
346
|
+
if (i < toolsToUpdate.length - 1) console.log('');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 汇总
|
|
350
|
+
console.log('');
|
|
351
|
+
console.log(fmt('green', fmt('bold', '✅ 更新完成!')));
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(` ${fmt('green', `✓ 更新文件: ${totalUpdated} 个`)}`);
|
|
354
|
+
if (totalSkipped > 0) {
|
|
355
|
+
console.log(` ${fmt('yellow', `⊘ 已保留文件: ${totalSkipped} 个`)}(用户自定义,使用 --force 可强制更新)`);
|
|
356
|
+
}
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log(fmt('cyan', '提示:'));
|
|
359
|
+
console.log(' 重启 Claude Code / Cursor 使新技能生效');
|
|
360
|
+
if (!force && totalSkipped > 0) {
|
|
361
|
+
console.log(` 如需同步保留文件,运行: ${fmt('bold', `npx ai-engineering-init update --force`)}`);
|
|
362
|
+
}
|
|
363
|
+
console.log('');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ── 主入口 ─────────────────────────────────────────────────────────────────
|
|
367
|
+
if (command === 'update') {
|
|
368
|
+
runUpdate(tool);
|
|
369
|
+
} else if (tool) {
|
|
161
370
|
run(tool);
|
|
162
371
|
} else {
|
|
163
372
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
164
373
|
console.log(fmt('cyan', '请选择要初始化的 AI 工具:'));
|
|
165
374
|
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',
|
|
375
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} — 初始化 .claude/ + CLAUDE.md`);
|
|
376
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} — 初始化 .cursor/(Skills + Agents + MCP)`);
|
|
377
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} — 初始化 .codex/ + AGENTS.md`);
|
|
378
|
+
console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex`);
|
|
170
379
|
console.log('');
|
|
171
380
|
rl.question(fmt('bold', '请输入选项 [1-4]: '), (answer) => {
|
|
172
381
|
rl.close();
|