ai-engineering-init 1.15.0 → 1.16.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/bin/index.js +445 -121
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -52,6 +52,7 @@ let targetDir = process.cwd();
|
|
|
52
52
|
let force = false;
|
|
53
53
|
let skillFilter = ''; // sync-back --skill <名称>
|
|
54
54
|
let submitIssue = false; // sync-back --submit
|
|
55
|
+
let configType = ''; // config --type <mysql|loki|all>
|
|
55
56
|
|
|
56
57
|
for (let i = 0; i < args.length; i++) {
|
|
57
58
|
const arg = args[i];
|
|
@@ -101,6 +102,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
101
102
|
case '--submit':
|
|
102
103
|
submitIssue = true;
|
|
103
104
|
break;
|
|
105
|
+
case '--type':
|
|
106
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
107
|
+
console.error(fmt('red', `错误:${arg} 需要一个值(mysql | loki | all)`));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
configType = args[++i];
|
|
111
|
+
break;
|
|
104
112
|
case '--help': case '-h':
|
|
105
113
|
printHelp();
|
|
106
114
|
process.exit(0);
|
|
@@ -122,7 +130,7 @@ function printHelp() {
|
|
|
122
130
|
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)`);
|
|
123
131
|
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor 等,对所有项目生效`);
|
|
124
132
|
console.log(` ${fmt('bold', 'sync-back')} 对比本地技能修改,生成 diff 或提交 GitHub Issue`);
|
|
125
|
-
console.log(` ${fmt('bold', 'config')}
|
|
133
|
+
console.log(` ${fmt('bold', 'config')} 初始化环境配置(数据库连接 / Loki 日志 / 全部)`);
|
|
126
134
|
console.log(` ${fmt('bold', 'mcp')} MCP 服务器管理(安装/卸载/状态检查)\n`);
|
|
127
135
|
console.log(`无命令时显示交互式主菜单。\n`);
|
|
128
136
|
console.log('选项:');
|
|
@@ -131,6 +139,7 @@ function printHelp() {
|
|
|
131
139
|
console.log(' --force, -f 强制覆盖(init 时覆盖已有文件;update/global 时同时更新保留文件)');
|
|
132
140
|
console.log(' --skill, -s <技能> sync-back 时只对比指定技能');
|
|
133
141
|
console.log(' --submit sync-back 时自动创建 GitHub Issue(需要 gh CLI)');
|
|
142
|
+
console.log(' --type <类型> config 时指定配置类型: mysql | loki | all');
|
|
134
143
|
console.log(' --help, -h 显示此帮助\n');
|
|
135
144
|
console.log('示例:');
|
|
136
145
|
console.log(' npx ai-engineering-init --tool claude');
|
|
@@ -144,6 +153,10 @@ function printHelp() {
|
|
|
144
153
|
console.log(' npx ai-engineering-init sync-back --tool claude # 只扫描 Claude');
|
|
145
154
|
console.log(' npx ai-engineering-init sync-back --skill bug-detective # 只对比指定技能');
|
|
146
155
|
console.log(' npx ai-engineering-init sync-back --skill bug-detective --submit # 提交 Issue');
|
|
156
|
+
console.log(' npx ai-engineering-init config # 交互式选择配置类型');
|
|
157
|
+
console.log(' npx ai-engineering-init config --type mysql # 只配置数据库连接');
|
|
158
|
+
console.log(' npx ai-engineering-init config --type loki # 只配置 Loki 日志');
|
|
159
|
+
console.log(' npx ai-engineering-init config --type all # 配置全部');
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
// ── 工具定义(init 用)────────────────────────────────────────────────────
|
|
@@ -1126,153 +1139,464 @@ function runConfig() {
|
|
|
1126
1139
|
process.exit(1);
|
|
1127
1140
|
}
|
|
1128
1141
|
|
|
1129
|
-
const configPath = path.join(targetDir, '.claude', 'mysql-config.json');
|
|
1130
1142
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1131
|
-
|
|
1132
1143
|
const ask = (question) => new Promise((resolve) => {
|
|
1133
1144
|
rl.question(question, (answer) => resolve(answer.trim()));
|
|
1134
1145
|
});
|
|
1135
1146
|
|
|
1136
|
-
const ENV_DEFAULTS = {
|
|
1137
|
-
local: { host: '127.0.0.1', user: 'root', desc: '本地开发环境' },
|
|
1138
|
-
dev: { host: '', user: '', desc: '开发测试环境' },
|
|
1139
|
-
test: { host: '', user: '', desc: '测试环境' },
|
|
1140
|
-
prod: { host: '', user: '', desc: '生产环境' },
|
|
1141
|
-
};
|
|
1142
|
-
|
|
1143
1147
|
(async () => {
|
|
1144
1148
|
try {
|
|
1145
|
-
|
|
1146
|
-
if (fs.existsSync(configPath)) {
|
|
1147
|
-
console.log(fmt('yellow', `⚠ 配置文件已存在:${configPath}`));
|
|
1148
|
-
const overwrite = await ask(fmt('bold', '是否重新配置?[y/N]: '));
|
|
1149
|
-
if (overwrite.toLowerCase() !== 'y') {
|
|
1150
|
-
console.log('已取消。');
|
|
1151
|
-
rl.close();
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
console.log('');
|
|
1155
|
-
}
|
|
1149
|
+
let type = configType;
|
|
1156
1150
|
|
|
1157
|
-
//
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const start = parseInt(rangeMatch[1], 10);
|
|
1175
|
-
const end = parseInt(rangeMatch[2], 10);
|
|
1176
|
-
for (let n = start; n <= end; n++) {
|
|
1177
|
-
if (n >= 1 && n <= 4) selected.add(envNames[n - 1]);
|
|
1178
|
-
}
|
|
1179
|
-
} else {
|
|
1180
|
-
const n = parseInt(trimmed, 10);
|
|
1181
|
-
if (n >= 1 && n <= 4) selected.add(envNames[n - 1]);
|
|
1151
|
+
// 未指定 --type 时显示交互式菜单
|
|
1152
|
+
if (!type) {
|
|
1153
|
+
console.log(fmt('cyan', '请选择要初始化的配置类型:'));
|
|
1154
|
+
console.log('');
|
|
1155
|
+
console.log(` ${fmt('bold', '1')}) ${fmt('green', 'MySQL 数据库连接')} — 配置 mysql-config.json`);
|
|
1156
|
+
console.log(` ${fmt('bold', '2')}) ${fmt('blue', 'Loki 日志查询')} — 配置 Grafana Loki Token`);
|
|
1157
|
+
console.log(` ${fmt('bold', '3')}) ${fmt('yellow','全部配置')} — 依次配置 MySQL + Loki`);
|
|
1158
|
+
console.log('');
|
|
1159
|
+
const answer = await ask(fmt('bold', '请输入选项 [1-3]: '));
|
|
1160
|
+
switch (answer) {
|
|
1161
|
+
case '1': type = 'mysql'; break;
|
|
1162
|
+
case '2': type = 'loki'; break;
|
|
1163
|
+
case '3': type = 'all'; break;
|
|
1164
|
+
default:
|
|
1165
|
+
console.error(fmt('red', '无效选项,退出。'));
|
|
1166
|
+
rl.close();
|
|
1167
|
+
process.exit(1);
|
|
1182
1168
|
}
|
|
1169
|
+
console.log('');
|
|
1183
1170
|
}
|
|
1184
1171
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
console.error(fmt('red', '未选择任何环境,退出。'));
|
|
1172
|
+
if (!['mysql', 'loki', 'all'].includes(type)) {
|
|
1173
|
+
console.error(fmt('red', `错误:不支持的配置类型 "${type}",可选:mysql | loki | all`));
|
|
1188
1174
|
rl.close();
|
|
1189
1175
|
process.exit(1);
|
|
1190
1176
|
}
|
|
1191
1177
|
|
|
1178
|
+
if (type === 'mysql' || type === 'all') {
|
|
1179
|
+
await runMysqlConfig(ask);
|
|
1180
|
+
}
|
|
1181
|
+
if (type === 'loki' || type === 'all') {
|
|
1182
|
+
if (type === 'all') console.log(''); // 分隔符
|
|
1183
|
+
await runLokiConfig(ask);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1192
1186
|
console.log('');
|
|
1193
|
-
console.log(fmt('green',
|
|
1194
|
-
|
|
1187
|
+
console.log(fmt('green', fmt('bold', '配置初始化完成!')));
|
|
1188
|
+
} finally {
|
|
1189
|
+
rl.close();
|
|
1190
|
+
}
|
|
1191
|
+
})();
|
|
1192
|
+
}
|
|
1195
1193
|
|
|
1196
|
-
|
|
1197
|
-
const environments = {};
|
|
1198
|
-
for (const env of selectedEnvs) {
|
|
1199
|
-
const defaults = ENV_DEFAULTS[env];
|
|
1200
|
-
console.log(fmt('cyan', `── ${env} 环境配置 ──`));
|
|
1201
|
-
|
|
1202
|
-
const host = await ask(` host [${defaults.host || '无默认'}]: `) || defaults.host;
|
|
1203
|
-
const port = await ask(' port [3306]: ') || '3306';
|
|
1204
|
-
const user = await ask(` user [${defaults.user || '无默认'}]: `) || defaults.user;
|
|
1205
|
-
const password = await ask(' password: ');
|
|
1206
|
-
const desc = await ask(` 描述 [${defaults.desc}]: `) || defaults.desc;
|
|
1207
|
-
console.log('');
|
|
1194
|
+
// ── MySQL 数据库配置 ────────────────────────────────────────────────────────
|
|
1208
1195
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
process.exit(1);
|
|
1213
|
-
}
|
|
1214
|
-
if (!user) {
|
|
1215
|
-
console.error(fmt('red', `错误:${env} 环境的 user 不能为空`));
|
|
1216
|
-
rl.close();
|
|
1217
|
-
process.exit(1);
|
|
1218
|
-
}
|
|
1196
|
+
async function runMysqlConfig(ask) {
|
|
1197
|
+
console.log(fmt('blue', fmt('bold', '┌─ MySQL 数据库连接配置 ─┐')));
|
|
1198
|
+
console.log('');
|
|
1219
1199
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
port: parseInt(port, 10),
|
|
1223
|
-
user,
|
|
1224
|
-
password,
|
|
1225
|
-
description: desc,
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1200
|
+
// 检测已安装的工具,决定写入哪些目录
|
|
1201
|
+
const targets = detectConfigTargets('mysql-config.json');
|
|
1228
1202
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1203
|
+
if (targets.length === 0) {
|
|
1204
|
+
console.log(fmt('yellow', '⚠ 未检测到 .claude/ 或 .cursor/ 目录。请先运行 init 安装框架。'));
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// 检测已有配置
|
|
1209
|
+
const existingTarget = targets.find(t => fs.existsSync(t.configPath));
|
|
1210
|
+
if (existingTarget) {
|
|
1211
|
+
console.log(fmt('yellow', `⚠ 配置文件已存在:${existingTarget.configPath}`));
|
|
1212
|
+
const overwrite = await ask(fmt('bold', '是否重新配置?[y/N]: '));
|
|
1213
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
1214
|
+
console.log('已跳过 MySQL 配置。');
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
console.log('');
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// 选择环境
|
|
1221
|
+
console.log(fmt('cyan', '请选择要配置的数据库环境(多选,用逗号分隔):'));
|
|
1222
|
+
console.log('');
|
|
1223
|
+
console.log(` ${fmt('bold', '1')}) local — 本地开发环境`);
|
|
1224
|
+
console.log(` ${fmt('bold', '2')}) dev — 开发测试环境`);
|
|
1225
|
+
console.log(` ${fmt('bold', '3')}) test — 测试环境`);
|
|
1226
|
+
console.log(` ${fmt('bold', '4')}) prod — 生产环境`);
|
|
1227
|
+
console.log('');
|
|
1228
|
+
const envAnswer = await ask(fmt('bold', '请输入选项(如 1,2 或 1-3): '));
|
|
1229
|
+
|
|
1230
|
+
const ENV_DEFAULTS = {
|
|
1231
|
+
local: { host: '127.0.0.1', user: 'root', desc: '本地开发环境' },
|
|
1232
|
+
dev: { host: '', user: '', desc: '开发测试环境' },
|
|
1233
|
+
test: { host: '', user: '', desc: '测试环境' },
|
|
1234
|
+
prod: { host: '', user: '', desc: '生产环境' },
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
const envNames = ['local', 'dev', 'test', 'prod'];
|
|
1238
|
+
const selected = parseSelection(envAnswer, envNames);
|
|
1239
|
+
|
|
1240
|
+
if (selected.length === 0) {
|
|
1241
|
+
console.error(fmt('red', '未选择任何环境,跳过 MySQL 配置。'));
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
console.log('');
|
|
1246
|
+
console.log(fmt('green', `已选择环境:${selected.join(', ')}`));
|
|
1247
|
+
console.log('');
|
|
1248
|
+
|
|
1249
|
+
// 收集每个环境的配置
|
|
1250
|
+
const environments = {};
|
|
1251
|
+
for (const env of selected) {
|
|
1252
|
+
const defaults = ENV_DEFAULTS[env];
|
|
1253
|
+
console.log(fmt('cyan', `── ${env} 环境配置 ──`));
|
|
1254
|
+
|
|
1255
|
+
const host = await ask(` host [${defaults.host || '无默认'}]: `) || defaults.host;
|
|
1256
|
+
const port = await ask(' port [3306]: ') || '3306';
|
|
1257
|
+
const user = await ask(` user [${defaults.user || '无默认'}]: `) || defaults.user;
|
|
1258
|
+
const password = await ask(' password: ');
|
|
1259
|
+
const desc = await ask(` 描述 [${defaults.desc}]: `) || defaults.desc;
|
|
1260
|
+
console.log('');
|
|
1261
|
+
|
|
1262
|
+
if (!host) {
|
|
1263
|
+
console.error(fmt('red', `错误:${env} 环境的 host 不能为空,跳过此环境。`));
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (!user) {
|
|
1267
|
+
console.error(fmt('red', `错误:${env} 环境的 user 不能为空,跳过此环境。`));
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
environments[env] = {
|
|
1272
|
+
host,
|
|
1273
|
+
port: parseInt(port, 10),
|
|
1274
|
+
user,
|
|
1275
|
+
password,
|
|
1276
|
+
_desc: desc,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (Object.keys(environments).length === 0) {
|
|
1281
|
+
console.error(fmt('red', '未成功配置任何环境。'));
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// 选择默认环境
|
|
1286
|
+
const configuredEnvs = Object.keys(environments);
|
|
1287
|
+
let defaultEnv = configuredEnvs[0];
|
|
1288
|
+
if (configuredEnvs.length > 1) {
|
|
1289
|
+
console.log(fmt('cyan', '请选择默认环境:'));
|
|
1290
|
+
configuredEnvs.forEach((env, i) => {
|
|
1291
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${env}`);
|
|
1292
|
+
});
|
|
1293
|
+
const defaultAnswer = await ask(fmt('bold', `请输入选项 [1-${configuredEnvs.length}]: `));
|
|
1294
|
+
const idx = parseInt(defaultAnswer, 10) - 1;
|
|
1295
|
+
if (idx >= 0 && idx < configuredEnvs.length) {
|
|
1296
|
+
defaultEnv = configuredEnvs[idx];
|
|
1297
|
+
}
|
|
1298
|
+
console.log('');
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// 写入配置文件(多目标)
|
|
1302
|
+
const config = {
|
|
1303
|
+
environments,
|
|
1304
|
+
default: defaultEnv,
|
|
1305
|
+
_comment: 'database 从日志自动提取(租户ID=数据库名),也可手动指定。使用时说\'连 dev 环境\'即可切换',
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
for (const t of targets) {
|
|
1309
|
+
const dir = path.dirname(t.configPath);
|
|
1310
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1311
|
+
fs.writeFileSync(t.configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
1312
|
+
console.log(` ${fmt('green', '✔')} 已写入:${t.configPath}`);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// 确保 .gitignore
|
|
1316
|
+
ensureGitignore(['mysql-config.json']);
|
|
1317
|
+
|
|
1318
|
+
console.log('');
|
|
1319
|
+
console.log(fmt('green', 'MySQL 数据库配置完成!'));
|
|
1320
|
+
console.log(`使用 ${fmt('bold', 'mysql-debug')} 技能时将自动读取此配置。`);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// ── Loki 日志查询配置 ──────────────────────────────────────────────────────
|
|
1324
|
+
|
|
1325
|
+
async function runLokiConfig(ask) {
|
|
1326
|
+
console.log(fmt('blue', fmt('bold', '┌─ Loki 日志查询配置 ─┐')));
|
|
1327
|
+
console.log('');
|
|
1328
|
+
|
|
1329
|
+
// Loki 配置存放在 skills/loki-log-query/environments.json
|
|
1330
|
+
const targets = detectLokiConfigTargets();
|
|
1331
|
+
|
|
1332
|
+
if (targets.length === 0) {
|
|
1333
|
+
console.log(fmt('yellow', '⚠ 未检测到 loki-log-query 技能目录。请先运行 init 安装框架。'));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// 读取已有配置作为模板
|
|
1338
|
+
let existingConfig = null;
|
|
1339
|
+
for (const t of targets) {
|
|
1340
|
+
if (fs.existsSync(t.configPath)) {
|
|
1341
|
+
try {
|
|
1342
|
+
existingConfig = JSON.parse(fs.readFileSync(t.configPath, 'utf-8'));
|
|
1343
|
+
break;
|
|
1344
|
+
} catch { /* ignore */ }
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// 默认环境模板
|
|
1349
|
+
const DEFAULT_ENVS = {
|
|
1350
|
+
test: {
|
|
1351
|
+
name: '测试环境',
|
|
1352
|
+
url: '',
|
|
1353
|
+
aliases: ['test'],
|
|
1354
|
+
projects: [],
|
|
1355
|
+
},
|
|
1356
|
+
dev: {
|
|
1357
|
+
name: '开发环境',
|
|
1358
|
+
url: '',
|
|
1359
|
+
aliases: ['dev'],
|
|
1360
|
+
projects: [],
|
|
1361
|
+
},
|
|
1362
|
+
prod: {
|
|
1363
|
+
name: '生产环境',
|
|
1364
|
+
url: '',
|
|
1365
|
+
aliases: ['prod'],
|
|
1366
|
+
projects: [],
|
|
1367
|
+
},
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// 如果已有配置,展示当前状态
|
|
1371
|
+
if (existingConfig && existingConfig.environments) {
|
|
1372
|
+
const envs = existingConfig.environments;
|
|
1373
|
+
const envList = Object.keys(envs);
|
|
1374
|
+
console.log(fmt('cyan', '当前已配置的 Loki 环境:'));
|
|
1375
|
+
console.log('');
|
|
1376
|
+
for (const key of envList) {
|
|
1377
|
+
const env = envs[key];
|
|
1378
|
+
const hasToken = env.token && env.token.length > 0;
|
|
1379
|
+
const status = hasToken ? fmt('green', '✔ 已配置 Token') : fmt('red', '✗ 缺少 Token');
|
|
1380
|
+
console.log(` ${fmt('bold', key)} — ${env.name || key} ${status}`);
|
|
1381
|
+
if (env.url) console.log(` URL: ${env.url}`);
|
|
1382
|
+
}
|
|
1383
|
+
console.log('');
|
|
1384
|
+
|
|
1385
|
+
// 检查是否所有环境都已有 Token
|
|
1386
|
+
const missingTokenEnvs = envList.filter(k => !envs[k].token);
|
|
1387
|
+
if (missingTokenEnvs.length === 0) {
|
|
1388
|
+
const reconfig = await ask(fmt('bold', '所有环境已配置 Token,是否重新配置?[y/N]: '));
|
|
1389
|
+
if (reconfig.toLowerCase() !== 'y') {
|
|
1390
|
+
console.log('已跳过 Loki 配置。');
|
|
1391
|
+
return;
|
|
1242
1392
|
}
|
|
1393
|
+
} else {
|
|
1394
|
+
console.log(fmt('yellow', `有 ${missingTokenEnvs.length} 个环境缺少 Token,将引导配置。`));
|
|
1395
|
+
}
|
|
1396
|
+
console.log('');
|
|
1243
1397
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1398
|
+
// 为已有环境补充 Token
|
|
1399
|
+
console.log(fmt('cyan', fmt('bold', 'Grafana Token 获取步骤:')));
|
|
1400
|
+
console.log(` 1. 打开 Grafana 管理后台`);
|
|
1401
|
+
console.log(` 2. 进入 Administration → Service accounts`);
|
|
1402
|
+
console.log(` 3. 创建 Service Account(角色选 ${fmt('bold', 'Viewer')})`);
|
|
1403
|
+
console.log(` 4. 点击 Add token → 复制 Token`);
|
|
1404
|
+
console.log('');
|
|
1405
|
+
|
|
1406
|
+
for (const key of envList) {
|
|
1407
|
+
const env = envs[key];
|
|
1408
|
+
const hasToken = env.token && env.token.length > 0;
|
|
1409
|
+
if (hasToken) {
|
|
1410
|
+
const update = await ask(` ${fmt('bold', key)} 已有 Token,是否更新?[y/N]: `);
|
|
1411
|
+
if (update.toLowerCase() !== 'y') continue;
|
|
1249
1412
|
}
|
|
1250
|
-
|
|
1251
|
-
console.log(fmt('
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1259
|
-
if (content.split('\n').some(line => line.trim() === ignoreEntry)) {
|
|
1260
|
-
needAppend = false;
|
|
1261
|
-
}
|
|
1413
|
+
console.log(` ${fmt('cyan', `── ${key}: ${env.name || key} ──`)}`);
|
|
1414
|
+
if (env.url) console.log(` Grafana URL: ${fmt('bold', env.url)}`);
|
|
1415
|
+
const token = await ask(` 输入 Grafana Service Account Token: `);
|
|
1416
|
+
if (token) {
|
|
1417
|
+
envs[key].token = token;
|
|
1418
|
+
console.log(` ${fmt('green', '✔')} Token 已设置`);
|
|
1419
|
+
} else {
|
|
1420
|
+
console.log(` ${fmt('yellow', '⚠')} 跳过(Token 为空)`);
|
|
1262
1421
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1422
|
+
console.log('');
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// 选择默认环境
|
|
1426
|
+
console.log(fmt('cyan', '请选择默认活跃环境:'));
|
|
1427
|
+
envList.forEach((env, i) => {
|
|
1428
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${env} — ${envs[env].name || env}`);
|
|
1429
|
+
});
|
|
1430
|
+
const activeAnswer = await ask(fmt('bold', `请输入选项 [1-${envList.length}]: `));
|
|
1431
|
+
const activeIdx = parseInt(activeAnswer, 10) - 1;
|
|
1432
|
+
const activeEnv = (activeIdx >= 0 && activeIdx < envList.length) ? envList[activeIdx] : existingConfig.active;
|
|
1433
|
+
console.log('');
|
|
1434
|
+
|
|
1435
|
+
// 写入配置
|
|
1436
|
+
existingConfig.active = activeEnv;
|
|
1437
|
+
for (const t of targets) {
|
|
1438
|
+
const dir = path.dirname(t.configPath);
|
|
1439
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1440
|
+
fs.writeFileSync(t.configPath, JSON.stringify(existingConfig, null, 2) + '\n', 'utf-8');
|
|
1441
|
+
console.log(` ${fmt('green', '✔')} 已写入:${t.configPath}`);
|
|
1442
|
+
}
|
|
1443
|
+
} else {
|
|
1444
|
+
// 无已有配置,从零创建
|
|
1445
|
+
console.log(fmt('cyan', '将创建新的 Loki 日志查询配置。'));
|
|
1446
|
+
console.log('');
|
|
1447
|
+
console.log(fmt('cyan', '请输入要配置的 Grafana 环境数量:'));
|
|
1448
|
+
const countAnswer = await ask(fmt('bold', '环境数量 [1]: ')) || '1';
|
|
1449
|
+
const count = Math.max(1, Math.min(10, parseInt(countAnswer, 10) || 1));
|
|
1450
|
+
console.log('');
|
|
1451
|
+
|
|
1452
|
+
console.log(fmt('cyan', fmt('bold', 'Grafana Token 获取步骤:')));
|
|
1453
|
+
console.log(` 1. 打开 Grafana 管理后台`);
|
|
1454
|
+
console.log(` 2. 进入 Administration → Service accounts`);
|
|
1455
|
+
console.log(` 3. 创建 Service Account(角色选 ${fmt('bold', 'Viewer')})`);
|
|
1456
|
+
console.log(` 4. 点击 Add token → 复制 Token`);
|
|
1457
|
+
console.log('');
|
|
1458
|
+
|
|
1459
|
+
const environments = {};
|
|
1460
|
+
let activeEnv = '';
|
|
1461
|
+
|
|
1462
|
+
for (let i = 0; i < count; i++) {
|
|
1463
|
+
console.log(fmt('cyan', `── 环境 ${i + 1}/${count} ──`));
|
|
1464
|
+
const envKey = await ask(` 环境标识(如 test、dev、prod): `);
|
|
1465
|
+
if (!envKey) {
|
|
1466
|
+
console.log(fmt('yellow', ' 跳过(标识为空)'));
|
|
1467
|
+
continue;
|
|
1267
1468
|
}
|
|
1469
|
+
const name = await ask(` 环境名称(如 "测试环境"): `) || envKey;
|
|
1470
|
+
const url = await ask(` Grafana URL(如 https://grafana.example.com): `);
|
|
1471
|
+
const token = await ask(` Grafana Service Account Token: `);
|
|
1472
|
+
const aliasStr = await ask(` 别名(逗号分隔,如 test,t)[${envKey}]: `) || envKey;
|
|
1473
|
+
const aliases = aliasStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
1474
|
+
console.log('');
|
|
1268
1475
|
|
|
1476
|
+
environments[envKey] = { name, url, token: token || '', aliases, projects: [] };
|
|
1477
|
+
if (!activeEnv) activeEnv = envKey;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (Object.keys(environments).length === 0) {
|
|
1481
|
+
console.error(fmt('red', '未配置任何环境。'));
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// 选择默认
|
|
1486
|
+
const envKeys = Object.keys(environments);
|
|
1487
|
+
if (envKeys.length > 1) {
|
|
1488
|
+
console.log(fmt('cyan', '请选择默认活跃环境:'));
|
|
1489
|
+
envKeys.forEach((env, i) => {
|
|
1490
|
+
console.log(` ${fmt('bold', String(i + 1))}) ${env}`);
|
|
1491
|
+
});
|
|
1492
|
+
const activeAnswer = await ask(fmt('bold', `请输入选项 [1-${envKeys.length}]: `));
|
|
1493
|
+
const idx = parseInt(activeAnswer, 10) - 1;
|
|
1494
|
+
if (idx >= 0 && idx < envKeys.length) activeEnv = envKeys[idx];
|
|
1269
1495
|
console.log('');
|
|
1270
|
-
console.log(fmt('green', '数据库配置初始化完成!'));
|
|
1271
|
-
console.log(`使用 ${fmt('bold', 'mysql-debug')} 技能时将自动读取此配置。`);
|
|
1272
|
-
} finally {
|
|
1273
|
-
rl.close();
|
|
1274
1496
|
}
|
|
1275
|
-
|
|
1497
|
+
|
|
1498
|
+
const config = {
|
|
1499
|
+
_comment: 'Loki 多环境配置。每个环境需要独立的 Grafana Service Account Token。',
|
|
1500
|
+
_usage: "用户说'查 test 的日志'或'去 dev 查'时,匹配对应环境。",
|
|
1501
|
+
_setup: 'Token 创建:Grafana → Administration → Service accounts → Add(Viewer 角色)→ Add token → 复制到对应环境的 token 字段。',
|
|
1502
|
+
active: activeEnv,
|
|
1503
|
+
environments,
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
for (const t of targets) {
|
|
1507
|
+
const dir = path.dirname(t.configPath);
|
|
1508
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1509
|
+
fs.writeFileSync(t.configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
1510
|
+
console.log(` ${fmt('green', '✔')} 已写入:${t.configPath}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// 确保 .gitignore
|
|
1515
|
+
ensureGitignore(['loki-log-query/environments.json']);
|
|
1516
|
+
|
|
1517
|
+
console.log('');
|
|
1518
|
+
console.log(fmt('green', 'Loki 日志查询配置完成!'));
|
|
1519
|
+
console.log(`使用 ${fmt('bold', 'loki-log-query')} 技能时将自动读取此配置。`);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// ── Config 工具函数 ─────────────────────────────────────────────────────────
|
|
1523
|
+
|
|
1524
|
+
/** 检测已安装工具目录,返回 MySQL 配置文件路径列表 */
|
|
1525
|
+
function detectConfigTargets(filename) {
|
|
1526
|
+
const targets = [];
|
|
1527
|
+
// MySQL 配置统一放在 .claude/ 下,Cursor 技能也引用 .claude/mysql-config.json
|
|
1528
|
+
const claudePath = path.join(targetDir, '.claude', filename);
|
|
1529
|
+
|
|
1530
|
+
if (fs.existsSync(path.join(targetDir, '.claude'))) {
|
|
1531
|
+
targets.push({ tool: 'claude', configPath: claudePath });
|
|
1532
|
+
} else if (fs.existsSync(path.join(targetDir, '.cursor'))) {
|
|
1533
|
+
// 如果只有 Cursor 没有 Claude,也写到 .claude/ 下(技能引用此路径)
|
|
1534
|
+
targets.push({ tool: 'cursor', configPath: claudePath });
|
|
1535
|
+
}
|
|
1536
|
+
return targets;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
/** 检测 Loki 配置文件路径列表 */
|
|
1540
|
+
function detectLokiConfigTargets() {
|
|
1541
|
+
const targets = [];
|
|
1542
|
+
const claudePath = path.join(targetDir, '.claude', 'skills', 'loki-log-query', 'environments.json');
|
|
1543
|
+
const cursorPath = path.join(targetDir, '.cursor', 'skills', 'loki-log-query', 'environments.json');
|
|
1544
|
+
|
|
1545
|
+
if (fs.existsSync(path.join(targetDir, '.claude', 'skills', 'loki-log-query'))) {
|
|
1546
|
+
targets.push({ tool: 'claude', configPath: claudePath });
|
|
1547
|
+
}
|
|
1548
|
+
if (fs.existsSync(path.join(targetDir, '.cursor', 'skills', 'loki-log-query'))) {
|
|
1549
|
+
targets.push({ tool: 'cursor', configPath: cursorPath });
|
|
1550
|
+
}
|
|
1551
|
+
return targets;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/** 解析选择字符串(如 "1,2" 或 "1-3") */
|
|
1555
|
+
function parseSelection(answer, names) {
|
|
1556
|
+
const selected = new Set();
|
|
1557
|
+
for (const part of answer.split(',')) {
|
|
1558
|
+
const trimmed = part.trim();
|
|
1559
|
+
const rangeMatch = trimmed.match(/^(\d)-(\d)$/);
|
|
1560
|
+
if (rangeMatch) {
|
|
1561
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
1562
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
1563
|
+
for (let n = start; n <= end; n++) {
|
|
1564
|
+
if (n >= 1 && n <= names.length) selected.add(names[n - 1]);
|
|
1565
|
+
}
|
|
1566
|
+
} else {
|
|
1567
|
+
const n = parseInt(trimmed, 10);
|
|
1568
|
+
if (n >= 1 && n <= names.length) selected.add(names[n - 1]);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return [...selected];
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/** 确保敏感配置文件在 .gitignore 中 */
|
|
1575
|
+
function ensureGitignore(patterns) {
|
|
1576
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
1577
|
+
let content = '';
|
|
1578
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1579
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1580
|
+
}
|
|
1581
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
1582
|
+
const toAdd = [];
|
|
1583
|
+
|
|
1584
|
+
for (const pattern of patterns) {
|
|
1585
|
+
// 检查是否已有任意形式的忽略规则(精确路径、通配符等)
|
|
1586
|
+
const alreadyIgnored = lines.some(line =>
|
|
1587
|
+
line.endsWith(pattern) || line.endsWith(`/${pattern}`) || line === `**/${pattern}`
|
|
1588
|
+
);
|
|
1589
|
+
if (!alreadyIgnored) {
|
|
1590
|
+
toAdd.push(`# 敏感配置 - ${pattern}`);
|
|
1591
|
+
toAdd.push(`**/${pattern}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (toAdd.length > 0) {
|
|
1596
|
+
const separator = content.endsWith('\n') || content === '' ? '' : '\n';
|
|
1597
|
+
fs.appendFileSync(gitignorePath, `${separator}${toAdd.join('\n')}\n`, 'utf-8');
|
|
1598
|
+
console.log(` ${fmt('green', '✔')} 已更新 .gitignore(防止提交敏感配置)`);
|
|
1599
|
+
}
|
|
1276
1600
|
}
|
|
1277
1601
|
|
|
1278
1602
|
// ── MCP 服务器管理 ──────────────────────────────────────────────────────────
|
|
@@ -1765,7 +2089,7 @@ function showMainMenu() {
|
|
|
1765
2089
|
console.log(` ${fmt('bold', '2')}) ${fmt('cyan', '更新')} — 更新已安装的框架文件`);
|
|
1766
2090
|
console.log(` ${fmt('bold', '3')}) ${fmt('yellow', '全局安装')} — 安装到 ~/.claude 等,对所有项目生效`);
|
|
1767
2091
|
console.log(` ${fmt('bold', '4')}) ${fmt('magenta', '技能同步反馈')} — 对比本地技能修改,生成 diff`);
|
|
1768
|
-
console.log(` ${fmt('bold', '5')}) ${fmt('blue', '
|
|
2092
|
+
console.log(` ${fmt('bold', '5')}) ${fmt('blue', '环境配置')} — 初始化数据库连接 / Loki 日志查询配置`);
|
|
1769
2093
|
console.log(` ${fmt('bold', '6')}) ${fmt('green', 'MCP 管理')} — MCP 服务器安装/卸载/状态检查`);
|
|
1770
2094
|
console.log('');
|
|
1771
2095
|
rl.question(fmt('bold', '请输入选项 [1-6]: '), (answer) => {
|