ai-engineering-init 1.5.0 → 1.7.0
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/agents/code-reviewer.md +3 -130
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/hooks/stop.js +24 -1
- package/.claude/skills/codex-code-review/SKILL.md +327 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +82 -2
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.claude/skills/loki-log-query/SKILL.md +400 -0
- package/.claude/skills/mysql-debug/SKILL.md +58 -22
- package/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/skill-creator/SKILL.md +479 -0
- package/.claude/skills/skill-creator/agents/analyzer.md +274 -0
- package/.claude/skills/skill-creator/agents/comparator.md +202 -0
- package/.claude/skills/skill-creator/agents/grader.md +223 -0
- package/.claude/skills/skill-creator/assets/eval_review.html +146 -0
- package/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.claude/skills/skill-creator/references/schemas.md +430 -0
- package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
- package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.claude/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.claude/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.claude/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.claude/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.claude/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.claude/skills/skill-creator/scripts/utils.py +47 -0
- package/.claude/skills/sync-back-merge/SKILL.md +66 -0
- package/.claude/skills/yunxiao-task-management/SKILL.md +489 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +82 -2
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.codex/skills/loki-log-query/SKILL.md +400 -0
- package/.codex/skills/loki-log-query/environments.json +45 -0
- package/.codex/skills/mysql-debug/SKILL.md +58 -22
- package/.codex/skills/skill-creator/LICENSE.txt +202 -0
- package/.codex/skills/skill-creator/SKILL.md +479 -0
- package/.codex/skills/skill-creator/agents/analyzer.md +274 -0
- package/.codex/skills/skill-creator/agents/comparator.md +202 -0
- package/.codex/skills/skill-creator/agents/grader.md +223 -0
- package/.codex/skills/skill-creator/assets/eval_review.html +146 -0
- package/.codex/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.codex/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.codex/skills/skill-creator/references/schemas.md +430 -0
- package/.codex/skills/skill-creator/scripts/__init__.py +0 -0
- package/.codex/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.codex/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.codex/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.codex/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.codex/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.codex/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.codex/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.codex/skills/skill-creator/scripts/utils.py +47 -0
- package/.codex/skills/sync-back-merge/SKILL.md +66 -0
- package/.codex/skills/yunxiao-task-management/SKILL.md +489 -0
- package/.cursor/hooks/stop.js +23 -1
- package/.cursor/skills/leniu-report-customization/SKILL.md +82 -2
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.cursor/skills/loki-log-query/SKILL.md +400 -0
- package/.cursor/skills/loki-log-query/environments.json +45 -0
- package/.cursor/skills/mysql-debug/SKILL.md +58 -22
- package/.cursor/skills/skill-creator/LICENSE.txt +202 -0
- package/.cursor/skills/skill-creator/SKILL.md +479 -0
- package/.cursor/skills/skill-creator/agents/analyzer.md +274 -0
- package/.cursor/skills/skill-creator/agents/comparator.md +202 -0
- package/.cursor/skills/skill-creator/agents/grader.md +223 -0
- package/.cursor/skills/skill-creator/assets/eval_review.html +146 -0
- package/.cursor/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.cursor/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.cursor/skills/skill-creator/references/schemas.md +430 -0
- package/.cursor/skills/skill-creator/scripts/__init__.py +0 -0
- package/.cursor/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.cursor/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.cursor/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.cursor/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.cursor/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.cursor/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.cursor/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.cursor/skills/skill-creator/scripts/utils.py +47 -0
- package/.cursor/skills/sync-back-merge/SKILL.md +66 -0
- package/.cursor/skills/yunxiao-task-management/SKILL.md +489 -0
- package/README.md +20 -236
- package/bin/index.js +1040 -28
- package/package.json +1 -1
- package/src/platform-map.json +4 -0
- package/src/skills/codex-code-review/SKILL.md +261 -69
- package/src/skills/leniu-report-customization/SKILL.md +82 -2
- package/src/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/src/skills/loki-log-query/SKILL.md +400 -0
- package/src/skills/loki-log-query/environments.json +45 -0
- package/src/skills/mysql-debug/SKILL.md +58 -22
- package/src/skills/skill-creator/LICENSE.txt +202 -0
- package/src/skills/skill-creator/SKILL.md +479 -0
- package/src/skills/skill-creator/agents/analyzer.md +274 -0
- package/src/skills/skill-creator/agents/comparator.md +202 -0
- package/src/skills/skill-creator/agents/grader.md +223 -0
- package/src/skills/skill-creator/assets/eval_review.html +146 -0
- package/src/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/src/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/src/skills/skill-creator/references/schemas.md +430 -0
- package/src/skills/skill-creator/scripts/__init__.py +0 -0
- package/src/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/src/skills/skill-creator/scripts/generate_report.py +326 -0
- package/src/skills/skill-creator/scripts/improve_description.py +248 -0
- package/src/skills/skill-creator/scripts/package_skill.py +136 -0
- package/src/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/src/skills/skill-creator/scripts/run_eval.py +310 -0
- package/src/skills/skill-creator/scripts/run_loop.py +332 -0
- package/src/skills/skill-creator/scripts/utils.py +47 -0
- package/src/skills/sync-back-merge/SKILL.md +66 -0
- package/src/skills/yunxiao-task-management/SKILL.md +489 -0
package/bin/index.js
CHANGED
|
@@ -46,10 +46,12 @@ console.log('');
|
|
|
46
46
|
|
|
47
47
|
// ── 参数解析 ───────────────────────────────────────────────────────────────
|
|
48
48
|
const args = process.argv.slice(2);
|
|
49
|
-
let command = ''; // 'update' | 'global' | ''
|
|
49
|
+
let command = ''; // 'update' | 'global' | 'sync-back' | ''
|
|
50
50
|
let tool = '';
|
|
51
51
|
let targetDir = process.cwd();
|
|
52
52
|
let force = false;
|
|
53
|
+
let skillFilter = ''; // sync-back --skill <名称>
|
|
54
|
+
let submitIssue = false; // sync-back --submit
|
|
53
55
|
|
|
54
56
|
for (let i = 0; i < args.length; i++) {
|
|
55
57
|
const arg = args[i];
|
|
@@ -60,6 +62,18 @@ for (let i = 0; i < args.length; i++) {
|
|
|
60
62
|
case 'global':
|
|
61
63
|
command = 'global';
|
|
62
64
|
break;
|
|
65
|
+
case 'init':
|
|
66
|
+
command = 'init';
|
|
67
|
+
break;
|
|
68
|
+
case 'sync-back':
|
|
69
|
+
command = 'sync-back';
|
|
70
|
+
break;
|
|
71
|
+
case 'config':
|
|
72
|
+
command = 'config';
|
|
73
|
+
break;
|
|
74
|
+
case 'mcp':
|
|
75
|
+
command = 'mcp';
|
|
76
|
+
break;
|
|
63
77
|
case '--tool': case '-t':
|
|
64
78
|
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
65
79
|
console.error(fmt('red', `错误:${arg} 需要一个值(claude | cursor | codex | all)`));
|
|
@@ -77,6 +91,16 @@ for (let i = 0; i < args.length; i++) {
|
|
|
77
91
|
case '--force': case '-f':
|
|
78
92
|
force = true;
|
|
79
93
|
break;
|
|
94
|
+
case '--skill': case '-s':
|
|
95
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
96
|
+
console.error(fmt('red', `错误:${arg} 需要一个技能名称`));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
skillFilter = args[++i];
|
|
100
|
+
break;
|
|
101
|
+
case '--submit':
|
|
102
|
+
submitIssue = true;
|
|
103
|
+
break;
|
|
80
104
|
case '--help': case '-h':
|
|
81
105
|
printHelp();
|
|
82
106
|
process.exit(0);
|
|
@@ -94,14 +118,20 @@ for (let i = 0; i < args.length; i++) {
|
|
|
94
118
|
function printHelp() {
|
|
95
119
|
console.log(`用法: ${fmt('bold', 'npx ai-engineering-init')} [命令] [选项]\n`);
|
|
96
120
|
console.log('命令:');
|
|
97
|
-
console.log(` ${fmt('bold', '
|
|
121
|
+
console.log(` ${fmt('bold', 'init')} 交互式初始化(安装到当前项目目录)`);
|
|
98
122
|
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)`);
|
|
99
|
-
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor
|
|
123
|
+
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor 等,对所有项目生效`);
|
|
124
|
+
console.log(` ${fmt('bold', 'sync-back')} 对比本地技能修改,生成 diff 或提交 GitHub Issue`);
|
|
125
|
+
console.log(` ${fmt('bold', 'config')} 初始化数据库配置文件(.claude/mysql-config.json)`);
|
|
126
|
+
console.log(` ${fmt('bold', 'mcp')} MCP 服务器管理(安装/卸载/状态检查)\n`);
|
|
127
|
+
console.log(`无命令时显示交互式主菜单。\n`);
|
|
100
128
|
console.log('选项:');
|
|
101
|
-
console.log(' --tool,
|
|
102
|
-
console.log(' --dir,
|
|
103
|
-
console.log(' --force
|
|
104
|
-
console.log(' --
|
|
129
|
+
console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
|
|
130
|
+
console.log(' --dir, -d <目录> 目标目录(默认:当前目录,仅 init/update 有效)');
|
|
131
|
+
console.log(' --force, -f 强制覆盖(init 时覆盖已有文件;update/global 时同时更新保留文件)');
|
|
132
|
+
console.log(' --skill, -s <技能> sync-back 时只对比指定技能');
|
|
133
|
+
console.log(' --submit sync-back 时自动创建 GitHub Issue(需要 gh CLI)');
|
|
134
|
+
console.log(' --help, -h 显示此帮助\n');
|
|
105
135
|
console.log('示例:');
|
|
106
136
|
console.log(' npx ai-engineering-init --tool claude');
|
|
107
137
|
console.log(' npx ai-engineering-init --tool all --dir /path/to/project');
|
|
@@ -109,7 +139,11 @@ function printHelp() {
|
|
|
109
139
|
console.log(' npx ai-engineering-init update --tool claude # 只更新 Claude');
|
|
110
140
|
console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件');
|
|
111
141
|
console.log(' npx ai-engineering-init global # 全局安装所有工具');
|
|
112
|
-
console.log(' npx ai-engineering-init global --tool claude # 只全局安装 Claude
|
|
142
|
+
console.log(' npx ai-engineering-init global --tool claude # 只全局安装 Claude');
|
|
143
|
+
console.log(' npx ai-engineering-init sync-back # 扫描所有已安装工具');
|
|
144
|
+
console.log(' npx ai-engineering-init sync-back --tool claude # 只扫描 Claude');
|
|
145
|
+
console.log(' npx ai-engineering-init sync-back --skill bug-detective # 只对比指定技能');
|
|
146
|
+
console.log(' npx ai-engineering-init sync-back --skill bug-detective --submit # 提交 Issue\n');
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
// ── 工具定义(init 用)────────────────────────────────────────────────────
|
|
@@ -675,37 +709,942 @@ function runUpdate(selectedTool) {
|
|
|
675
709
|
if (totalFailed > 0) process.exitCode = 1;
|
|
676
710
|
}
|
|
677
711
|
|
|
678
|
-
// ──
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
712
|
+
// ── SYNC-BACK 逻辑 ─────────────────────────────────────────────────────────
|
|
713
|
+
|
|
714
|
+
/** 技能目录名称到工具的映射 */
|
|
715
|
+
const SKILL_DIRS = {
|
|
716
|
+
claude: '.claude/skills',
|
|
717
|
+
cursor: '.cursor/skills',
|
|
718
|
+
codex: '.codex/skills',
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
/** 递归列出目录下所有文件(相对路径) */
|
|
722
|
+
function listFilesRecursive(dir, prefix) {
|
|
723
|
+
prefix = prefix || '';
|
|
724
|
+
let results = [];
|
|
725
|
+
let entries;
|
|
726
|
+
try { entries = fs.readdirSync(dir); } catch { return results; }
|
|
727
|
+
for (const entry of entries) {
|
|
728
|
+
const fullPath = path.join(dir, entry);
|
|
729
|
+
const relPath = prefix ? prefix + '/' + entry : entry;
|
|
730
|
+
try {
|
|
731
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
732
|
+
results = results.concat(listFilesRecursive(fullPath, relPath));
|
|
733
|
+
} else {
|
|
734
|
+
results.push(relPath);
|
|
735
|
+
}
|
|
736
|
+
} catch { /* 跳过不可读文件 */ }
|
|
737
|
+
}
|
|
738
|
+
return results;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* 简易 unified diff 生成器(纯 Node.js,零依赖)
|
|
743
|
+
* 使用贪心 LCS 简化算法,输出标准 unified diff 格式
|
|
744
|
+
*/
|
|
745
|
+
function generateDiff(oldContent, newContent, oldLabel, newLabel) {
|
|
746
|
+
const oldLines = oldContent.split(/\r?\n/);
|
|
747
|
+
const newLines = newContent.split(/\r?\n/);
|
|
748
|
+
|
|
749
|
+
// 计算 LCS 表(简化版,O(n*m) 但对技能文件足够)
|
|
750
|
+
const m = oldLines.length;
|
|
751
|
+
const n = newLines.length;
|
|
752
|
+
|
|
753
|
+
// 对于大文件,跳过 LCS 直接标记全部替换
|
|
754
|
+
if (m * n > 1000000) {
|
|
755
|
+
const lines = [];
|
|
756
|
+
lines.push(`--- ${oldLabel}`);
|
|
757
|
+
lines.push(`+++ ${newLabel}`);
|
|
758
|
+
lines.push(`@@ -1,${m} +1,${n} @@`);
|
|
759
|
+
for (const line of oldLines) lines.push('-' + line);
|
|
760
|
+
for (const line of newLines) lines.push('+' + line);
|
|
761
|
+
return lines.join('\n');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// LCS 回溯表
|
|
765
|
+
const dp = Array.from({ length: m + 1 }, () => new Uint16Array(n + 1));
|
|
766
|
+
for (let i = 1; i <= m; i++) {
|
|
767
|
+
for (let j = 1; j <= n; j++) {
|
|
768
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
769
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
770
|
+
} else {
|
|
771
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// 回溯生成编辑操作序列
|
|
777
|
+
const ops = []; // { type: 'equal'|'delete'|'insert', line }
|
|
778
|
+
let i = m, j = n;
|
|
779
|
+
while (i > 0 || j > 0) {
|
|
780
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
781
|
+
ops.push({ type: 'equal', oldIdx: i, newIdx: j, line: oldLines[i - 1] });
|
|
782
|
+
i--; j--;
|
|
783
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
784
|
+
ops.push({ type: 'insert', newIdx: j, line: newLines[j - 1] });
|
|
785
|
+
j--;
|
|
786
|
+
} else {
|
|
787
|
+
ops.push({ type: 'delete', oldIdx: i, line: oldLines[i - 1] });
|
|
788
|
+
i--;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
ops.reverse();
|
|
792
|
+
|
|
793
|
+
// 将操作序列组织为 hunks(上下文 3 行)
|
|
794
|
+
const CTX = 3;
|
|
795
|
+
const hunks = [];
|
|
796
|
+
let hunkOps = [];
|
|
797
|
+
let lastChangeIdx = -999;
|
|
798
|
+
|
|
799
|
+
for (let k = 0; k < ops.length; k++) {
|
|
800
|
+
if (ops[k].type !== 'equal') {
|
|
801
|
+
// 如果距离上次变更超过 2*CTX+1,开始新 hunk
|
|
802
|
+
if (k - lastChangeIdx > 2 * CTX + 1 && hunkOps.length > 0) {
|
|
803
|
+
hunks.push(hunkOps);
|
|
804
|
+
hunkOps = [];
|
|
805
|
+
// 回退加上下文
|
|
806
|
+
const start = Math.max(k - CTX, lastChangeIdx + CTX + 1);
|
|
807
|
+
for (let c = start; c < k; c++) {
|
|
808
|
+
if (ops[c]) hunkOps.push(ops[c]);
|
|
809
|
+
}
|
|
810
|
+
} else if (hunkOps.length === 0) {
|
|
811
|
+
// 新 hunk 加前上下文
|
|
812
|
+
const start = Math.max(0, k - CTX);
|
|
813
|
+
for (let c = start; c < k; c++) {
|
|
814
|
+
hunkOps.push(ops[c]);
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
// 补充中间的上下文行
|
|
818
|
+
for (let c = lastChangeIdx + 1; c < k; c++) {
|
|
819
|
+
if (!hunkOps.includes(ops[c])) hunkOps.push(ops[c]);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
hunkOps.push(ops[k]);
|
|
823
|
+
lastChangeIdx = k;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// 最后一个 hunk 加后上下文
|
|
827
|
+
if (hunkOps.length > 0) {
|
|
828
|
+
const end = Math.min(ops.length, lastChangeIdx + CTX + 1);
|
|
829
|
+
for (let c = lastChangeIdx + 1; c < end; c++) {
|
|
830
|
+
hunkOps.push(ops[c]);
|
|
831
|
+
}
|
|
832
|
+
hunks.push(hunkOps);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (hunks.length === 0) return ''; // 无差异
|
|
836
|
+
|
|
837
|
+
// 格式化输出
|
|
838
|
+
const lines = [];
|
|
839
|
+
lines.push(`--- ${oldLabel}`);
|
|
840
|
+
lines.push(`+++ ${newLabel}`);
|
|
841
|
+
|
|
842
|
+
for (const hunk of hunks) {
|
|
843
|
+
// 计算 hunk 头
|
|
844
|
+
let oldStart = Infinity, oldCount = 0, newStart = Infinity, newCount = 0;
|
|
845
|
+
for (const op of hunk) {
|
|
846
|
+
if (op.type === 'equal' || op.type === 'delete') {
|
|
847
|
+
if (op.oldIdx < oldStart) oldStart = op.oldIdx;
|
|
848
|
+
oldCount++;
|
|
849
|
+
}
|
|
850
|
+
if (op.type === 'equal' || op.type === 'insert') {
|
|
851
|
+
if (op.newIdx < newStart) newStart = op.newIdx;
|
|
852
|
+
newCount++;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
|
|
856
|
+
for (const op of hunk) {
|
|
857
|
+
if (op.type === 'equal') lines.push(' ' + op.line);
|
|
858
|
+
if (op.type === 'delete') lines.push('-' + op.line);
|
|
859
|
+
if (op.type === 'insert') lines.push('+' + op.line);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return lines.join('\n');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/** 统计 diff 中的增删行数 */
|
|
867
|
+
function countDiffLines(diffText) {
|
|
868
|
+
let added = 0, removed = 0;
|
|
869
|
+
for (const line of diffText.split('\n')) {
|
|
870
|
+
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
871
|
+
if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
872
|
+
}
|
|
873
|
+
return { added, removed };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/** 检测 gh CLI 是否可用 */
|
|
877
|
+
function isGhAvailable() {
|
|
878
|
+
try {
|
|
879
|
+
const { execSync } = require('child_process');
|
|
880
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
881
|
+
return true;
|
|
882
|
+
} catch { return false; }
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** 通过 gh CLI 创建 GitHub Issue */
|
|
886
|
+
function submitGitHubIssue(changes, allDiffText) {
|
|
887
|
+
const { execSync } = require('child_process');
|
|
888
|
+
const skillNames = changes.map(c => c.skillName).join(', ');
|
|
889
|
+
const title = `[sync-back] 技能改进:${skillNames}`;
|
|
890
|
+
const body = [
|
|
891
|
+
'## 技能修改反馈',
|
|
892
|
+
'',
|
|
893
|
+
`> 由 \`npx ai-engineering-init sync-back --submit\` 自动生成`,
|
|
894
|
+
'',
|
|
895
|
+
'### 修改的技能',
|
|
896
|
+
'',
|
|
897
|
+
...changes.map(c => {
|
|
898
|
+
const files = c.files.map(f => ` - \`${f.relPath}\` (+${f.added}, -${f.removed})`).join('\n');
|
|
899
|
+
return `- **${c.skillName}**\n${files}`;
|
|
900
|
+
}),
|
|
901
|
+
'',
|
|
902
|
+
'### Diff',
|
|
903
|
+
'',
|
|
904
|
+
'```diff',
|
|
905
|
+
allDiffText,
|
|
906
|
+
'```',
|
|
907
|
+
'',
|
|
908
|
+
'---',
|
|
909
|
+
`CLI 版本: v${PKG_VERSION}`,
|
|
910
|
+
].join('\n');
|
|
911
|
+
|
|
912
|
+
try {
|
|
913
|
+
const result = execSync(
|
|
914
|
+
`gh issue create --repo xu-cell/ai-engineering-init --title "${title.replace(/"/g, '\\"')}" --body-file -`,
|
|
915
|
+
{ input: body, stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8' }
|
|
916
|
+
);
|
|
917
|
+
return result.trim();
|
|
918
|
+
} catch (e) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** sync-back 命令主流程 */
|
|
924
|
+
function runSyncBack(selectedTool, selectedSkill, doSubmit) {
|
|
925
|
+
console.log(` 目标目录: ${fmt('bold', targetDir)}`);
|
|
926
|
+
console.log(` 本机版本: ${fmt('bold', `v${PKG_VERSION}`)}`);
|
|
927
|
+
console.log('');
|
|
928
|
+
|
|
929
|
+
// 1. 确定扫描范围
|
|
930
|
+
let toolsToScan = [];
|
|
931
|
+
if (selectedTool && selectedTool !== 'all') {
|
|
932
|
+
if (!SKILL_DIRS[selectedTool]) {
|
|
933
|
+
console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
|
|
934
|
+
process.exit(1);
|
|
935
|
+
}
|
|
936
|
+
toolsToScan = [selectedTool];
|
|
937
|
+
} else {
|
|
938
|
+
toolsToScan = detectInstalledTools();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (toolsToScan.length === 0) {
|
|
942
|
+
console.log(fmt('yellow', '⚠ 当前目录未检测到已安装的 AI 工具配置。'));
|
|
943
|
+
console.log(` 请先运行: ${fmt('bold', hintCmd('--tool claude'))}\n`);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
console.log(` 扫描工具: ${fmt('bold', toolsToScan.join(', '))}`);
|
|
948
|
+
console.log('');
|
|
949
|
+
console.log(fmt('bold', '🔍 正在对比技能文件...'));
|
|
950
|
+
console.log('');
|
|
951
|
+
|
|
952
|
+
// 2. 对比每个工具的 skills 目录
|
|
953
|
+
const allChanges = []; // { toolKey, skillName, files: [{ relPath, diff, added, removed }] }
|
|
954
|
+
|
|
955
|
+
for (const toolKey of toolsToScan) {
|
|
956
|
+
const skillDir = SKILL_DIRS[toolKey];
|
|
957
|
+
const userSkillsDir = path.join(targetDir, skillDir);
|
|
958
|
+
const srcSkillsDir = path.join(SOURCE_DIR, skillDir);
|
|
959
|
+
|
|
960
|
+
if (!isRealDir(userSkillsDir) || !isRealDir(srcSkillsDir)) continue;
|
|
961
|
+
|
|
962
|
+
// 列出用户目录中的技能
|
|
963
|
+
let skillNames;
|
|
964
|
+
try { skillNames = fs.readdirSync(userSkillsDir); } catch { continue; }
|
|
965
|
+
|
|
966
|
+
for (const name of skillNames) {
|
|
967
|
+
if (selectedSkill && name !== selectedSkill) continue;
|
|
968
|
+
|
|
969
|
+
const userSkillDir = path.join(userSkillsDir, name);
|
|
970
|
+
const srcSkillDir = path.join(srcSkillsDir, name);
|
|
971
|
+
|
|
972
|
+
if (!isRealDir(userSkillDir)) continue;
|
|
973
|
+
|
|
974
|
+
// 列出用户技能目录下所有文件
|
|
975
|
+
const userFiles = listFilesRecursive(userSkillDir);
|
|
976
|
+
const srcFiles = isRealDir(srcSkillDir) ? listFilesRecursive(srcSkillDir) : [];
|
|
977
|
+
const allFiles = [...new Set([...userFiles, ...srcFiles])].sort();
|
|
978
|
+
|
|
979
|
+
const changedFiles = [];
|
|
980
|
+
|
|
981
|
+
for (const relFile of allFiles) {
|
|
982
|
+
const userFile = path.join(userSkillDir, relFile);
|
|
983
|
+
const srcFile = path.join(srcSkillDir, relFile);
|
|
984
|
+
|
|
985
|
+
const userExists = fs.existsSync(userFile);
|
|
986
|
+
const srcExists = fs.existsSync(srcFile);
|
|
987
|
+
|
|
988
|
+
if (userExists && srcExists) {
|
|
989
|
+
// 两边都有,对比内容
|
|
990
|
+
const userContent = fs.readFileSync(userFile, 'utf8');
|
|
991
|
+
const srcContent = fs.readFileSync(srcFile, 'utf8');
|
|
992
|
+
if (userContent !== srcContent) {
|
|
993
|
+
const diff = generateDiff(srcContent, userContent,
|
|
994
|
+
`原版 (v${PKG_VERSION})`, '本地修改');
|
|
995
|
+
if (diff) {
|
|
996
|
+
const { added, removed } = countDiffLines(diff);
|
|
997
|
+
changedFiles.push({ relPath: relFile, diff, added, removed, status: 'modified' });
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} else if (userExists && !srcExists) {
|
|
1001
|
+
// 用户新增的文件
|
|
1002
|
+
const content = fs.readFileSync(userFile, 'utf8');
|
|
1003
|
+
const lineCount = content.split(/\r?\n/).length;
|
|
1004
|
+
changedFiles.push({
|
|
1005
|
+
relPath: relFile,
|
|
1006
|
+
diff: `--- /dev/null\n+++ 本地新增\n@@ -0,0 +1,${lineCount} @@\n` +
|
|
1007
|
+
content.split(/\r?\n/).map(l => '+' + l).join('\n'),
|
|
1008
|
+
added: lineCount, removed: 0, status: 'added'
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
// srcExists && !userExists: 用户删除的文件(不报告,可能是有意删除)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (changedFiles.length > 0) {
|
|
1015
|
+
// 去重:只保留第一个工具的结果(多工具 skills 内容相同)
|
|
1016
|
+
const existing = allChanges.find(c => c.skillName === name);
|
|
1017
|
+
if (!existing) {
|
|
1018
|
+
allChanges.push({ toolKey, skillName: name, files: changedFiles });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// 3. 展示结果
|
|
1025
|
+
if (allChanges.length === 0) {
|
|
1026
|
+
console.log(fmt('green', ' ✓ 未检测到技能修改,所有技能与包版本一致。'));
|
|
1027
|
+
console.log('');
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
console.log(` 检测到 ${fmt('bold', String(allChanges.length))} 个技能有修改:`);
|
|
1032
|
+
console.log('');
|
|
1033
|
+
|
|
1034
|
+
for (let idx = 0; idx < allChanges.length; idx++) {
|
|
1035
|
+
const change = allChanges[idx];
|
|
1036
|
+
console.log(` ${fmt('bold', String(idx + 1) + '.')} ${fmt('cyan', change.skillName)}`);
|
|
1037
|
+
for (const file of change.files) {
|
|
1038
|
+
const statusLabel = file.status === 'added' ? fmt('green', '新增文件') : fmt('yellow', '修改文件');
|
|
1039
|
+
console.log(` ${statusLabel}: ${file.relPath} (${fmt('green', '+' + file.added)} 行, ${fmt('red', '-' + file.removed)} 行)`);
|
|
1040
|
+
}
|
|
1041
|
+
console.log('');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// 4. 展示 diff 详情
|
|
1045
|
+
const allDiffParts = [];
|
|
1046
|
+
|
|
1047
|
+
for (const change of allChanges) {
|
|
1048
|
+
for (const file of change.files) {
|
|
1049
|
+
const header = `${change.skillName}/${file.relPath}`;
|
|
1050
|
+
console.log(fmt('bold', '─'.repeat(Math.min(50, header.length + 10))));
|
|
1051
|
+
console.log(`📋 ${fmt('bold', header)} 的变更:`);
|
|
1052
|
+
console.log(fmt('bold', '─'.repeat(Math.min(50, header.length + 10))));
|
|
1053
|
+
|
|
1054
|
+
// 着色 diff 输出
|
|
1055
|
+
for (const line of file.diff.split('\n')) {
|
|
1056
|
+
if (line.startsWith('+++') || line.startsWith('---')) {
|
|
1057
|
+
console.log(fmt('bold', line));
|
|
1058
|
+
} else if (line.startsWith('+')) {
|
|
1059
|
+
console.log(fmt('green', line));
|
|
1060
|
+
} else if (line.startsWith('-')) {
|
|
1061
|
+
console.log(fmt('red', line));
|
|
1062
|
+
} else if (line.startsWith('@@')) {
|
|
1063
|
+
console.log(fmt('cyan', line));
|
|
1064
|
+
} else {
|
|
1065
|
+
console.log(line);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
console.log('');
|
|
1069
|
+
|
|
1070
|
+
allDiffParts.push(`# ${header}\n${file.diff}`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const allDiffText = allDiffParts.join('\n\n');
|
|
1075
|
+
|
|
1076
|
+
// 5. 提交 Issue 或提示
|
|
1077
|
+
if (doSubmit) {
|
|
1078
|
+
console.log(fmt('bold', '📤 正在提交 GitHub Issue...'));
|
|
1079
|
+
console.log('');
|
|
1080
|
+
|
|
1081
|
+
if (!isGhAvailable()) {
|
|
1082
|
+
console.log(fmt('yellow', '⚠ 未检测到 gh CLI,无法自动提交 Issue。'));
|
|
1083
|
+
console.log(` 安装方法: ${fmt('bold', 'https://cli.github.com/')}`);
|
|
1084
|
+
console.log('');
|
|
1085
|
+
console.log(fmt('bold', '📋 请手动复制以下内容到 GitHub Issue:'));
|
|
1086
|
+
console.log('');
|
|
1087
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1088
|
+
console.log(`标题: [sync-back] 技能改进:${allChanges.map(c => c.skillName).join(', ')}`);
|
|
1089
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1090
|
+
console.log(allDiffText);
|
|
1091
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1092
|
+
console.log('');
|
|
1093
|
+
console.log(`提交到: ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1094
|
+
} else {
|
|
1095
|
+
const issueUrl = submitGitHubIssue(allChanges, allDiffText);
|
|
1096
|
+
if (issueUrl) {
|
|
1097
|
+
console.log(fmt('green', fmt('bold', '✅ Issue 已创建!')));
|
|
1098
|
+
console.log(` ${fmt('bold', issueUrl)}`);
|
|
1099
|
+
} else {
|
|
1100
|
+
console.log(fmt('red', '✗ Issue 创建失败,请检查 gh 认证状态(gh auth status)'));
|
|
1101
|
+
console.log('');
|
|
1102
|
+
console.log(fmt('bold', '📋 请手动复制上方 diff 到 GitHub Issue:'));
|
|
1103
|
+
console.log(` ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
} else {
|
|
1107
|
+
console.log(fmt('cyan', '💡 提交方式:'));
|
|
1108
|
+
if (allChanges.length === 1) {
|
|
1109
|
+
console.log(` → 运行 ${fmt('bold', hintCmd(`sync-back --skill ${allChanges[0].skillName} --submit`))}`);
|
|
1110
|
+
} else {
|
|
1111
|
+
console.log(` → 运行 ${fmt('bold', hintCmd('sync-back --submit'))}`);
|
|
1112
|
+
}
|
|
1113
|
+
console.log(` → 或手动复制上方 diff 到 ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1114
|
+
}
|
|
1115
|
+
console.log('');
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// ── 数据库配置初始化 ──────────────────────────────────────────────────────
|
|
1119
|
+
function runConfig() {
|
|
1120
|
+
if (!process.stdin.isTTY) {
|
|
1121
|
+
console.error(fmt('red', '错误:config 命令需要交互式终端'));
|
|
1122
|
+
process.exit(1);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const configPath = path.join(targetDir, '.claude', 'mysql-config.json');
|
|
1126
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1127
|
+
|
|
1128
|
+
const ask = (question) => new Promise((resolve) => {
|
|
1129
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
const ENV_DEFAULTS = {
|
|
1133
|
+
local: { host: '127.0.0.1', user: 'root', desc: '本地开发环境' },
|
|
1134
|
+
dev: { host: '', user: '', desc: '开发测试环境' },
|
|
1135
|
+
test: { host: '', user: '', desc: '测试环境' },
|
|
1136
|
+
prod: { host: '', user: '', desc: '生产环境' },
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
(async () => {
|
|
1140
|
+
try {
|
|
1141
|
+
// 1. 检测已有配置
|
|
1142
|
+
if (fs.existsSync(configPath)) {
|
|
1143
|
+
console.log(fmt('yellow', `⚠ 配置文件已存在:${configPath}`));
|
|
1144
|
+
const overwrite = await ask(fmt('bold', '是否重新配置?[y/N]: '));
|
|
1145
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
1146
|
+
console.log('已取消。');
|
|
1147
|
+
rl.close();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
console.log('');
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// 2. 选择环境
|
|
1154
|
+
console.log(fmt('cyan', '请选择要配置的数据库环境(多选,用逗号分隔):'));
|
|
1155
|
+
console.log('');
|
|
1156
|
+
console.log(` ${fmt('bold', '1')}) local — 本地开发环境`);
|
|
1157
|
+
console.log(` ${fmt('bold', '2')}) dev — 开发测试环境`);
|
|
1158
|
+
console.log(` ${fmt('bold', '3')}) test — 测试环境`);
|
|
1159
|
+
console.log(` ${fmt('bold', '4')}) prod — 生产环境`);
|
|
1160
|
+
console.log('');
|
|
1161
|
+
const envAnswer = await ask(fmt('bold', '请输入选项(如 1,2 或 1-3): '));
|
|
1162
|
+
|
|
1163
|
+
// 解析选择
|
|
1164
|
+
const envNames = ['local', 'dev', 'test', 'prod'];
|
|
1165
|
+
const selected = new Set();
|
|
1166
|
+
for (const part of envAnswer.split(',')) {
|
|
1167
|
+
const trimmed = part.trim();
|
|
1168
|
+
const rangeMatch = trimmed.match(/^(\d)-(\d)$/);
|
|
1169
|
+
if (rangeMatch) {
|
|
1170
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
1171
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
1172
|
+
for (let n = start; n <= end; n++) {
|
|
1173
|
+
if (n >= 1 && n <= 4) selected.add(envNames[n - 1]);
|
|
1174
|
+
}
|
|
1175
|
+
} else {
|
|
1176
|
+
const n = parseInt(trimmed, 10);
|
|
1177
|
+
if (n >= 1 && n <= 4) selected.add(envNames[n - 1]);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const selectedEnvs = [...selected];
|
|
1182
|
+
if (selectedEnvs.length === 0) {
|
|
1183
|
+
console.error(fmt('red', '未选择任何环境,退出。'));
|
|
1184
|
+
rl.close();
|
|
1185
|
+
process.exit(1);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
console.log('');
|
|
1189
|
+
console.log(fmt('green', `已选择环境:${selectedEnvs.join(', ')}`));
|
|
1190
|
+
console.log('');
|
|
1191
|
+
|
|
1192
|
+
// 3. 收集每个环境的配置
|
|
1193
|
+
const environments = {};
|
|
1194
|
+
for (const env of selectedEnvs) {
|
|
1195
|
+
const defaults = ENV_DEFAULTS[env];
|
|
1196
|
+
console.log(fmt('cyan', `── ${env} 环境配置 ──`));
|
|
1197
|
+
|
|
1198
|
+
const host = await ask(` host [${defaults.host || '无默认'}]: `) || defaults.host;
|
|
1199
|
+
const port = await ask(' port [3306]: ') || '3306';
|
|
1200
|
+
const user = await ask(` user [${defaults.user || '无默认'}]: `) || defaults.user;
|
|
1201
|
+
const password = await ask(' password: ');
|
|
1202
|
+
const desc = await ask(` 描述 [${defaults.desc}]: `) || defaults.desc;
|
|
1203
|
+
console.log('');
|
|
1204
|
+
|
|
1205
|
+
if (!host) {
|
|
1206
|
+
console.error(fmt('red', `错误:${env} 环境的 host 不能为空`));
|
|
1207
|
+
rl.close();
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
if (!user) {
|
|
1211
|
+
console.error(fmt('red', `错误:${env} 环境的 user 不能为空`));
|
|
1212
|
+
rl.close();
|
|
1213
|
+
process.exit(1);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
environments[env] = {
|
|
1217
|
+
host,
|
|
1218
|
+
port: parseInt(port, 10),
|
|
1219
|
+
user,
|
|
1220
|
+
password,
|
|
1221
|
+
description: desc,
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// 4. 选择默认环境
|
|
1226
|
+
let defaultEnv = selectedEnvs[0];
|
|
1227
|
+
if (selectedEnvs.length > 1) {
|
|
1228
|
+
console.log(fmt('cyan', '请选择默认环境:'));
|
|
1229
|
+
selectedEnvs.forEach((env, i) => {
|
|
1230
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${env}`);
|
|
1231
|
+
});
|
|
1232
|
+
const defaultAnswer = await ask(fmt('bold', `请输入选项 [1-${selectedEnvs.length}]: `));
|
|
1233
|
+
const idx = parseInt(defaultAnswer, 10) - 1;
|
|
1234
|
+
if (idx >= 0 && idx < selectedEnvs.length) {
|
|
1235
|
+
defaultEnv = selectedEnvs[idx];
|
|
1236
|
+
}
|
|
1237
|
+
console.log('');
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// 5. 写入配置文件
|
|
1241
|
+
const config = { defaultEnv, environments };
|
|
1242
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
1243
|
+
if (!fs.existsSync(claudeDir)) {
|
|
1244
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
1245
|
+
}
|
|
1246
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
1247
|
+
console.log(fmt('green', `✔ 配置已写入:${configPath}`));
|
|
1248
|
+
|
|
1249
|
+
// 6. 确保 .gitignore 包含该文件
|
|
1250
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
1251
|
+
const ignoreEntry = '.claude/mysql-config.json';
|
|
1252
|
+
let needAppend = true;
|
|
1253
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1254
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1255
|
+
if (content.split('\n').some(line => line.trim() === ignoreEntry)) {
|
|
1256
|
+
needAppend = false;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (needAppend) {
|
|
1260
|
+
const separator = fs.existsSync(gitignorePath) ? '\n' : '';
|
|
1261
|
+
fs.appendFileSync(gitignorePath, `${separator}${ignoreEntry}\n`, 'utf-8');
|
|
1262
|
+
console.log(fmt('green', `✔ 已添加 ${ignoreEntry} 到 .gitignore`));
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
console.log('');
|
|
1266
|
+
console.log(fmt('green', '数据库配置初始化完成!'));
|
|
1267
|
+
console.log(`使用 ${fmt('bold', 'mysql-debug')} 技能时将自动读取此配置。`);
|
|
1268
|
+
} finally {
|
|
1269
|
+
rl.close();
|
|
1270
|
+
}
|
|
1271
|
+
})();
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// ── MCP 服务器管理 ──────────────────────────────────────────────────────────
|
|
1275
|
+
|
|
1276
|
+
const MCP_REGISTRY = [
|
|
1277
|
+
{
|
|
1278
|
+
name: 'sequential-thinking',
|
|
1279
|
+
package: '@modelcontextprotocol/server-sequential-thinking',
|
|
1280
|
+
description: '链式推理 — 深度分析、仔细思考、全面评估时使用',
|
|
1281
|
+
env: {},
|
|
1282
|
+
recommended: true,
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
name: 'context7',
|
|
1286
|
+
package: '@upstash/context7-mcp',
|
|
1287
|
+
description: '官方文档查询 — 最佳实践、官方文档、标准写法时使用',
|
|
1288
|
+
env: {},
|
|
1289
|
+
recommended: true,
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
name: 'github',
|
|
1293
|
+
package: '@modelcontextprotocol/server-github',
|
|
1294
|
+
description: 'GitHub 集成 — 查询 Issues、PR、仓库信息',
|
|
1295
|
+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: '${GITHUB_TOKEN}' },
|
|
1296
|
+
recommended: true,
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
name: 'filesystem',
|
|
1300
|
+
package: '@modelcontextprotocol/server-filesystem',
|
|
1301
|
+
description: '文件系统访问 — 读写项目外的文件',
|
|
1302
|
+
env: {},
|
|
1303
|
+
recommended: false,
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
name: 'fetch',
|
|
1307
|
+
package: '@anthropic-ai/mcp-fetch',
|
|
1308
|
+
description: '网页抓取 — 获取网页内容',
|
|
1309
|
+
env: {},
|
|
1310
|
+
recommended: false,
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
name: 'yunxiao',
|
|
1314
|
+
package: 'alibabacloud-devops-mcp-server',
|
|
1315
|
+
description: '阿里云效 — DevOps 项目管理、代码仓库、流水线集成',
|
|
1316
|
+
env: { YUNXIAO_ACCESS_TOKEN: '<YOUR_TOKEN>' },
|
|
1317
|
+
recommended: false,
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
name: 'yuque',
|
|
1321
|
+
package: 'yuque-mcp',
|
|
1322
|
+
description: '语雀 — 知识库文档读写、搜索、团队协作',
|
|
1323
|
+
env: { YUQUE_TOKEN: '<YOUR_TOKEN>' },
|
|
1324
|
+
recommended: false,
|
|
1325
|
+
},
|
|
1326
|
+
];
|
|
1327
|
+
|
|
1328
|
+
/** MCP 配置文件路径映射 */
|
|
1329
|
+
const MCP_CONFIG_PATHS = {
|
|
1330
|
+
claude: { file: '.claude/settings.json', key: 'mcpServers' },
|
|
1331
|
+
cursor: { file: '.cursor/mcp.json', key: 'mcpServers' },
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
/** 检测项目中已有的工具配置目录 */
|
|
1335
|
+
function detectMcpTools() {
|
|
1336
|
+
const tools = [];
|
|
1337
|
+
if (isRealDir(path.join(targetDir, '.claude'))) tools.push('claude');
|
|
1338
|
+
if (isRealDir(path.join(targetDir, '.cursor'))) tools.push('cursor');
|
|
1339
|
+
return tools;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/** 读取指定工具的 MCP 已配置服务器 */
|
|
1343
|
+
function getMcpServers(toolName) {
|
|
1344
|
+
const config = MCP_CONFIG_PATHS[toolName];
|
|
1345
|
+
if (!config) return {};
|
|
1346
|
+
const filePath = path.join(targetDir, config.file);
|
|
1347
|
+
try {
|
|
1348
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1349
|
+
return data[config.key] || {};
|
|
1350
|
+
} catch { return {}; }
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/** 写入指定工具的 MCP 配置(保留文件其他字段) */
|
|
1354
|
+
function setMcpServers(toolName, mcpServers) {
|
|
1355
|
+
const config = MCP_CONFIG_PATHS[toolName];
|
|
1356
|
+
if (!config) return;
|
|
1357
|
+
const filePath = path.join(targetDir, config.file);
|
|
1358
|
+
let data = {};
|
|
1359
|
+
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { /* 新建 */ }
|
|
1360
|
+
data[config.key] = mcpServers;
|
|
1361
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1362
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/** 构建单个 MCP 服务器的配置对象 */
|
|
1366
|
+
function buildMcpServerConfig(entry) {
|
|
1367
|
+
const config = {
|
|
1368
|
+
command: 'npx',
|
|
1369
|
+
args: ['-y', entry.package],
|
|
1370
|
+
};
|
|
1371
|
+
if (Object.keys(entry.env).length > 0) {
|
|
1372
|
+
config.env = { ...entry.env };
|
|
1373
|
+
}
|
|
1374
|
+
return config;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/** 获取所有工具中已安装的 MCP 服务器名称集合 */
|
|
1378
|
+
function getInstalledMcpNames(tools) {
|
|
1379
|
+
const names = new Set();
|
|
1380
|
+
for (const t of tools) {
|
|
1381
|
+
const servers = getMcpServers(t);
|
|
1382
|
+
for (const name of Object.keys(servers)) names.add(name);
|
|
1383
|
+
}
|
|
1384
|
+
return names;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function runMcp() {
|
|
1388
|
+
if (!process.stdin.isTTY) {
|
|
1389
|
+
console.error(fmt('red', '错误:mcp 命令需要交互式终端'));
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const tools = detectMcpTools();
|
|
1394
|
+
if (tools.length === 0) {
|
|
1395
|
+
console.log(fmt('yellow', '⚠ 当前目录未检测到 .claude/ 或 .cursor/ 配置目录。'));
|
|
1396
|
+
console.log(` 请先运行: ${fmt('bold', hintCmd('init --tool claude'))}`);
|
|
1397
|
+
console.log('');
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
console.log(` 检测到工具: ${fmt('bold', tools.join(', '))}`);
|
|
1402
|
+
console.log('');
|
|
1403
|
+
|
|
1404
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1405
|
+
const ask = (question) => new Promise((resolve) => {
|
|
1406
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
(async () => {
|
|
1410
|
+
try {
|
|
1411
|
+
console.log(fmt('cyan', '请选择 MCP 操作:'));
|
|
1412
|
+
console.log('');
|
|
1413
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', '安装 MCP 服务器')} — 从预置列表选择并安装到配置`);
|
|
1414
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('red', '卸载 MCP 服务器')} — 从已安装列表中移除`);
|
|
1415
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('cyan', '查看状态')} — 检查已配置的 MCP 服务器`);
|
|
1416
|
+
console.log(` ${fmt('bold', '4')}) ${fmt('yellow', '一键推荐安装')} — 安装所有推荐的 MCP 服务器`);
|
|
1417
|
+
console.log('');
|
|
1418
|
+
const action = await ask(fmt('bold', '请输入选项 [1-4]: '));
|
|
1419
|
+
console.log('');
|
|
1420
|
+
|
|
1421
|
+
switch (action) {
|
|
1422
|
+
case '1': await mcpInstall(tools, ask); break;
|
|
1423
|
+
case '2': await mcpUninstall(tools, ask); break;
|
|
1424
|
+
case '3': mcpStatus(tools); break;
|
|
1425
|
+
case '4': await mcpRecommend(tools, ask); break;
|
|
1426
|
+
default:
|
|
1427
|
+
console.error(fmt('red', '无效选项,退出。'));
|
|
1428
|
+
process.exit(1);
|
|
1429
|
+
}
|
|
1430
|
+
} finally {
|
|
1431
|
+
rl.close();
|
|
1432
|
+
}
|
|
1433
|
+
})();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/** 安装 MCP 服务器 */
|
|
1437
|
+
async function mcpInstall(tools, ask) {
|
|
1438
|
+
const installed = getInstalledMcpNames(tools);
|
|
1439
|
+
|
|
1440
|
+
console.log(fmt('cyan', '可用的 MCP 服务器:'));
|
|
1441
|
+
console.log('');
|
|
1442
|
+
for (let i = 0; i < MCP_REGISTRY.length; i++) {
|
|
1443
|
+
const entry = MCP_REGISTRY[i];
|
|
1444
|
+
const tags = [];
|
|
1445
|
+
if (installed.has(entry.name)) tags.push(fmt('green', '[已安装]'));
|
|
1446
|
+
if (entry.recommended) tags.push(fmt('yellow', '[推荐]'));
|
|
1447
|
+
const tagStr = tags.length > 0 ? ' ' + tags.join(' ') : '';
|
|
1448
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${fmt('bold', entry.name)}${tagStr}`);
|
|
1449
|
+
console.log(` ${entry.description}`);
|
|
1450
|
+
}
|
|
1451
|
+
console.log('');
|
|
1452
|
+
const answer = await ask(fmt('bold', '请选择要安装的服务器(逗号分隔,如 1,2,3): '));
|
|
1453
|
+
const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < MCP_REGISTRY.length);
|
|
1454
|
+
|
|
1455
|
+
if (indices.length === 0) {
|
|
1456
|
+
console.log(fmt('yellow', '未选择任何服务器,退出。'));
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const selected = indices.map(i => MCP_REGISTRY[i]);
|
|
1461
|
+
console.log('');
|
|
1462
|
+
|
|
1463
|
+
// 处理需要 env 的服务器
|
|
1464
|
+
for (const entry of selected) {
|
|
1465
|
+
if (Object.keys(entry.env).length > 0) {
|
|
1466
|
+
console.log(fmt('cyan', `── ${entry.name} 环境变量配置 ──`));
|
|
1467
|
+
for (const [key, defaultVal] of Object.entries(entry.env)) {
|
|
1468
|
+
const val = await ask(` ${key} [${defaultVal}]: `);
|
|
1469
|
+
if (val) entry.env[key] = val;
|
|
1470
|
+
}
|
|
1471
|
+
console.log('');
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// 写入所有检测到的工具配置
|
|
1476
|
+
for (const toolName of tools) {
|
|
1477
|
+
const servers = getMcpServers(toolName);
|
|
1478
|
+
for (const entry of selected) {
|
|
1479
|
+
servers[entry.name] = buildMcpServerConfig(entry);
|
|
1480
|
+
}
|
|
1481
|
+
setMcpServers(toolName, servers);
|
|
1482
|
+
console.log(` ${fmt('green', '✓')} ${toolName}: 已写入 ${selected.map(e => e.name).join(', ')}`);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
console.log('');
|
|
1486
|
+
console.log(fmt('green', fmt('bold', `✅ 已安装 ${selected.length} 个 MCP 服务器!`)));
|
|
1487
|
+
console.log('');
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/** 卸载 MCP 服务器 */
|
|
1491
|
+
async function mcpUninstall(tools, ask) {
|
|
1492
|
+
// 收集所有已安装的服务器(合并去重)
|
|
1493
|
+
const allServers = new Map(); // name → 出现在哪些工具中
|
|
1494
|
+
for (const toolName of tools) {
|
|
1495
|
+
const servers = getMcpServers(toolName);
|
|
1496
|
+
for (const name of Object.keys(servers)) {
|
|
1497
|
+
if (!allServers.has(name)) allServers.set(name, []);
|
|
1498
|
+
allServers.get(name).push(toolName);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
if (allServers.size === 0) {
|
|
1503
|
+
console.log(fmt('yellow', ' 当前没有已安装的 MCP 服务器。'));
|
|
1504
|
+
console.log(` 运行 ${fmt('bold', hintCmd('mcp'))} 安装服务器。`);
|
|
1505
|
+
console.log('');
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const serverNames = [...allServers.keys()];
|
|
1510
|
+
console.log(fmt('cyan', '已安装的 MCP 服务器:'));
|
|
1511
|
+
console.log('');
|
|
1512
|
+
for (let i = 0; i < serverNames.length; i++) {
|
|
1513
|
+
const name = serverNames[i];
|
|
1514
|
+
const toolList = allServers.get(name).join(', ');
|
|
1515
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${fmt('bold', name)} ${fmt('magenta', `(${toolList})`)}`);
|
|
1516
|
+
}
|
|
1517
|
+
console.log('');
|
|
1518
|
+
const answer = await ask(fmt('bold', '请选择要卸载的服务器(逗号分隔): '));
|
|
1519
|
+
const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < serverNames.length);
|
|
1520
|
+
|
|
1521
|
+
if (indices.length === 0) {
|
|
1522
|
+
console.log(fmt('yellow', '未选择任何服务器,退出。'));
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const toRemove = indices.map(i => serverNames[i]);
|
|
1527
|
+
console.log('');
|
|
1528
|
+
|
|
1529
|
+
for (const toolName of tools) {
|
|
1530
|
+
const servers = getMcpServers(toolName);
|
|
1531
|
+
let removed = 0;
|
|
1532
|
+
for (const name of toRemove) {
|
|
1533
|
+
if (name in servers) {
|
|
1534
|
+
delete servers[name];
|
|
1535
|
+
removed++;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (removed > 0) {
|
|
1539
|
+
setMcpServers(toolName, servers);
|
|
1540
|
+
console.log(` ${fmt('green', '✓')} ${toolName}: 已移除 ${toRemove.filter(n => !servers[n]).join(', ')}`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
console.log('');
|
|
1545
|
+
console.log(fmt('green', fmt('bold', `✅ 已卸载 ${toRemove.length} 个 MCP 服务器!`)));
|
|
1546
|
+
console.log('');
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/** 查看 MCP 状态 */
|
|
1550
|
+
function mcpStatus(tools) {
|
|
1551
|
+
let hasAny = false;
|
|
1552
|
+
|
|
1553
|
+
for (const toolName of tools) {
|
|
1554
|
+
const servers = getMcpServers(toolName);
|
|
1555
|
+
const names = Object.keys(servers);
|
|
1556
|
+
|
|
1557
|
+
console.log(fmt('cyan', `[${toolName}]`) + ` ${MCP_CONFIG_PATHS[toolName].file}`);
|
|
1558
|
+
|
|
1559
|
+
if (names.length === 0) {
|
|
1560
|
+
console.log(` ${fmt('yellow', '(无已安装的 MCP 服务器)')}`);
|
|
1561
|
+
} else {
|
|
1562
|
+
hasAny = true;
|
|
1563
|
+
for (const name of names) {
|
|
1564
|
+
const srv = servers[name];
|
|
1565
|
+
const pkg = (srv.args || []).find(a => a.startsWith('@')) || '—';
|
|
1566
|
+
const envKeys = srv.env ? Object.keys(srv.env).join(', ') : '—';
|
|
1567
|
+
console.log(` ${fmt('green', '●')} ${fmt('bold', name)}`);
|
|
1568
|
+
console.log(` 包: ${pkg} | 环境变量: ${envKeys}`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
console.log('');
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if (!hasAny) {
|
|
1575
|
+
console.log(fmt('yellow', '💡 未安装任何 MCP 服务器。'));
|
|
1576
|
+
console.log(` 运行 ${fmt('bold', hintCmd('mcp'))} 开始安装。`);
|
|
1577
|
+
console.log('');
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** 一键推荐安装 */
|
|
1582
|
+
async function mcpRecommend(tools, ask) {
|
|
1583
|
+
const installed = getInstalledMcpNames(tools);
|
|
1584
|
+
const toInstall = MCP_REGISTRY.filter(e => e.recommended && !installed.has(e.name));
|
|
1585
|
+
|
|
1586
|
+
if (toInstall.length === 0) {
|
|
1587
|
+
console.log(fmt('green', ' ✓ 所有推荐的 MCP 服务器已安装!'));
|
|
1588
|
+
console.log('');
|
|
1589
|
+
mcpStatus(tools);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
console.log(fmt('cyan', '将安装以下推荐服务器:'));
|
|
1594
|
+
console.log('');
|
|
1595
|
+
for (const entry of toInstall) {
|
|
1596
|
+
console.log(` ${fmt('green', '●')} ${fmt('bold', entry.name)} — ${entry.description}`);
|
|
1597
|
+
}
|
|
1598
|
+
console.log('');
|
|
1599
|
+
|
|
1600
|
+
// 处理需要 env 的服务器
|
|
1601
|
+
for (const entry of toInstall) {
|
|
1602
|
+
if (Object.keys(entry.env).length > 0) {
|
|
1603
|
+
console.log(fmt('cyan', `── ${entry.name} 环境变量配置 ──`));
|
|
1604
|
+
for (const [key, defaultVal] of Object.entries(entry.env)) {
|
|
1605
|
+
const val = await ask(` ${key} [${defaultVal}]: `);
|
|
1606
|
+
if (val) entry.env[key] = val;
|
|
1607
|
+
}
|
|
1608
|
+
console.log('');
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// 写入配置
|
|
1613
|
+
for (const toolName of tools) {
|
|
1614
|
+
const servers = getMcpServers(toolName);
|
|
1615
|
+
for (const entry of toInstall) {
|
|
1616
|
+
servers[entry.name] = buildMcpServerConfig(entry);
|
|
1617
|
+
}
|
|
1618
|
+
setMcpServers(toolName, servers);
|
|
1619
|
+
console.log(` ${fmt('green', '✓')} ${toolName}: 已写入 ${toInstall.map(e => e.name).join(', ')}`);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
console.log('');
|
|
1623
|
+
console.log(fmt('green', fmt('bold', `✅ 已安装 ${toInstall.length} 个推荐 MCP 服务器!`)));
|
|
1624
|
+
console.log('');
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// ── 工具选择菜单(init 用)─────────────────────────────────────────────────
|
|
1628
|
+
function showToolMenu() {
|
|
687
1629
|
if (!process.stdin.isTTY) {
|
|
688
1630
|
console.error(fmt('red', '错误:非交互环境下必须指定 --tool 参数'));
|
|
689
|
-
console.error(` 示例: ${fmt('bold', hintCmd('--tool claude'))}`);
|
|
1631
|
+
console.error(` 示例: ${fmt('bold', hintCmd('init --tool claude'))}`);
|
|
690
1632
|
process.exit(1);
|
|
691
1633
|
}
|
|
692
1634
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
693
|
-
console.log(fmt('cyan', '
|
|
1635
|
+
console.log(fmt('cyan', '请选择要初始化的工具:'));
|
|
694
1636
|
console.log('');
|
|
695
|
-
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} —
|
|
696
|
-
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} —
|
|
697
|
-
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} —
|
|
698
|
-
console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex
|
|
699
|
-
console.log(` ${fmt('bold', '5')}) ${fmt('magenta', '全局安装')} — 安装到 ~/.claude / ~/.cursor,对所有项目生效`);
|
|
1637
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'Claude Code')} — .claude/ + CLAUDE.md`);
|
|
1638
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', 'Cursor')} — .cursor/(Skills + Agents)`);
|
|
1639
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', 'OpenAI Codex')} — .codex/ + AGENTS.md`);
|
|
1640
|
+
console.log(` ${fmt('bold', '4')}) ${fmt('blue', '全部工具')} — 同时初始化 Claude + Cursor + Codex`);
|
|
700
1641
|
console.log('');
|
|
701
|
-
rl.question(fmt('bold', '请输入选项 [1-
|
|
1642
|
+
rl.question(fmt('bold', '请输入选项 [1-4]: '), (answer) => {
|
|
702
1643
|
rl.close();
|
|
703
1644
|
const map = { '1': 'claude', '2': 'cursor', '3': 'codex', '4': 'all' };
|
|
704
1645
|
const selected = map[answer.trim()];
|
|
705
1646
|
console.log('');
|
|
706
|
-
if (
|
|
707
|
-
runGlobal('all');
|
|
708
|
-
} else if (selected) {
|
|
1647
|
+
if (selected) {
|
|
709
1648
|
run(selected);
|
|
710
1649
|
} else {
|
|
711
1650
|
console.error(fmt('red', '无效选项,退出。'));
|
|
@@ -713,3 +1652,76 @@ if (command === 'update') {
|
|
|
713
1652
|
}
|
|
714
1653
|
});
|
|
715
1654
|
}
|
|
1655
|
+
|
|
1656
|
+
// ── 主菜单(无命令时展示)──────────────────────────────────────────────────
|
|
1657
|
+
function showMainMenu() {
|
|
1658
|
+
if (!process.stdin.isTTY) {
|
|
1659
|
+
console.error(fmt('red', '错误:非交互环境下必须指定命令'));
|
|
1660
|
+
console.error(` 示例: ${fmt('bold', hintCmd('init --tool claude'))}`);
|
|
1661
|
+
console.error(` 运行 ${fmt('bold', hintCmd('--help'))} 查看所有命令`);
|
|
1662
|
+
process.exit(1);
|
|
1663
|
+
}
|
|
1664
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1665
|
+
console.log(fmt('cyan', '请选择操作:'));
|
|
1666
|
+
console.log('');
|
|
1667
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', '初始化')} — 安装 AI 工具配置到当前项目`);
|
|
1668
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', '更新')} — 更新已安装的框架文件`);
|
|
1669
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', '全局安装')} — 安装到 ~/.claude 等,对所有项目生效`);
|
|
1670
|
+
console.log(` ${fmt('bold', '4')}) ${fmt('magenta', '技能同步反馈')} — 对比本地技能修改,生成 diff`);
|
|
1671
|
+
console.log(` ${fmt('bold', '5')}) ${fmt('blue', '数据库配置')} — 初始化 mysql-config.json(数据库连接信息)`);
|
|
1672
|
+
console.log(` ${fmt('bold', '6')}) ${fmt('green', 'MCP 管理')} — MCP 服务器安装/卸载/状态检查`);
|
|
1673
|
+
console.log('');
|
|
1674
|
+
rl.question(fmt('bold', '请输入选项 [1-6]: '), (answer) => {
|
|
1675
|
+
rl.close();
|
|
1676
|
+
console.log('');
|
|
1677
|
+
switch (answer.trim()) {
|
|
1678
|
+
case '1':
|
|
1679
|
+
showToolMenu();
|
|
1680
|
+
break;
|
|
1681
|
+
case '2':
|
|
1682
|
+
runUpdate(tool);
|
|
1683
|
+
break;
|
|
1684
|
+
case '3':
|
|
1685
|
+
runGlobal(tool || 'all');
|
|
1686
|
+
break;
|
|
1687
|
+
case '4':
|
|
1688
|
+
runSyncBack(tool, skillFilter, submitIssue);
|
|
1689
|
+
break;
|
|
1690
|
+
case '5':
|
|
1691
|
+
runConfig();
|
|
1692
|
+
break;
|
|
1693
|
+
case '6':
|
|
1694
|
+
runMcp();
|
|
1695
|
+
break;
|
|
1696
|
+
default:
|
|
1697
|
+
console.error(fmt('red', '无效选项,退出。'));
|
|
1698
|
+
process.exit(1);
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// ── 主入口 ────────────────────────────────────────────────────────────────
|
|
1704
|
+
if (command === 'init') {
|
|
1705
|
+
// 显式 init 子命令
|
|
1706
|
+
if (tool) {
|
|
1707
|
+
run(tool);
|
|
1708
|
+
} else {
|
|
1709
|
+
showToolMenu();
|
|
1710
|
+
}
|
|
1711
|
+
} else if (command === 'update') {
|
|
1712
|
+
runUpdate(tool);
|
|
1713
|
+
} else if (command === 'global') {
|
|
1714
|
+
runGlobal(tool);
|
|
1715
|
+
} else if (command === 'sync-back') {
|
|
1716
|
+
runSyncBack(tool, skillFilter, submitIssue);
|
|
1717
|
+
} else if (command === 'config') {
|
|
1718
|
+
runConfig();
|
|
1719
|
+
} else if (command === 'mcp') {
|
|
1720
|
+
runMcp();
|
|
1721
|
+
} else if (tool) {
|
|
1722
|
+
// 向后兼容:无 command 但有 --tool,当作 init 执行
|
|
1723
|
+
run(tool);
|
|
1724
|
+
} else {
|
|
1725
|
+
// 无命令无参数:显示主菜单
|
|
1726
|
+
showMainMenu();
|
|
1727
|
+
}
|