kairn-cli 1.7.0 → 1.8.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/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/cli.ts
2
- import { Command as Command10 } from "commander";
3
- import chalk12 from "chalk";
2
+ import { Command as Command11 } from "commander";
3
+ import chalk14 from "chalk";
4
4
 
5
5
  // src/commands/init.ts
6
6
  import { Command } from "commander";
@@ -90,7 +90,7 @@ var ui = {
90
90
  // Key-value pairs
91
91
  kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
92
92
  // File list
93
- file: (path13) => chalk.dim(` ${path13}`),
93
+ file: (path15) => chalk.dim(` ${path15}`),
94
94
  // Tool display
95
95
  tool: (name, reason) => ` ${warm("\u25CF")} ${chalk.bold(name)}
96
96
  ${chalk.dim(reason)}`,
@@ -339,7 +339,7 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
339
339
  // src/commands/describe.ts
340
340
  import { Command as Command2 } from "commander";
341
341
  import { input, confirm } from "@inquirer/prompts";
342
- import chalk4 from "chalk";
342
+ import chalk5 from "chalk";
343
343
  import ora from "ora";
344
344
 
345
345
  // src/compiler/compile.ts
@@ -926,25 +926,39 @@ function isCodeProject(spec) {
926
926
  const commands = spec.harness.commands ?? {};
927
927
  return "status" in commands || "test" in commands;
928
928
  }
929
- function resolveSettings(spec) {
929
+ var ENV_LOADER_HOOK = {
930
+ matcher: "",
931
+ hooks: [{
932
+ type: "command",
933
+ command: 'if [ -f .env ] && [ -n "$CLAUDE_ENV_FILE" ]; then grep -v "^#" .env | grep -v "^$" | grep "=" >> "$CLAUDE_ENV_FILE"; fi'
934
+ }]
935
+ };
936
+ function resolveSettings(spec, options) {
930
937
  const settings = spec.harness.settings;
931
- if (!settings || Object.keys(settings).length === 0) return null;
932
- if ("statusLine" in settings) return settings;
933
- if (isCodeProject(spec)) {
934
- return { ...settings, statusLine: STATUS_LINE };
935
- }
936
- return settings;
938
+ const base = settings && Object.keys(settings).length > 0 ? { ...settings } : {};
939
+ if (!("statusLine" in base) && isCodeProject(spec)) {
940
+ base.statusLine = STATUS_LINE;
941
+ }
942
+ if (options?.hasEnvVars) {
943
+ const hooks = base.hooks ?? {};
944
+ const sessionStart = hooks.SessionStart ?? [];
945
+ sessionStart.push(ENV_LOADER_HOOK);
946
+ hooks.SessionStart = sessionStart;
947
+ base.hooks = hooks;
948
+ }
949
+ if (Object.keys(base).length === 0) return null;
950
+ return base;
937
951
  }
938
952
  async function writeFile(filePath, content) {
939
953
  await fs5.mkdir(path5.dirname(filePath), { recursive: true });
940
954
  await fs5.writeFile(filePath, content, "utf-8");
941
955
  }
942
- function buildFileMap(spec) {
956
+ function buildFileMap(spec, options) {
943
957
  const files = /* @__PURE__ */ new Map();
944
958
  if (spec.harness.claude_md) {
945
959
  files.set(".claude/CLAUDE.md", spec.harness.claude_md);
946
960
  }
947
- const resolvedSettings = resolveSettings(spec);
961
+ const resolvedSettings = resolveSettings(spec, options);
948
962
  if (resolvedSettings) {
949
963
  files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
950
964
  }
@@ -981,7 +995,7 @@ function buildFileMap(spec) {
981
995
  }
982
996
  return files;
983
997
  }
984
- async function writeEnvironment(spec, targetDir) {
998
+ async function writeEnvironment(spec, targetDir, options) {
985
999
  const claudeDir = path5.join(targetDir, ".claude");
986
1000
  const written = [];
987
1001
  if (spec.harness.claude_md) {
@@ -989,7 +1003,7 @@ async function writeEnvironment(spec, targetDir) {
989
1003
  await writeFile(p, spec.harness.claude_md);
990
1004
  written.push(".claude/CLAUDE.md");
991
1005
  }
992
- const resolvedSettings = resolveSettings(spec);
1006
+ const resolvedSettings = resolveSettings(spec, options);
993
1007
  if (resolvedSettings) {
994
1008
  const p = path5.join(claudeDir, "settings.json");
995
1009
  await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
@@ -1192,6 +1206,114 @@ async function writeHermesEnvironment(spec, registry) {
1192
1206
  return written;
1193
1207
  }
1194
1208
 
1209
+ // src/secrets.ts
1210
+ import { password as password2 } from "@inquirer/prompts";
1211
+ import chalk4 from "chalk";
1212
+ import fs7 from "fs/promises";
1213
+ import path7 from "path";
1214
+ async function collectAndWriteKeys(envSetup, targetDir) {
1215
+ console.log(ui.section("API Keys"));
1216
+ console.log(
1217
+ chalk4.dim(" Some tools need API keys. Enter them now or press Enter to skip.\n")
1218
+ );
1219
+ const envEntries = [
1220
+ "# Generated by Kairn \u2014 API keys for MCP servers",
1221
+ "# Do NOT commit this file to git",
1222
+ ""
1223
+ ];
1224
+ let keysEntered = 0;
1225
+ let keysSkipped = 0;
1226
+ const seen = /* @__PURE__ */ new Set();
1227
+ for (const env of envSetup) {
1228
+ if (seen.has(env.envVar)) continue;
1229
+ seen.add(env.envVar);
1230
+ console.log(chalk4.bold(` ${env.envVar}`) + chalk4.dim(` (${env.toolName})`));
1231
+ if (env.signupUrl) {
1232
+ console.log(chalk4.dim(` Get one at: ${env.signupUrl}`));
1233
+ }
1234
+ const value = await password2({
1235
+ message: env.envVar,
1236
+ mask: "\u2022"
1237
+ });
1238
+ if (value && value.trim()) {
1239
+ envEntries.push(`${env.envVar}=${value.trim()}`);
1240
+ console.log(chalk4.green(" \u2713 saved\n"));
1241
+ keysEntered++;
1242
+ } else {
1243
+ envEntries.push(`${env.envVar}=`);
1244
+ console.log(chalk4.dim(" (skipped)\n"));
1245
+ keysSkipped++;
1246
+ }
1247
+ }
1248
+ const envPath = path7.join(targetDir, ".env");
1249
+ await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
1250
+ await ensureGitignoreEntry(targetDir, ".env");
1251
+ console.log(chalk4.green(` \u2713 ${keysEntered} key(s) saved to .env (gitignored)`));
1252
+ if (keysSkipped > 0) {
1253
+ console.log(chalk4.dim(" Skipped keys can be added later: kairn keys"));
1254
+ }
1255
+ return { keysEntered, keysSkipped, envPath };
1256
+ }
1257
+ async function writeEmptyEnvFile(envSetup, targetDir) {
1258
+ const envEntries = [
1259
+ "# Generated by Kairn \u2014 API keys for MCP servers",
1260
+ "# Do NOT commit this file to git",
1261
+ ""
1262
+ ];
1263
+ const seen = /* @__PURE__ */ new Set();
1264
+ for (const env of envSetup) {
1265
+ if (seen.has(env.envVar)) continue;
1266
+ seen.add(env.envVar);
1267
+ envEntries.push(`${env.envVar}=`);
1268
+ }
1269
+ const envPath = path7.join(targetDir, ".env");
1270
+ await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
1271
+ await ensureGitignoreEntry(targetDir, ".env");
1272
+ }
1273
+ async function ensureGitignoreEntry(targetDir, entry) {
1274
+ const gitignorePath = path7.join(targetDir, ".gitignore");
1275
+ let gitignore = "";
1276
+ try {
1277
+ gitignore = await fs7.readFile(gitignorePath, "utf-8");
1278
+ } catch {
1279
+ }
1280
+ if (!gitignore.split("\n").some((line) => line.trim() === entry)) {
1281
+ const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
1282
+ await fs7.writeFile(gitignorePath, gitignore + separator + entry + "\n", "utf-8");
1283
+ }
1284
+ }
1285
+ async function readEnvFile(targetDir) {
1286
+ const envPath = path7.join(targetDir, ".env");
1287
+ const entries = /* @__PURE__ */ new Map();
1288
+ try {
1289
+ const content = await fs7.readFile(envPath, "utf-8");
1290
+ for (const line of content.split("\n")) {
1291
+ const trimmed = line.trim();
1292
+ if (!trimmed || trimmed.startsWith("#")) continue;
1293
+ const eqIndex = trimmed.indexOf("=");
1294
+ if (eqIndex === -1) continue;
1295
+ const key = trimmed.slice(0, eqIndex);
1296
+ const value = trimmed.slice(eqIndex + 1);
1297
+ entries.set(key, value);
1298
+ }
1299
+ } catch {
1300
+ }
1301
+ return entries;
1302
+ }
1303
+ async function detectRequiredEnvVars(targetDir) {
1304
+ const mcpPath = path7.join(targetDir, ".mcp.json");
1305
+ const envVars = /* @__PURE__ */ new Set();
1306
+ try {
1307
+ const content = await fs7.readFile(mcpPath, "utf-8");
1308
+ const matches = content.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
1309
+ for (const match of matches) {
1310
+ envVars.add(match[1]);
1311
+ }
1312
+ } catch {
1313
+ }
1314
+ return [...envVars];
1315
+ }
1316
+
1195
1317
  // src/commands/describe.ts
1196
1318
  var describeCommand = new Command2("describe").description("Describe your workflow and generate a Claude Code environment").argument("[intent]", "What you want your agent to do").option("-y, --yes", "Skip confirmation prompt").option("-q, --quick", "Skip clarification questions").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (intentArg, options) => {
1197
1319
  printFullBanner("The Agent Environment Compiler");
@@ -1200,7 +1322,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1200
1322
  console.log(
1201
1323
  ui.errorBox(
1202
1324
  "No configuration found",
1203
- `Run ${chalk4.bold("kairn init")} to set up your API key.`
1325
+ `Run ${chalk5.bold("kairn init")} to set up your API key.`
1204
1326
  )
1205
1327
  );
1206
1328
  process.exit(1);
@@ -1209,14 +1331,14 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1209
1331
  message: "What do you want your agent to do?"
1210
1332
  });
1211
1333
  if (!intentRaw.trim()) {
1212
- console.log(chalk4.red("\n No description provided. Aborting.\n"));
1334
+ console.log(chalk5.red("\n No description provided. Aborting.\n"));
1213
1335
  process.exit(1);
1214
1336
  }
1215
1337
  let finalIntent = intentRaw;
1216
1338
  if (!options.quick) {
1217
1339
  console.log(ui.section("Clarification"));
1218
- console.log(chalk4.dim(" Let me understand your project better."));
1219
- console.log(chalk4.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
1340
+ console.log(chalk5.dim(" Let me understand your project better."));
1341
+ console.log(chalk5.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
1220
1342
  let clarifications = [];
1221
1343
  try {
1222
1344
  clarifications = await generateClarifications(intentRaw);
@@ -1249,7 +1371,7 @@ ${clarificationLines}`;
1249
1371
  } catch (err) {
1250
1372
  spinner.fail("Compilation failed");
1251
1373
  const msg = err instanceof Error ? err.message : String(err);
1252
- console.log(chalk4.red(`
1374
+ console.log(chalk5.red(`
1253
1375
  ${msg}
1254
1376
  `));
1255
1377
  process.exit(1);
@@ -1279,7 +1401,7 @@ ${clarificationLines}`;
1279
1401
  default: true
1280
1402
  });
1281
1403
  if (!proceed) {
1282
- console.log(chalk4.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1404
+ console.log(chalk5.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1283
1405
  return;
1284
1406
  }
1285
1407
  const targetDir = process.cwd();
@@ -1288,25 +1410,24 @@ ${clarificationLines}`;
1288
1410
  await writeHermesEnvironment(spec, registry);
1289
1411
  console.log("\n" + ui.success("Environment written for Hermes"));
1290
1412
  console.log(
1291
- chalk4.cyan("\n Ready! Run ") + chalk4.bold("hermes") + chalk4.cyan(" to start.\n")
1413
+ chalk5.cyan("\n Ready! Run ") + chalk5.bold("hermes") + chalk5.cyan(" to start.\n")
1292
1414
  );
1293
1415
  } else {
1294
- const written = await writeEnvironment(spec, targetDir);
1416
+ const hasEnvVars = summary.envSetup.length > 0;
1417
+ const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
1295
1418
  console.log(ui.section("Files Written"));
1296
1419
  console.log("");
1297
1420
  for (const file of written) {
1298
1421
  console.log(ui.file(file));
1299
1422
  }
1300
- if (summary.envSetup.length > 0) {
1301
- console.log(ui.section("Setup Required"));
1302
- console.log("");
1303
- const seen = /* @__PURE__ */ new Set();
1304
- for (const env of summary.envSetup) {
1305
- if (seen.has(env.envVar)) continue;
1306
- seen.add(env.envVar);
1307
- console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1308
- console.log("");
1423
+ if (hasEnvVars) {
1424
+ if (options.quick) {
1425
+ await writeEmptyEnvFile(summary.envSetup, targetDir);
1426
+ console.log(ui.success("Empty .env written (gitignored) \u2014 fill in keys later: kairn keys"));
1427
+ } else {
1428
+ await collectAndWriteKeys(summary.envSetup, targetDir);
1309
1429
  }
1430
+ console.log("");
1310
1431
  }
1311
1432
  if (summary.pluginCommands.length > 0) {
1312
1433
  console.log(ui.section("Plugins"));
@@ -1324,28 +1445,28 @@ ${clarificationLines}`;
1324
1445
 
1325
1446
  // src/commands/list.ts
1326
1447
  import { Command as Command3 } from "commander";
1327
- import chalk5 from "chalk";
1328
- import fs7 from "fs/promises";
1329
- import path7 from "path";
1448
+ import chalk6 from "chalk";
1449
+ import fs8 from "fs/promises";
1450
+ import path8 from "path";
1330
1451
  var listCommand = new Command3("list").description("Show saved environments").action(async () => {
1331
1452
  printCompactBanner();
1332
1453
  const envsDir = getEnvsDir();
1333
1454
  let files;
1334
1455
  try {
1335
- files = await fs7.readdir(envsDir);
1456
+ files = await fs8.readdir(envsDir);
1336
1457
  } catch {
1337
- console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1458
+ console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
1338
1459
  return;
1339
1460
  }
1340
1461
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
1341
1462
  if (jsonFiles.length === 0) {
1342
- console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1463
+ console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
1343
1464
  return;
1344
1465
  }
1345
1466
  let first = true;
1346
1467
  for (const file of jsonFiles) {
1347
1468
  try {
1348
- const data = await fs7.readFile(path7.join(envsDir, file), "utf-8");
1469
+ const data = await fs8.readFile(path8.join(envsDir, file), "utf-8");
1349
1470
  const spec = JSON.parse(data);
1350
1471
  const date = new Date(spec.created_at).toLocaleDateString();
1351
1472
  const toolCount = spec.tools?.length ?? 0;
@@ -1353,10 +1474,10 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1353
1474
  console.log(ui.divider());
1354
1475
  }
1355
1476
  first = false;
1356
- console.log(ui.kv("Name", chalk5.bold(spec.name)));
1477
+ console.log(ui.kv("Name", chalk6.bold(spec.name)));
1357
1478
  console.log(ui.kv("Description", spec.description));
1358
1479
  console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
1359
- console.log(ui.kv("ID", chalk5.dim(spec.id)));
1480
+ console.log(ui.kv("ID", chalk6.dim(spec.id)));
1360
1481
  console.log("");
1361
1482
  } catch {
1362
1483
  }
@@ -1365,9 +1486,9 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1365
1486
 
1366
1487
  // src/commands/activate.ts
1367
1488
  import { Command as Command4 } from "commander";
1368
- import chalk6 from "chalk";
1369
- import fs8 from "fs/promises";
1370
- import path8 from "path";
1489
+ import chalk7 from "chalk";
1490
+ import fs9 from "fs/promises";
1491
+ import path9 from "path";
1371
1492
  var activateCommand = new Command4("activate").description("Re-deploy a saved environment to the current directory").argument("<env_id>", "Environment ID (from kairn list)").action(async (envId) => {
1372
1493
  printCompactBanner();
1373
1494
  const envsDir = getEnvsDir();
@@ -1377,7 +1498,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1377
1498
  let fromTemplate = false;
1378
1499
  let envFiles = [];
1379
1500
  try {
1380
- envFiles = await fs8.readdir(envsDir);
1501
+ envFiles = await fs9.readdir(envsDir);
1381
1502
  } catch {
1382
1503
  }
1383
1504
  match = envFiles.find(
@@ -1388,7 +1509,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1388
1509
  } else {
1389
1510
  let templateFiles = [];
1390
1511
  try {
1391
- templateFiles = await fs8.readdir(templatesDir);
1512
+ templateFiles = await fs9.readdir(templatesDir);
1392
1513
  } catch {
1393
1514
  }
1394
1515
  match = templateFiles.find(
@@ -1399,16 +1520,16 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1399
1520
  fromTemplate = true;
1400
1521
  } else {
1401
1522
  console.log(ui.error(`Environment "${envId}" not found.`));
1402
- console.log(chalk6.dim(" Run kairn list to see saved environments."));
1403
- console.log(chalk6.dim(" Run kairn templates to see available templates.\n"));
1523
+ console.log(chalk7.dim(" Run kairn list to see saved environments."));
1524
+ console.log(chalk7.dim(" Run kairn templates to see available templates.\n"));
1404
1525
  process.exit(1);
1405
1526
  }
1406
1527
  }
1407
- const data = await fs8.readFile(path8.join(sourceDir, match), "utf-8");
1528
+ const data = await fs9.readFile(path9.join(sourceDir, match), "utf-8");
1408
1529
  const spec = JSON.parse(data);
1409
- const label = fromTemplate ? chalk6.dim(" (template)") : "";
1410
- console.log(chalk6.cyan(` Activating: ${spec.name}`) + label);
1411
- console.log(chalk6.dim(` ${spec.description}
1530
+ const label = fromTemplate ? chalk7.dim(" (template)") : "";
1531
+ console.log(chalk7.cyan(` Activating: ${spec.name}`) + label);
1532
+ console.log(chalk7.dim(` ${spec.description}
1412
1533
  `));
1413
1534
  const targetDir = process.cwd();
1414
1535
  const written = await writeEnvironment(spec, targetDir);
@@ -1421,22 +1542,22 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1421
1542
 
1422
1543
  // src/commands/update-registry.ts
1423
1544
  import { Command as Command5 } from "commander";
1424
- import chalk7 from "chalk";
1425
- import fs9 from "fs/promises";
1426
- import path9 from "path";
1545
+ import chalk8 from "chalk";
1546
+ import fs10 from "fs/promises";
1547
+ import path10 from "path";
1427
1548
  import { fileURLToPath as fileURLToPath3 } from "url";
1428
1549
  var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
1429
1550
  async function getLocalRegistryPath() {
1430
1551
  const __filename3 = fileURLToPath3(import.meta.url);
1431
- const __dirname3 = path9.dirname(__filename3);
1552
+ const __dirname3 = path10.dirname(__filename3);
1432
1553
  const candidates = [
1433
- path9.resolve(__dirname3, "../registry/tools.json"),
1434
- path9.resolve(__dirname3, "../src/registry/tools.json"),
1435
- path9.resolve(__dirname3, "../../src/registry/tools.json")
1554
+ path10.resolve(__dirname3, "../registry/tools.json"),
1555
+ path10.resolve(__dirname3, "../src/registry/tools.json"),
1556
+ path10.resolve(__dirname3, "../../src/registry/tools.json")
1436
1557
  ];
1437
1558
  for (const candidate of candidates) {
1438
1559
  try {
1439
- await fs9.access(candidate);
1560
+ await fs10.access(candidate);
1440
1561
  return candidate;
1441
1562
  } catch {
1442
1563
  continue;
@@ -1447,15 +1568,15 @@ async function getLocalRegistryPath() {
1447
1568
  var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
1448
1569
  printCompactBanner();
1449
1570
  const url = options.url || REGISTRY_URL;
1450
- console.log(chalk7.dim(` Fetching registry from ${url}...`));
1571
+ console.log(chalk8.dim(` Fetching registry from ${url}...`));
1451
1572
  try {
1452
1573
  const response = await fetch(url);
1453
1574
  if (!response.ok) {
1454
1575
  console.log(
1455
1576
  ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
1456
1577
  );
1457
- console.log(chalk7.dim(" The remote registry may not be available yet."));
1458
- console.log(chalk7.dim(" Your local registry is still active.\n"));
1578
+ console.log(chalk8.dim(" The remote registry may not be available yet."));
1579
+ console.log(chalk8.dim(" Your local registry is still active.\n"));
1459
1580
  return;
1460
1581
  }
1461
1582
  const text = await response.text();
@@ -1473,35 +1594,35 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1473
1594
  const registryPath = await getLocalRegistryPath();
1474
1595
  const backupPath = registryPath + ".bak";
1475
1596
  try {
1476
- await fs9.copyFile(registryPath, backupPath);
1597
+ await fs10.copyFile(registryPath, backupPath);
1477
1598
  } catch {
1478
1599
  }
1479
- await fs9.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
1600
+ await fs10.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
1480
1601
  console.log(ui.success(`Registry updated: ${tools.length} tools`));
1481
- console.log(chalk7.dim(` Saved to: ${registryPath}`));
1482
- console.log(chalk7.dim(` Backup: ${backupPath}
1602
+ console.log(chalk8.dim(` Saved to: ${registryPath}`));
1603
+ console.log(chalk8.dim(` Backup: ${backupPath}
1483
1604
  `));
1484
1605
  } catch (err) {
1485
1606
  const msg = err instanceof Error ? err.message : String(err);
1486
1607
  console.log(ui.error(`Network error: ${msg}`));
1487
- console.log(chalk7.dim(" Your local registry is still active.\n"));
1608
+ console.log(chalk8.dim(" Your local registry is still active.\n"));
1488
1609
  }
1489
1610
  });
1490
1611
 
1491
1612
  // src/commands/optimize.ts
1492
1613
  import { Command as Command6 } from "commander";
1493
1614
  import { confirm as confirm2 } from "@inquirer/prompts";
1494
- import chalk8 from "chalk";
1615
+ import chalk9 from "chalk";
1495
1616
  import ora2 from "ora";
1496
- import fs11 from "fs/promises";
1497
- import path11 from "path";
1617
+ import fs12 from "fs/promises";
1618
+ import path12 from "path";
1498
1619
 
1499
1620
  // src/scanner/scan.ts
1500
- import fs10 from "fs/promises";
1501
- import path10 from "path";
1621
+ import fs11 from "fs/promises";
1622
+ import path11 from "path";
1502
1623
  async function fileExists(p) {
1503
1624
  try {
1504
- await fs10.access(p);
1625
+ await fs11.access(p);
1505
1626
  return true;
1506
1627
  } catch {
1507
1628
  return false;
@@ -1509,7 +1630,7 @@ async function fileExists(p) {
1509
1630
  }
1510
1631
  async function readJsonSafe(p) {
1511
1632
  try {
1512
- const data = await fs10.readFile(p, "utf-8");
1633
+ const data = await fs11.readFile(p, "utf-8");
1513
1634
  return JSON.parse(data);
1514
1635
  } catch {
1515
1636
  return null;
@@ -1517,14 +1638,14 @@ async function readJsonSafe(p) {
1517
1638
  }
1518
1639
  async function readFileSafe(p) {
1519
1640
  try {
1520
- return await fs10.readFile(p, "utf-8");
1641
+ return await fs11.readFile(p, "utf-8");
1521
1642
  } catch {
1522
1643
  return null;
1523
1644
  }
1524
1645
  }
1525
1646
  async function listDirSafe(p) {
1526
1647
  try {
1527
- const entries = await fs10.readdir(p);
1648
+ const entries = await fs11.readdir(p);
1528
1649
  return entries.filter((e) => !e.startsWith("."));
1529
1650
  } catch {
1530
1651
  return [];
@@ -1576,7 +1697,7 @@ function extractEnvKeys(content) {
1576
1697
  return keys;
1577
1698
  }
1578
1699
  async function scanProject(dir) {
1579
- const pkg = await readJsonSafe(path10.join(dir, "package.json"));
1700
+ const pkg = await readJsonSafe(path11.join(dir, "package.json"));
1580
1701
  const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
1581
1702
  const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
1582
1703
  const allDeps = [...deps, ...devDeps];
@@ -1604,19 +1725,19 @@ async function scanProject(dir) {
1604
1725
  const framework = detectFramework(allDeps);
1605
1726
  const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
1606
1727
  const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
1607
- const hasTests = testCommand !== null || await fileExists(path10.join(dir, "tests")) || await fileExists(path10.join(dir, "__tests__")) || await fileExists(path10.join(dir, "test"));
1728
+ const hasTests = testCommand !== null || await fileExists(path11.join(dir, "tests")) || await fileExists(path11.join(dir, "__tests__")) || await fileExists(path11.join(dir, "test"));
1608
1729
  const buildCommand = scripts.build || null;
1609
1730
  const lintCommand = scripts.lint || null;
1610
- const hasSrc = await fileExists(path10.join(dir, "src"));
1611
- const hasDocker = await fileExists(path10.join(dir, "docker-compose.yml")) || await fileExists(path10.join(dir, "Dockerfile"));
1612
- const hasCi = await fileExists(path10.join(dir, ".github/workflows"));
1613
- const hasEnvFile = await fileExists(path10.join(dir, ".env")) || await fileExists(path10.join(dir, ".env.example"));
1731
+ const hasSrc = await fileExists(path11.join(dir, "src"));
1732
+ const hasDocker = await fileExists(path11.join(dir, "docker-compose.yml")) || await fileExists(path11.join(dir, "Dockerfile"));
1733
+ const hasCi = await fileExists(path11.join(dir, ".github/workflows"));
1734
+ const hasEnvFile = await fileExists(path11.join(dir, ".env")) || await fileExists(path11.join(dir, ".env.example"));
1614
1735
  let envKeys = [];
1615
- const envExample = await readFileSafe(path10.join(dir, ".env.example"));
1736
+ const envExample = await readFileSafe(path11.join(dir, ".env.example"));
1616
1737
  if (envExample) {
1617
1738
  envKeys = extractEnvKeys(envExample);
1618
1739
  }
1619
- const claudeDir = path10.join(dir, ".claude");
1740
+ const claudeDir = path11.join(dir, ".claude");
1620
1741
  const hasClaudeDir = await fileExists(claudeDir);
1621
1742
  let existingClaudeMd = null;
1622
1743
  let existingSettings = null;
@@ -1628,21 +1749,21 @@ async function scanProject(dir) {
1628
1749
  let mcpServerCount = 0;
1629
1750
  let claudeMdLineCount = 0;
1630
1751
  if (hasClaudeDir) {
1631
- existingClaudeMd = await readFileSafe(path10.join(claudeDir, "CLAUDE.md"));
1752
+ existingClaudeMd = await readFileSafe(path11.join(claudeDir, "CLAUDE.md"));
1632
1753
  if (existingClaudeMd) {
1633
1754
  claudeMdLineCount = existingClaudeMd.split("\n").length;
1634
1755
  }
1635
- existingSettings = await readJsonSafe(path10.join(claudeDir, "settings.json"));
1636
- existingMcpConfig = await readJsonSafe(path10.join(dir, ".mcp.json"));
1756
+ existingSettings = await readJsonSafe(path11.join(claudeDir, "settings.json"));
1757
+ existingMcpConfig = await readJsonSafe(path11.join(dir, ".mcp.json"));
1637
1758
  if (existingMcpConfig?.mcpServers) {
1638
1759
  mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
1639
1760
  }
1640
- existingCommands = (await listDirSafe(path10.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1641
- existingRules = (await listDirSafe(path10.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1642
- existingSkills = await listDirSafe(path10.join(claudeDir, "skills"));
1643
- existingAgents = (await listDirSafe(path10.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1761
+ existingCommands = (await listDirSafe(path11.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1762
+ existingRules = (await listDirSafe(path11.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1763
+ existingSkills = await listDirSafe(path11.join(claudeDir, "skills"));
1764
+ existingAgents = (await listDirSafe(path11.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1644
1765
  }
1645
- const name = pkg?.name || path10.basename(dir);
1766
+ const name = pkg?.name || path11.basename(dir);
1646
1767
  const description = pkg?.description || "";
1647
1768
  return {
1648
1769
  name,
@@ -1687,31 +1808,31 @@ function simpleDiff(oldContent, newContent) {
1687
1808
  const oldLine = oldLines[i];
1688
1809
  const newLine = newLines[i];
1689
1810
  if (oldLine === void 0) {
1690
- output.push(chalk8.green(`+ ${newLine}`));
1811
+ output.push(chalk9.green(`+ ${newLine}`));
1691
1812
  } else if (newLine === void 0) {
1692
- output.push(chalk8.red(`- ${oldLine}`));
1813
+ output.push(chalk9.red(`- ${oldLine}`));
1693
1814
  } else if (oldLine !== newLine) {
1694
- output.push(chalk8.red(`- ${oldLine}`));
1695
- output.push(chalk8.green(`+ ${newLine}`));
1815
+ output.push(chalk9.red(`- ${oldLine}`));
1816
+ output.push(chalk9.green(`+ ${newLine}`));
1696
1817
  }
1697
1818
  }
1698
1819
  return output;
1699
1820
  }
1700
- async function generateDiff(spec, targetDir) {
1701
- const fileMap = buildFileMap(spec);
1821
+ async function generateDiff(spec, targetDir, options) {
1822
+ const fileMap = buildFileMap(spec, options);
1702
1823
  const results = [];
1703
1824
  for (const [relativePath, newContent] of fileMap) {
1704
- const absolutePath = path11.join(targetDir, relativePath);
1825
+ const absolutePath = path12.join(targetDir, relativePath);
1705
1826
  let oldContent = null;
1706
1827
  try {
1707
- oldContent = await fs11.readFile(absolutePath, "utf-8");
1828
+ oldContent = await fs12.readFile(absolutePath, "utf-8");
1708
1829
  } catch {
1709
1830
  }
1710
1831
  if (oldContent === null) {
1711
1832
  results.push({
1712
1833
  path: relativePath,
1713
1834
  status: "new",
1714
- diff: chalk8.green("+ NEW FILE")
1835
+ diff: chalk9.green("+ NEW FILE")
1715
1836
  });
1716
1837
  } else if (oldContent === newContent) {
1717
1838
  results.push({
@@ -1857,7 +1978,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1857
1978
  console.log(ui.success("No obvious issues found"));
1858
1979
  }
1859
1980
  if (options.auditOnly) {
1860
- console.log(chalk8.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1981
+ console.log(chalk9.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1861
1982
  return;
1862
1983
  }
1863
1984
  if (!options.yes) {
@@ -1867,19 +1988,19 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1867
1988
  default: false
1868
1989
  });
1869
1990
  if (!proceed) {
1870
- console.log(chalk8.dim("\n Aborted.\n"));
1991
+ console.log(chalk9.dim("\n Aborted.\n"));
1871
1992
  return;
1872
1993
  }
1873
1994
  }
1874
1995
  } else {
1875
- console.log(chalk8.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1996
+ console.log(chalk9.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1876
1997
  if (!options.yes) {
1877
1998
  const proceed = await confirm2({
1878
1999
  message: "Generate Claude Code environment for this project?",
1879
2000
  default: true
1880
2001
  });
1881
2002
  if (!proceed) {
1882
- console.log(chalk8.dim("\n Aborted.\n"));
2003
+ console.log(chalk9.dim("\n Aborted.\n"));
1883
2004
  return;
1884
2005
  }
1885
2006
  }
@@ -1915,8 +2036,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1915
2036
  console.log(ui.tool(name, tool.reason));
1916
2037
  }
1917
2038
  }
2039
+ const hasEnvVars = summary.envSetup.length > 0;
1918
2040
  if (options.diff) {
1919
- const diffs = await generateDiff(spec, targetDir);
2041
+ const diffs = await generateDiff(spec, targetDir, { hasEnvVars });
1920
2042
  const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
1921
2043
  if (changedDiffs.length === 0) {
1922
2044
  console.log(ui.success("No changes needed \u2014 environment is already up to date."));
@@ -1925,7 +2047,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1925
2047
  }
1926
2048
  console.log(ui.section("Changes Preview"));
1927
2049
  for (const d of changedDiffs) {
1928
- console.log(chalk8.cyan(`
2050
+ console.log(chalk9.cyan(`
1929
2051
  --- ${d.path}`));
1930
2052
  if (d.status === "new") {
1931
2053
  console.log(` ${d.diff}`);
@@ -1941,7 +2063,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1941
2063
  default: true
1942
2064
  });
1943
2065
  if (!apply) {
1944
- console.log(chalk8.dim("\n Aborted.\n"));
2066
+ console.log(chalk9.dim("\n Aborted.\n"));
1945
2067
  return;
1946
2068
  }
1947
2069
  }
@@ -1952,20 +2074,14 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1952
2074
  console.log(ui.success(`Ready! Run: $ hermes`));
1953
2075
  console.log("");
1954
2076
  } else {
1955
- const written = await writeEnvironment(spec, targetDir);
2077
+ const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
1956
2078
  console.log(ui.section("Files Written"));
1957
2079
  for (const file of written) {
1958
2080
  console.log(ui.file(file));
1959
2081
  }
1960
- if (summary.envSetup.length > 0) {
1961
- console.log(ui.section("Setup Required"));
1962
- const seen = /* @__PURE__ */ new Set();
1963
- for (const env of summary.envSetup) {
1964
- if (seen.has(env.envVar)) continue;
1965
- seen.add(env.envVar);
1966
- console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1967
- console.log("");
1968
- }
2082
+ if (hasEnvVars) {
2083
+ await collectAndWriteKeys(summary.envSetup, targetDir);
2084
+ console.log("");
1969
2085
  }
1970
2086
  if (summary.pluginCommands.length > 0) {
1971
2087
  console.log(ui.section("Plugins"));
@@ -1982,7 +2098,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1982
2098
 
1983
2099
  // src/commands/doctor.ts
1984
2100
  import { Command as Command7 } from "commander";
1985
- import chalk9 from "chalk";
2101
+ import chalk10 from "chalk";
1986
2102
  function runChecks(profile) {
1987
2103
  const checks = [];
1988
2104
  if (!profile.existingClaudeMd) {
@@ -2114,12 +2230,12 @@ var doctorCommand = new Command7("doctor").description(
2114
2230
  ).action(async () => {
2115
2231
  printFullBanner("Doctor");
2116
2232
  const targetDir = process.cwd();
2117
- console.log(chalk9.dim(" Checking .claude/ environment...\n"));
2233
+ console.log(chalk10.dim(" Checking .claude/ environment...\n"));
2118
2234
  const profile = await scanProject(targetDir);
2119
2235
  if (!profile.hasClaudeDir) {
2120
2236
  console.log(ui.error("No .claude/ directory found.\n"));
2121
2237
  console.log(
2122
- chalk9.dim(" Run ") + chalk9.bold("kairn describe") + chalk9.dim(" or ") + chalk9.bold("kairn optimize") + chalk9.dim(" to generate one.\n")
2238
+ chalk10.dim(" Run ") + chalk10.bold("kairn describe") + chalk10.dim(" or ") + chalk10.bold("kairn optimize") + chalk10.dim(" to generate one.\n")
2123
2239
  );
2124
2240
  process.exit(1);
2125
2241
  }
@@ -2142,7 +2258,7 @@ var doctorCommand = new Command7("doctor").description(
2142
2258
  return sum;
2143
2259
  }, 0);
2144
2260
  const percentage = Math.round(score / maxScore * 100);
2145
- const scoreColor = percentage >= 80 ? chalk9.green : percentage >= 50 ? chalk9.yellow : chalk9.red;
2261
+ const scoreColor = percentage >= 80 ? chalk10.green : percentage >= 50 ? chalk10.yellow : chalk10.red;
2146
2262
  console.log(
2147
2263
  `
2148
2264
  Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
@@ -2150,14 +2266,14 @@ var doctorCommand = new Command7("doctor").description(
2150
2266
  );
2151
2267
  if (percentage < 80) {
2152
2268
  console.log(
2153
- chalk9.dim(" Run ") + chalk9.bold("kairn optimize") + chalk9.dim(" to fix issues.\n")
2269
+ chalk10.dim(" Run ") + chalk10.bold("kairn optimize") + chalk10.dim(" to fix issues.\n")
2154
2270
  );
2155
2271
  }
2156
2272
  });
2157
2273
 
2158
2274
  // src/commands/registry.ts
2159
2275
  import { Command as Command8 } from "commander";
2160
- import chalk10 from "chalk";
2276
+ import chalk11 from "chalk";
2161
2277
  import { input as input2, select as select2 } from "@inquirer/prompts";
2162
2278
  var listCommand2 = new Command8("list").description("List tools in the registry").option("--category <cat>", "Filter by category").option("--user-only", "Show only user-defined tools").action(async (options) => {
2163
2279
  printCompactBanner();
@@ -2182,7 +2298,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2182
2298
  );
2183
2299
  }
2184
2300
  if (tools.length === 0) {
2185
- console.log(chalk10.dim("\n No tools found.\n"));
2301
+ console.log(chalk11.dim("\n No tools found.\n"));
2186
2302
  return;
2187
2303
  }
2188
2304
  const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
@@ -2196,13 +2312,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2196
2312
  `tier ${tool.tier}`,
2197
2313
  tool.auth
2198
2314
  ].join(", ");
2199
- console.log(` ${ui.accent(tool.id)}` + chalk10.dim(` (${meta})`));
2200
- console.log(chalk10.dim(` ${tool.description}`));
2315
+ console.log(` ${ui.accent(tool.id)}` + chalk11.dim(` (${meta})`));
2316
+ console.log(chalk11.dim(` ${tool.description}`));
2201
2317
  if (tool.best_for.length > 0) {
2202
- console.log(chalk10.dim(` Best for: ${tool.best_for.join(", ")}`));
2318
+ console.log(chalk11.dim(` Best for: ${tool.best_for.join(", ")}`));
2203
2319
  }
2204
2320
  if (isUser) {
2205
- console.log(chalk10.yellow(" [USER-DEFINED]"));
2321
+ console.log(chalk11.yellow(" [USER-DEFINED]"));
2206
2322
  }
2207
2323
  console.log("");
2208
2324
  }
@@ -2210,7 +2326,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2210
2326
  const shownUser = tools.filter((t) => userIds.has(t.id)).length;
2211
2327
  const shownBundled = totalShown - shownUser;
2212
2328
  console.log(
2213
- chalk10.dim(
2329
+ chalk11.dim(
2214
2330
  ` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
2215
2331
  ) + "\n"
2216
2332
  );
@@ -2334,20 +2450,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
2334
2450
 
2335
2451
  // src/commands/templates.ts
2336
2452
  import { Command as Command9 } from "commander";
2337
- import chalk11 from "chalk";
2338
- import fs12 from "fs/promises";
2339
- import path12 from "path";
2453
+ import chalk12 from "chalk";
2454
+ import fs13 from "fs/promises";
2455
+ import path13 from "path";
2340
2456
  var templatesCommand = new Command9("templates").description("Browse available templates").option("--category <cat>", "filter templates by category keyword").option("--json", "output raw JSON array").action(async (options) => {
2341
2457
  printCompactBanner();
2342
2458
  const templatesDir = getTemplatesDir();
2343
2459
  let files;
2344
2460
  try {
2345
- files = await fs12.readdir(templatesDir);
2461
+ files = await fs13.readdir(templatesDir);
2346
2462
  } catch {
2347
2463
  console.log(
2348
- chalk11.dim(
2464
+ chalk12.dim(
2349
2465
  " No templates found. Templates will be installed with "
2350
- ) + chalk11.bold("kairn init") + chalk11.dim(
2466
+ ) + chalk12.bold("kairn init") + chalk12.dim(
2351
2467
  " or you can add .json files to ~/.kairn/templates/\n"
2352
2468
  )
2353
2469
  );
@@ -2356,9 +2472,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
2356
2472
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2357
2473
  if (jsonFiles.length === 0) {
2358
2474
  console.log(
2359
- chalk11.dim(
2475
+ chalk12.dim(
2360
2476
  " No templates found. Templates will be installed with "
2361
- ) + chalk11.bold("kairn init") + chalk11.dim(
2477
+ ) + chalk12.bold("kairn init") + chalk12.dim(
2362
2478
  " or you can add .json files to ~/.kairn/templates/\n"
2363
2479
  )
2364
2480
  );
@@ -2367,8 +2483,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
2367
2483
  const templates = [];
2368
2484
  for (const file of jsonFiles) {
2369
2485
  try {
2370
- const data = await fs12.readFile(
2371
- path12.join(templatesDir, file),
2486
+ const data = await fs13.readFile(
2487
+ path13.join(templatesDir, file),
2372
2488
  "utf-8"
2373
2489
  );
2374
2490
  const spec = JSON.parse(data);
@@ -2386,7 +2502,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
2386
2502
  }
2387
2503
  if (filtered.length === 0) {
2388
2504
  console.log(
2389
- chalk11.dim(` No templates matched category "${options.category}".
2505
+ chalk12.dim(` No templates matched category "${options.category}".
2390
2506
  `)
2391
2507
  );
2392
2508
  return;
@@ -2397,8 +2513,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
2397
2513
  const toolCount = spec.tools?.length ?? 0;
2398
2514
  const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
2399
2515
  const ruleCount = Object.keys(spec.harness?.rules ?? {}).length;
2400
- console.log(ui.kv("Name", chalk11.bold(spec.name)));
2401
- console.log(ui.kv("ID", chalk11.dim(spec.id)));
2516
+ console.log(ui.kv("Name", chalk12.bold(spec.name)));
2517
+ console.log(ui.kv("ID", chalk12.dim(spec.id)));
2402
2518
  console.log(ui.kv("Description", spec.description));
2403
2519
  console.log(
2404
2520
  ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
@@ -2406,16 +2522,139 @@ var templatesCommand = new Command9("templates").description("Browse available t
2406
2522
  console.log("");
2407
2523
  }
2408
2524
  console.log(
2409
- chalk11.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2525
+ chalk12.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2410
2526
  `)
2411
2527
  );
2412
2528
  });
2413
2529
 
2530
+ // src/commands/keys.ts
2531
+ import { Command as Command10 } from "commander";
2532
+ import { password as password3 } from "@inquirer/prompts";
2533
+ import chalk13 from "chalk";
2534
+ import fs14 from "fs/promises";
2535
+ import path14 from "path";
2536
+ var keysCommand = new Command10("keys").description("Add or update API keys for the current environment").option("--show", "Show which keys are set vs missing").action(async (options) => {
2537
+ printCompactBanner();
2538
+ const targetDir = process.cwd();
2539
+ const requiredVars = await detectRequiredEnvVars(targetDir);
2540
+ if (requiredVars.length === 0) {
2541
+ console.log(
2542
+ ui.info("No MCP servers found in .mcp.json \u2014 no API keys needed.")
2543
+ );
2544
+ console.log("");
2545
+ return;
2546
+ }
2547
+ const existing = await readEnvFile(targetDir);
2548
+ const registry = await loadRegistry();
2549
+ const envSetupMap = /* @__PURE__ */ new Map();
2550
+ for (const tool of registry) {
2551
+ if (!tool.env_vars) continue;
2552
+ for (const ev of tool.env_vars) {
2553
+ if (requiredVars.includes(ev.name)) {
2554
+ envSetupMap.set(ev.name, {
2555
+ toolName: tool.name,
2556
+ envVar: ev.name,
2557
+ description: ev.description,
2558
+ signupUrl: tool.signup_url
2559
+ });
2560
+ }
2561
+ }
2562
+ }
2563
+ for (const varName of requiredVars) {
2564
+ if (!envSetupMap.has(varName)) {
2565
+ envSetupMap.set(varName, {
2566
+ toolName: "unknown",
2567
+ envVar: varName,
2568
+ description: "Required by MCP server"
2569
+ });
2570
+ }
2571
+ }
2572
+ if (options.show) {
2573
+ console.log(ui.section("API Key Status"));
2574
+ console.log("");
2575
+ for (const varName of requiredVars) {
2576
+ const value = existing.get(varName);
2577
+ const info = envSetupMap.get(varName);
2578
+ const toolLabel = info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "";
2579
+ if (value && value.length > 0) {
2580
+ const masked = value.slice(0, 4) + "\u2022".repeat(Math.max(0, value.length - 4));
2581
+ console.log(chalk13.green(` \u2713 ${varName}`) + toolLabel + chalk13.dim(` = ${masked}`));
2582
+ } else {
2583
+ console.log(chalk13.yellow(` \u2717 ${varName}`) + toolLabel + chalk13.dim(" = (not set)"));
2584
+ if (info?.signupUrl) {
2585
+ console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
2586
+ }
2587
+ }
2588
+ }
2589
+ const setCount = requiredVars.filter((v) => {
2590
+ const val = existing.get(v);
2591
+ return val && val.length > 0;
2592
+ }).length;
2593
+ const missingCount = requiredVars.length - setCount;
2594
+ console.log("");
2595
+ if (missingCount === 0) {
2596
+ console.log(ui.success(`All ${setCount} key(s) configured`));
2597
+ } else {
2598
+ console.log(
2599
+ ui.warn(`${missingCount} key(s) missing \u2014 run ${chalk13.bold("kairn keys")} to add them`)
2600
+ );
2601
+ }
2602
+ console.log("");
2603
+ return;
2604
+ }
2605
+ const missing = requiredVars.filter((v) => {
2606
+ const val = existing.get(v);
2607
+ return !val || val.length === 0;
2608
+ });
2609
+ if (missing.length === 0) {
2610
+ console.log(ui.success("All API keys are already configured."));
2611
+ console.log(chalk13.dim(" Use --show to see current keys.\n"));
2612
+ return;
2613
+ }
2614
+ console.log(ui.section("API Keys"));
2615
+ console.log(chalk13.dim(` ${missing.length} key(s) need to be set. Press Enter to skip.
2616
+ `));
2617
+ const envEntries = new Map(existing);
2618
+ let keysEntered = 0;
2619
+ for (const varName of missing) {
2620
+ const info = envSetupMap.get(varName);
2621
+ console.log(
2622
+ chalk13.bold(` ${varName}`) + (info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "")
2623
+ );
2624
+ if (info?.signupUrl) {
2625
+ console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
2626
+ }
2627
+ const value = await password3({
2628
+ message: varName,
2629
+ mask: "\u2022"
2630
+ });
2631
+ if (value && value.trim()) {
2632
+ envEntries.set(varName, value.trim());
2633
+ console.log(chalk13.green(" \u2713 saved\n"));
2634
+ keysEntered++;
2635
+ } else {
2636
+ console.log(chalk13.dim(" (skipped)\n"));
2637
+ }
2638
+ }
2639
+ const envLines = [
2640
+ "# Generated by Kairn \u2014 API keys for MCP servers",
2641
+ "# Do NOT commit this file to git",
2642
+ ""
2643
+ ];
2644
+ for (const [key, value] of envEntries) {
2645
+ envLines.push(`${key}=${value}`);
2646
+ }
2647
+ const envPath = path14.join(targetDir, ".env");
2648
+ await fs14.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
2649
+ console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
2650
+ console.log("");
2651
+ });
2652
+
2414
2653
  // src/cli.ts
2415
- var program = new Command10();
2654
+ var program = new Command11();
2416
2655
  program.name("kairn").description(
2417
2656
  "Compile natural language intent into optimized Claude Code environments"
2418
- ).version("1.7.0").option("--no-color", "Disable colored output");
2657
+ ).version("1.8.0").option("--no-color", "Disable colored output");
2419
2658
  program.addCommand(initCommand);
2420
2659
  program.addCommand(describeCommand);
2421
2660
  program.addCommand(optimizeCommand);
@@ -2425,8 +2664,9 @@ program.addCommand(updateRegistryCommand);
2425
2664
  program.addCommand(doctorCommand);
2426
2665
  program.addCommand(registryCommand);
2427
2666
  program.addCommand(templatesCommand);
2667
+ program.addCommand(keysCommand);
2428
2668
  if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
2429
- chalk12.level = 0;
2669
+ chalk14.level = 0;
2430
2670
  }
2431
2671
  program.parse();
2432
2672
  //# sourceMappingURL=cli.js.map