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.
Files changed (2) hide show
  1. package/bin/index.js +445 -121
  2. 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')} 初始化数据库配置文件(.claude/mysql-config.json)`);
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
- // 1. 检测已有配置
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
- // 2. 选择环境
1158
- console.log(fmt('cyan', '请选择要配置的数据库环境(多选,用逗号分隔):'));
1159
- console.log('');
1160
- console.log(` ${fmt('bold', '1')}) local — 本地开发环境`);
1161
- console.log(` ${fmt('bold', '2')}) dev 开发测试环境`);
1162
- console.log(` ${fmt('bold', '3')}) test 测试环境`);
1163
- console.log(` ${fmt('bold', '4')}) prod 生产环境`);
1164
- console.log('');
1165
- const envAnswer = await ask(fmt('bold', '请输入选项(如 1,2 或 1-3): '));
1166
-
1167
- // 解析选择
1168
- const envNames = ['local', 'dev', 'test', 'prod'];
1169
- const selected = new Set();
1170
- for (const part of envAnswer.split(',')) {
1171
- const trimmed = part.trim();
1172
- const rangeMatch = trimmed.match(/^(\d)-(\d)$/);
1173
- if (rangeMatch) {
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
- const selectedEnvs = [...selected];
1186
- if (selectedEnvs.length === 0) {
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', `已选择环境:${selectedEnvs.join(', ')}`));
1194
- console.log('');
1187
+ console.log(fmt('green', fmt('bold', '配置初始化完成!')));
1188
+ } finally {
1189
+ rl.close();
1190
+ }
1191
+ })();
1192
+ }
1195
1193
 
1196
- // 3. 收集每个环境的配置
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
- if (!host) {
1210
- console.error(fmt('red', `错误:${env} 环境的 host 不能为空`));
1211
- rl.close();
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
- environments[env] = {
1221
- host,
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
- // 4. 选择默认环境
1230
- let defaultEnv = selectedEnvs[0];
1231
- if (selectedEnvs.length > 1) {
1232
- console.log(fmt('cyan', '请选择默认环境:'));
1233
- selectedEnvs.forEach((env, i) => {
1234
- console.log(` ${fmt('bold', String(i + 1))}) ${env}`);
1235
- });
1236
- const defaultAnswer = await ask(fmt('bold', `请输入选项 [1-${selectedEnvs.length}]: `));
1237
- const idx = parseInt(defaultAnswer, 10) - 1;
1238
- if (idx >= 0 && idx < selectedEnvs.length) {
1239
- defaultEnv = selectedEnvs[idx];
1240
- }
1241
- console.log('');
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
- // 5. 写入配置文件
1245
- const config = { defaultEnv, environments };
1246
- const claudeDir = path.join(targetDir, '.claude');
1247
- if (!fs.existsSync(claudeDir)) {
1248
- fs.mkdirSync(claudeDir, { recursive: true });
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
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
1251
- console.log(fmt('green', `✔ 配置已写入:${configPath}`));
1252
-
1253
- // 6. 确保 .gitignore 包含该文件
1254
- const gitignorePath = path.join(targetDir, '.gitignore');
1255
- const ignoreEntry = '.claude/mysql-config.json';
1256
- let needAppend = true;
1257
- if (fs.existsSync(gitignorePath)) {
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
- if (needAppend) {
1264
- const separator = fs.existsSync(gitignorePath) ? '\n' : '';
1265
- fs.appendFileSync(gitignorePath, `${separator}${ignoreEntry}\n`, 'utf-8');
1266
- console.log(fmt('green', `✔ 已添加 ${ignoreEntry} 到 .gitignore`));
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', '数据库配置')} 初始化 mysql-config.json(数据库连接信息)`);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-engineering-init",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "AI 工程化配置初始化工具 — 一键为 Claude Code、OpenAI Codex 等 AI 工具初始化 Skills 和项目规范",
5
5
  "keywords": [
6
6
  "claude-code",