kairn-cli 1.7.0 → 1.9.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)}`,
@@ -338,8 +338,8 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
338
338
 
339
339
  // src/commands/describe.ts
340
340
  import { Command as Command2 } from "commander";
341
- import { input, confirm } from "@inquirer/prompts";
342
- import chalk4 from "chalk";
341
+ import { input, confirm, select as select2 } from "@inquirer/prompts";
342
+ import chalk5 from "chalk";
343
343
  import ora from "ora";
344
344
 
345
345
  // src/compiler/compile.ts
@@ -614,6 +614,19 @@ When generating for Hermes runtime, the same EnvironmentSpec JSON is produced. T
614
614
 
615
615
  The LLM output format does not change. Adapter-level conversion happens post-compilation.
616
616
 
617
+ ## Autonomy Levels
618
+
619
+ The user may specify an autonomy level (1-4). This affects CLAUDE.md content:
620
+
621
+ - **Level 1 (Guided):** Add a "Workflow" section showing recommended command flow (e.g., spec \u2192 sprint \u2192 plan \u2192 code \u2192 prove \u2192 grill \u2192 commit) and a "When to Use What" reference table.
622
+ - **Level 2 (Assisted):** Level 1 content + mention /project:loop in the workflow section and @pm in the agents section of CLAUDE.md.
623
+ - **Level 3 (Autonomous):** Level 2 content + mention /project:auto and worktree-based PR delivery workflow.
624
+ - **Level 4 (Full Auto):** Level 3 content + add a prominent warning section about autonomous operation.
625
+
626
+ The autonomy-specific commands, agents, and hooks are injected post-compilation. Focus on tailoring the CLAUDE.md content and workflow guidance for the selected level.
627
+
628
+ If no autonomy level is specified, assume Level 1 (Guided).
629
+
617
630
  ## Output Schema
618
631
 
619
632
  Return ONLY valid JSON matching this structure:
@@ -884,7 +897,8 @@ async function compile(intent, onProgress) {
884
897
  id: `env_${crypto.randomUUID()}`,
885
898
  intent,
886
899
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
887
- ...parsed
900
+ ...parsed,
901
+ autonomy_level: parsed.autonomy_level ?? 1
888
902
  };
889
903
  validateSpec(spec, onProgress);
890
904
  await ensureDirs();
@@ -919,6 +933,290 @@ async function generateClarifications(intent, onProgress) {
919
933
  // src/adapter/claude-code.ts
920
934
  import fs5 from "fs/promises";
921
935
  import path5 from "path";
936
+
937
+ // src/autonomy.ts
938
+ var AUTONOMY_LABELS = {
939
+ 1: "Guided",
940
+ 2: "Assisted",
941
+ 3: "Autonomous",
942
+ 4: "Full Auto"
943
+ };
944
+ var WELCOME_HOOK = {
945
+ matcher: "",
946
+ hooks: [{
947
+ type: "command",
948
+ command: "if [ ! -f .claude/.toured ]; then echo '\\n Welcome to your Kairn environment!\\n Type /project:tour for a walkthrough, or /project:help for a quick reference.\\n'; fi"
949
+ }]
950
+ };
951
+ var TOUR_COMMAND = `# Environment Tour
952
+
953
+ Welcome! Let me show you around this Kairn environment.
954
+
955
+ ## Your Commands
956
+ Read .claude/commands/ and list each one with a one-line description.
957
+ Group them by workflow phase:
958
+
959
+ PLAN: /project:spec, /project:sprint, /project:plan
960
+ BUILD: (just start coding \u2014 Claude reads CLAUDE.md automatically)
961
+ VERIFY: /project:prove, /project:grill, /project:test
962
+ SHIP: /project:commit, /project:review
963
+ MANAGE: /project:status, /project:tasks, /project:reset
964
+
965
+ ## Your Agents
966
+ Read .claude/agents/ and explain each one with how to invoke it.
967
+
968
+ ## Your MCP Tools
969
+ !claude mcp list 2>/dev/null || echo "Run /mcp in Claude Code to see active tools"
970
+
971
+ ## Workflow
972
+ For this project, the recommended flow is:
973
+ spec \u2192 sprint \u2192 plan \u2192 code \u2192 prove \u2192 grill \u2192 commit
974
+
975
+ ## Tips
976
+ - Type / to see all commands
977
+ - Type @ to invoke an agent
978
+ - Paste raw errors \u2014 don't summarize them
979
+ - Use subagents for deep investigation
980
+ - Say "update CLAUDE.md" after any correction
981
+
982
+ After showing the tour, create .claude/.toured to suppress the welcome message:
983
+ !touch .claude/.toured
984
+
985
+ Ready to start? Try /project:spec to define your first feature.`;
986
+ function buildQuickstart(spec) {
987
+ const commands = Object.keys(spec.harness.commands || {});
988
+ const agents = Object.keys(spec.harness.agents || {});
989
+ const commandList = commands.map((c) => `- \`/project:${c}\``).join("\n");
990
+ const agentList = agents.length > 0 ? agents.map((a) => `- \`@${a}\``).join("\n") : "- (none configured)";
991
+ return `# Quick Start Guide
992
+
993
+ This environment was generated by Kairn. Here's how to use it.
994
+
995
+ ## First Time
996
+ 1. Open terminal in this directory
997
+ 2. Run \`claude\`
998
+ 3. Type \`/project:tour\` for a guided walkthrough
999
+
1000
+ ## Daily Workflow
1001
+ 1. \`/project:status\` \u2014 see where things stand
1002
+ 2. \`/project:spec\` \u2014 define what to build (Claude will interview you)
1003
+ 3. Start coding \u2014 Claude follows CLAUDE.md automatically
1004
+ 4. \`/project:prove\` \u2014 verify your work
1005
+ 5. \`/project:commit\` \u2014 ship it
1006
+
1007
+ ## Commands
1008
+ ${commandList}
1009
+
1010
+ ## Agents
1011
+ ${agentList}
1012
+
1013
+ ## Need Help?
1014
+ Type \`/project:help\` in Claude Code for a quick reference.
1015
+ `;
1016
+ }
1017
+ var LOOP_COMMAND_CODE = `# Development Loop
1018
+
1019
+ Run an assisted development cycle for the next feature.
1020
+
1021
+ ## Phase 1: SPEC
1022
+ Review docs/TODO.md and docs/SPRINT.md.
1023
+ If no sprint is defined, run /project:spec to interview the user.
1024
+ Wait for user approval of the spec.
1025
+
1026
+ ## Phase 2: PLAN
1027
+ Read the approved spec in docs/SPRINT.md.
1028
+ Plan the implementation: files to change, tests to write, approach.
1029
+ Write plan to docs/DECISIONS.md.
1030
+ Wait for user approval of the plan.
1031
+
1032
+ ## Phase 3: IMPLEMENT
1033
+ Follow the plan. Implement the feature.
1034
+ Run tests after each change.
1035
+ Commit each logical unit: "feat: description"
1036
+
1037
+ ## Phase 4: VERIFY
1038
+ Run /project:prove to verify the implementation.
1039
+ If confidence is LOW or MEDIUM, fix issues and re-verify.
1040
+
1041
+ ## Phase 5: REVIEW
1042
+ Run /project:grill for adversarial review.
1043
+ Fix any BLOCKERs.
1044
+
1045
+ ## Phase 6: SHIP
1046
+ Run /project:commit.
1047
+ Report what was built and what's next from docs/TODO.md.
1048
+
1049
+ Then ask: "Continue to next feature?"
1050
+ If yes, return to Phase 1.`;
1051
+ var LOOP_COMMAND_RESEARCH = `# Research Loop
1052
+
1053
+ Run an assisted research cycle.
1054
+
1055
+ ## Phase 1: QUESTION
1056
+ Review docs/TODO.md for the next research question.
1057
+ If none, ask the user what to investigate.
1058
+
1059
+ ## Phase 2: RESEARCH
1060
+ Search, extract, and analyze sources.
1061
+ Log findings to docs/SOURCES.md and docs/LEARNINGS.md.
1062
+
1063
+ ## Phase 3: SYNTHESIZE
1064
+ Review all findings. Write structured summary to docs/SUMMARY.md.
1065
+ Cite all sources.
1066
+
1067
+ ## Phase 4: REVIEW
1068
+ Present the summary. Ask the user for feedback.
1069
+ Revise based on feedback.
1070
+
1071
+ ## Phase 5: NEXT
1072
+ Update docs/TODO.md \u2014 mark question as done, identify follow-ups.
1073
+ Ask: "Continue to next question?"`;
1074
+ var PM_AGENT = `---
1075
+ name: pm
1076
+ description: Project manager agent. Maintains roadmap, specs features, prioritizes work.
1077
+ model: opus
1078
+ ---
1079
+
1080
+ You are a project manager for this codebase.
1081
+
1082
+ Your responsibilities:
1083
+ 1. Maintain docs/TODO.md \u2014 keep it prioritized and current
1084
+ 2. Write specs to docs/SPRINT.md when asked
1085
+ 3. Review completed work and suggest what's next
1086
+ 4. Track decisions in docs/DECISIONS.md
1087
+ 5. Track learnings in docs/LEARNINGS.md
1088
+
1089
+ When invoked:
1090
+ - Read all docs/ files to understand current state
1091
+ - Read recent git log for what changed
1092
+ - Suggest the highest-priority next task
1093
+ - If asked to spec something, interview the user (5-8 questions)
1094
+
1095
+ You do NOT write code. You plan, spec, and prioritize.`;
1096
+ var AUTO_COMMAND = `# Autonomous Development
1097
+
1098
+ PM-driven development loop with PR delivery.
1099
+
1100
+ ## Phase 1: PLAN (@pm)
1101
+ Use @pm to:
1102
+ - Read docs/TODO.md and docs/SPRINT.md
1103
+ - Select the highest-priority unfinished task
1104
+ - Write a spec to docs/SPRINT.md
1105
+ - Present the spec for approval
1106
+
1107
+ Wait for user approval. If approved, proceed.
1108
+
1109
+ ## Phase 2: BRANCH
1110
+ Create an isolated worktree:
1111
+ git worktree add ../project-feat-{name} -b feat/{name}
1112
+ All implementation happens in the worktree.
1113
+
1114
+ ## Phase 3: IMPLEMENT
1115
+ In the worktree directory:
1116
+ - Follow the spec in docs/SPRINT.md
1117
+ - Build, test, commit after each change
1118
+
1119
+ ## Phase 4: VERIFY
1120
+ Run verification:
1121
+ - Static analysis (linting, type checks)
1122
+ - Run functional tests
1123
+ - If NEEDS FIXES: fix and re-verify
1124
+
1125
+ ## Phase 5: PR
1126
+ Create a pull request:
1127
+ gh pr create --title "feat: {name}" --body "{spec + QA report}"
1128
+
1129
+ ## Phase 6: NEXT
1130
+ Report:
1131
+ "PR #{N} ready for review: {link}
1132
+ Next priority from TODO.md: {next task}
1133
+ Continue? (y/n)"
1134
+
1135
+ If yes, return to Phase 1 with next task.`;
1136
+ var AUTOPILOT_COMMAND = `# Autopilot Mode
1137
+
1138
+ Continuous autonomous development. The PM plans, the loop executes,
1139
+ PRs are opened automatically. You review when ready.
1140
+
1141
+ ## Configuration
1142
+ - Max features per session: 5 (prevent runaway)
1143
+ - Stop on: test failure, build error, or blocked dependency
1144
+ - All work in isolated worktrees
1145
+ - Every feature = one PR
1146
+
1147
+ ## The Loop
1148
+ Repeat until max features reached or stopped:
1149
+ 1. @pm selects next priority from docs/TODO.md
1150
+ 2. Create worktree + branch
1151
+ 3. Implement the feature
1152
+ 4. Run verification (build, test, lint)
1153
+ 5. Open PR via gh
1154
+ 6. Report status
1155
+ 7. Move to next feature
1156
+
1157
+ ## Stop Conditions
1158
+ - Max 5 features per autopilot session
1159
+ - Any BLOCKER from verification
1160
+ - Build failure that can't be resolved in 3 attempts
1161
+ - User presses Escape`;
1162
+ var AUTOPILOT_WARNING = `
1163
+ ## Autopilot Mode Active
1164
+ This environment is configured for autonomous operation.
1165
+ The @pm agent plans features and /project:autopilot executes them.
1166
+ All changes are delivered as PRs \u2014 review before merging.
1167
+ Stop conditions: max 5 features, test failure, or Escape.`;
1168
+ function isResearchProject(spec) {
1169
+ const commands = spec.harness.commands ?? {};
1170
+ return "research" in commands || "summarize" in commands;
1171
+ }
1172
+ function applyAutonomyLevel(spec) {
1173
+ const level = spec.autonomy_level ?? 1;
1174
+ const commands = spec.harness.commands ?? {};
1175
+ const agents = spec.harness.agents ?? {};
1176
+ const docs = spec.harness.docs ?? {};
1177
+ const settings = spec.harness.settings ?? {};
1178
+ if (level >= 1) {
1179
+ if (!("tour" in commands)) {
1180
+ commands.tour = TOUR_COMMAND;
1181
+ }
1182
+ docs.QUICKSTART = buildQuickstart(spec);
1183
+ const hooks = settings.hooks ?? {};
1184
+ const sessionStart = hooks.SessionStart ?? [];
1185
+ sessionStart.push(WELCOME_HOOK);
1186
+ hooks.SessionStart = sessionStart;
1187
+ settings.hooks = hooks;
1188
+ }
1189
+ if (level >= 2) {
1190
+ if (!("loop" in commands)) {
1191
+ commands.loop = isResearchProject(spec) ? LOOP_COMMAND_RESEARCH : LOOP_COMMAND_CODE;
1192
+ }
1193
+ if (!("pm" in agents)) {
1194
+ agents.pm = PM_AGENT;
1195
+ }
1196
+ }
1197
+ if (level >= 3) {
1198
+ if (!("auto" in commands)) {
1199
+ commands.auto = AUTO_COMMAND;
1200
+ }
1201
+ }
1202
+ if (level >= 4) {
1203
+ if (!("autopilot" in commands)) {
1204
+ commands.autopilot = AUTOPILOT_COMMAND;
1205
+ }
1206
+ if (spec.harness.claude_md && !spec.harness.claude_md.includes("Autopilot Mode")) {
1207
+ spec.harness.claude_md += "\n" + AUTOPILOT_WARNING;
1208
+ }
1209
+ }
1210
+ spec.harness.commands = commands;
1211
+ spec.harness.agents = agents;
1212
+ spec.harness.docs = docs;
1213
+ spec.harness.settings = settings;
1214
+ }
1215
+ function autonomyLabel(level) {
1216
+ return AUTONOMY_LABELS[level];
1217
+ }
1218
+
1219
+ // src/adapter/claude-code.ts
922
1220
  var STATUS_LINE = {
923
1221
  command: `printf '%s | %s tasks' "$(git branch --show-current 2>/dev/null || echo 'no-git')" "$(grep -c '\\- \\[ \\]' docs/TODO.md 2>/dev/null || echo 0)"`
924
1222
  };
@@ -926,25 +1224,40 @@ function isCodeProject(spec) {
926
1224
  const commands = spec.harness.commands ?? {};
927
1225
  return "status" in commands || "test" in commands;
928
1226
  }
929
- function resolveSettings(spec) {
1227
+ var ENV_LOADER_HOOK = {
1228
+ matcher: "",
1229
+ hooks: [{
1230
+ type: "command",
1231
+ command: 'if [ -f .env ] && [ -n "$CLAUDE_ENV_FILE" ]; then grep -v "^#" .env | grep -v "^$" | grep "=" >> "$CLAUDE_ENV_FILE"; fi'
1232
+ }]
1233
+ };
1234
+ function resolveSettings(spec, options) {
930
1235
  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;
1236
+ const base = settings && Object.keys(settings).length > 0 ? { ...settings } : {};
1237
+ if (!("statusLine" in base) && isCodeProject(spec)) {
1238
+ base.statusLine = STATUS_LINE;
1239
+ }
1240
+ if (options?.hasEnvVars) {
1241
+ const hooks = base.hooks ?? {};
1242
+ const sessionStart = hooks.SessionStart ?? [];
1243
+ sessionStart.push(ENV_LOADER_HOOK);
1244
+ hooks.SessionStart = sessionStart;
1245
+ base.hooks = hooks;
1246
+ }
1247
+ if (Object.keys(base).length === 0) return null;
1248
+ return base;
937
1249
  }
938
1250
  async function writeFile(filePath, content) {
939
1251
  await fs5.mkdir(path5.dirname(filePath), { recursive: true });
940
1252
  await fs5.writeFile(filePath, content, "utf-8");
941
1253
  }
942
- function buildFileMap(spec) {
1254
+ function buildFileMap(spec, options) {
1255
+ applyAutonomyLevel(spec);
943
1256
  const files = /* @__PURE__ */ new Map();
944
1257
  if (spec.harness.claude_md) {
945
1258
  files.set(".claude/CLAUDE.md", spec.harness.claude_md);
946
1259
  }
947
- const resolvedSettings = resolveSettings(spec);
1260
+ const resolvedSettings = resolveSettings(spec, options);
948
1261
  if (resolvedSettings) {
949
1262
  files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
950
1263
  }
@@ -981,7 +1294,8 @@ function buildFileMap(spec) {
981
1294
  }
982
1295
  return files;
983
1296
  }
984
- async function writeEnvironment(spec, targetDir) {
1297
+ async function writeEnvironment(spec, targetDir, options) {
1298
+ applyAutonomyLevel(spec);
985
1299
  const claudeDir = path5.join(targetDir, ".claude");
986
1300
  const written = [];
987
1301
  if (spec.harness.claude_md) {
@@ -989,7 +1303,7 @@ async function writeEnvironment(spec, targetDir) {
989
1303
  await writeFile(p, spec.harness.claude_md);
990
1304
  written.push(".claude/CLAUDE.md");
991
1305
  }
992
- const resolvedSettings = resolveSettings(spec);
1306
+ const resolvedSettings = resolveSettings(spec, options);
993
1307
  if (resolvedSettings) {
994
1308
  const p = path5.join(claudeDir, "settings.json");
995
1309
  await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
@@ -1192,6 +1506,114 @@ async function writeHermesEnvironment(spec, registry) {
1192
1506
  return written;
1193
1507
  }
1194
1508
 
1509
+ // src/secrets.ts
1510
+ import { password as password2 } from "@inquirer/prompts";
1511
+ import chalk4 from "chalk";
1512
+ import fs7 from "fs/promises";
1513
+ import path7 from "path";
1514
+ async function collectAndWriteKeys(envSetup, targetDir) {
1515
+ console.log(ui.section("API Keys"));
1516
+ console.log(
1517
+ chalk4.dim(" Some tools need API keys. Enter them now or press Enter to skip.\n")
1518
+ );
1519
+ const envEntries = [
1520
+ "# Generated by Kairn \u2014 API keys for MCP servers",
1521
+ "# Do NOT commit this file to git",
1522
+ ""
1523
+ ];
1524
+ let keysEntered = 0;
1525
+ let keysSkipped = 0;
1526
+ const seen = /* @__PURE__ */ new Set();
1527
+ for (const env of envSetup) {
1528
+ if (seen.has(env.envVar)) continue;
1529
+ seen.add(env.envVar);
1530
+ console.log(chalk4.bold(` ${env.envVar}`) + chalk4.dim(` (${env.toolName})`));
1531
+ if (env.signupUrl) {
1532
+ console.log(chalk4.dim(` Get one at: ${env.signupUrl}`));
1533
+ }
1534
+ const value = await password2({
1535
+ message: env.envVar,
1536
+ mask: "\u2022"
1537
+ });
1538
+ if (value && value.trim()) {
1539
+ envEntries.push(`${env.envVar}=${value.trim()}`);
1540
+ console.log(chalk4.green(" \u2713 saved\n"));
1541
+ keysEntered++;
1542
+ } else {
1543
+ envEntries.push(`${env.envVar}=`);
1544
+ console.log(chalk4.dim(" (skipped)\n"));
1545
+ keysSkipped++;
1546
+ }
1547
+ }
1548
+ const envPath = path7.join(targetDir, ".env");
1549
+ await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
1550
+ await ensureGitignoreEntry(targetDir, ".env");
1551
+ console.log(chalk4.green(` \u2713 ${keysEntered} key(s) saved to .env (gitignored)`));
1552
+ if (keysSkipped > 0) {
1553
+ console.log(chalk4.dim(" Skipped keys can be added later: kairn keys"));
1554
+ }
1555
+ return { keysEntered, keysSkipped, envPath };
1556
+ }
1557
+ async function writeEmptyEnvFile(envSetup, targetDir) {
1558
+ const envEntries = [
1559
+ "# Generated by Kairn \u2014 API keys for MCP servers",
1560
+ "# Do NOT commit this file to git",
1561
+ ""
1562
+ ];
1563
+ const seen = /* @__PURE__ */ new Set();
1564
+ for (const env of envSetup) {
1565
+ if (seen.has(env.envVar)) continue;
1566
+ seen.add(env.envVar);
1567
+ envEntries.push(`${env.envVar}=`);
1568
+ }
1569
+ const envPath = path7.join(targetDir, ".env");
1570
+ await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
1571
+ await ensureGitignoreEntry(targetDir, ".env");
1572
+ }
1573
+ async function ensureGitignoreEntry(targetDir, entry) {
1574
+ const gitignorePath = path7.join(targetDir, ".gitignore");
1575
+ let gitignore = "";
1576
+ try {
1577
+ gitignore = await fs7.readFile(gitignorePath, "utf-8");
1578
+ } catch {
1579
+ }
1580
+ if (!gitignore.split("\n").some((line) => line.trim() === entry)) {
1581
+ const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
1582
+ await fs7.writeFile(gitignorePath, gitignore + separator + entry + "\n", "utf-8");
1583
+ }
1584
+ }
1585
+ async function readEnvFile(targetDir) {
1586
+ const envPath = path7.join(targetDir, ".env");
1587
+ const entries = /* @__PURE__ */ new Map();
1588
+ try {
1589
+ const content = await fs7.readFile(envPath, "utf-8");
1590
+ for (const line of content.split("\n")) {
1591
+ const trimmed = line.trim();
1592
+ if (!trimmed || trimmed.startsWith("#")) continue;
1593
+ const eqIndex = trimmed.indexOf("=");
1594
+ if (eqIndex === -1) continue;
1595
+ const key = trimmed.slice(0, eqIndex);
1596
+ const value = trimmed.slice(eqIndex + 1);
1597
+ entries.set(key, value);
1598
+ }
1599
+ } catch {
1600
+ }
1601
+ return entries;
1602
+ }
1603
+ async function detectRequiredEnvVars(targetDir) {
1604
+ const mcpPath = path7.join(targetDir, ".mcp.json");
1605
+ const envVars = /* @__PURE__ */ new Set();
1606
+ try {
1607
+ const content = await fs7.readFile(mcpPath, "utf-8");
1608
+ const matches = content.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
1609
+ for (const match of matches) {
1610
+ envVars.add(match[1]);
1611
+ }
1612
+ } catch {
1613
+ }
1614
+ return [...envVars];
1615
+ }
1616
+
1195
1617
  // src/commands/describe.ts
1196
1618
  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
1619
  printFullBanner("The Agent Environment Compiler");
@@ -1200,7 +1622,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1200
1622
  console.log(
1201
1623
  ui.errorBox(
1202
1624
  "No configuration found",
1203
- `Run ${chalk4.bold("kairn init")} to set up your API key.`
1625
+ `Run ${chalk5.bold("kairn init")} to set up your API key.`
1204
1626
  )
1205
1627
  );
1206
1628
  process.exit(1);
@@ -1209,14 +1631,14 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1209
1631
  message: "What do you want your agent to do?"
1210
1632
  });
1211
1633
  if (!intentRaw.trim()) {
1212
- console.log(chalk4.red("\n No description provided. Aborting.\n"));
1634
+ console.log(chalk5.red("\n No description provided. Aborting.\n"));
1213
1635
  process.exit(1);
1214
1636
  }
1215
1637
  let finalIntent = intentRaw;
1216
1638
  if (!options.quick) {
1217
1639
  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"));
1640
+ console.log(chalk5.dim(" Let me understand your project better."));
1641
+ console.log(chalk5.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
1220
1642
  let clarifications = [];
1221
1643
  try {
1222
1644
  clarifications = await generateClarifications(intentRaw);
@@ -1238,6 +1660,23 @@ Clarifications:
1238
1660
  ${clarificationLines}`;
1239
1661
  }
1240
1662
  }
1663
+ let autonomyLevel = 1;
1664
+ if (!options.quick) {
1665
+ console.log(ui.section("Autonomy"));
1666
+ autonomyLevel = await select2({
1667
+ message: "Autonomy level",
1668
+ choices: [
1669
+ { name: "1. Guided \u2014 orientation + commands, you drive", value: 1 },
1670
+ { name: "2. Assisted \u2014 workflow loop, you approve phases", value: 2 },
1671
+ { name: "3. Autonomous \u2014 PM plans, loop executes, you review PRs", value: 3 },
1672
+ { name: "4. Full Auto \u2014 continuous execution (\u26A0 advanced)", value: 4 }
1673
+ ],
1674
+ default: 1
1675
+ });
1676
+ finalIntent += `
1677
+
1678
+ Autonomy level: ${autonomyLevel} (${autonomyLabel(autonomyLevel)})`;
1679
+ }
1241
1680
  console.log(ui.section("Compilation"));
1242
1681
  const spinner = ora({ text: "Loading tool registry...", indent: 2 }).start();
1243
1682
  let spec;
@@ -1245,11 +1684,12 @@ ${clarificationLines}`;
1245
1684
  spec = await compile(finalIntent, (msg) => {
1246
1685
  spinner.text = msg;
1247
1686
  });
1687
+ spec.autonomy_level = autonomyLevel;
1248
1688
  spinner.succeed("Environment compiled");
1249
1689
  } catch (err) {
1250
1690
  spinner.fail("Compilation failed");
1251
1691
  const msg = err instanceof Error ? err.message : String(err);
1252
- console.log(chalk4.red(`
1692
+ console.log(chalk5.red(`
1253
1693
  ${msg}
1254
1694
  `));
1255
1695
  process.exit(1);
@@ -1259,6 +1699,7 @@ ${clarificationLines}`;
1259
1699
  console.log("");
1260
1700
  console.log(ui.kv("Name:", spec.name));
1261
1701
  console.log(ui.kv("Description:", spec.description));
1702
+ console.log(ui.kv("Autonomy:", `Level ${spec.autonomy_level} (${autonomyLabel(spec.autonomy_level)})`));
1262
1703
  console.log(ui.kv("Tools:", String(summary.toolCount)));
1263
1704
  console.log(ui.kv("Commands:", String(summary.commandCount)));
1264
1705
  console.log(ui.kv("Rules:", String(summary.ruleCount)));
@@ -1279,7 +1720,7 @@ ${clarificationLines}`;
1279
1720
  default: true
1280
1721
  });
1281
1722
  if (!proceed) {
1282
- console.log(chalk4.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1723
+ console.log(chalk5.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1283
1724
  return;
1284
1725
  }
1285
1726
  const targetDir = process.cwd();
@@ -1288,25 +1729,24 @@ ${clarificationLines}`;
1288
1729
  await writeHermesEnvironment(spec, registry);
1289
1730
  console.log("\n" + ui.success("Environment written for Hermes"));
1290
1731
  console.log(
1291
- chalk4.cyan("\n Ready! Run ") + chalk4.bold("hermes") + chalk4.cyan(" to start.\n")
1732
+ chalk5.cyan("\n Ready! Run ") + chalk5.bold("hermes") + chalk5.cyan(" to start.\n")
1292
1733
  );
1293
1734
  } else {
1294
- const written = await writeEnvironment(spec, targetDir);
1735
+ const hasEnvVars = summary.envSetup.length > 0;
1736
+ const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
1295
1737
  console.log(ui.section("Files Written"));
1296
1738
  console.log("");
1297
1739
  for (const file of written) {
1298
1740
  console.log(ui.file(file));
1299
1741
  }
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("");
1742
+ if (hasEnvVars) {
1743
+ if (options.quick) {
1744
+ await writeEmptyEnvFile(summary.envSetup, targetDir);
1745
+ console.log(ui.success("Empty .env written (gitignored) \u2014 fill in keys later: kairn keys"));
1746
+ } else {
1747
+ await collectAndWriteKeys(summary.envSetup, targetDir);
1309
1748
  }
1749
+ console.log("");
1310
1750
  }
1311
1751
  if (summary.pluginCommands.length > 0) {
1312
1752
  console.log(ui.section("Plugins"));
@@ -1324,28 +1764,28 @@ ${clarificationLines}`;
1324
1764
 
1325
1765
  // src/commands/list.ts
1326
1766
  import { Command as Command3 } from "commander";
1327
- import chalk5 from "chalk";
1328
- import fs7 from "fs/promises";
1329
- import path7 from "path";
1767
+ import chalk6 from "chalk";
1768
+ import fs8 from "fs/promises";
1769
+ import path8 from "path";
1330
1770
  var listCommand = new Command3("list").description("Show saved environments").action(async () => {
1331
1771
  printCompactBanner();
1332
1772
  const envsDir = getEnvsDir();
1333
1773
  let files;
1334
1774
  try {
1335
- files = await fs7.readdir(envsDir);
1775
+ files = await fs8.readdir(envsDir);
1336
1776
  } catch {
1337
- console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1777
+ console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
1338
1778
  return;
1339
1779
  }
1340
1780
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
1341
1781
  if (jsonFiles.length === 0) {
1342
- console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1782
+ console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
1343
1783
  return;
1344
1784
  }
1345
1785
  let first = true;
1346
1786
  for (const file of jsonFiles) {
1347
1787
  try {
1348
- const data = await fs7.readFile(path7.join(envsDir, file), "utf-8");
1788
+ const data = await fs8.readFile(path8.join(envsDir, file), "utf-8");
1349
1789
  const spec = JSON.parse(data);
1350
1790
  const date = new Date(spec.created_at).toLocaleDateString();
1351
1791
  const toolCount = spec.tools?.length ?? 0;
@@ -1353,10 +1793,10 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1353
1793
  console.log(ui.divider());
1354
1794
  }
1355
1795
  first = false;
1356
- console.log(ui.kv("Name", chalk5.bold(spec.name)));
1796
+ console.log(ui.kv("Name", chalk6.bold(spec.name)));
1357
1797
  console.log(ui.kv("Description", spec.description));
1358
1798
  console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
1359
- console.log(ui.kv("ID", chalk5.dim(spec.id)));
1799
+ console.log(ui.kv("ID", chalk6.dim(spec.id)));
1360
1800
  console.log("");
1361
1801
  } catch {
1362
1802
  }
@@ -1365,9 +1805,9 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1365
1805
 
1366
1806
  // src/commands/activate.ts
1367
1807
  import { Command as Command4 } from "commander";
1368
- import chalk6 from "chalk";
1369
- import fs8 from "fs/promises";
1370
- import path8 from "path";
1808
+ import chalk7 from "chalk";
1809
+ import fs9 from "fs/promises";
1810
+ import path9 from "path";
1371
1811
  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
1812
  printCompactBanner();
1373
1813
  const envsDir = getEnvsDir();
@@ -1377,7 +1817,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1377
1817
  let fromTemplate = false;
1378
1818
  let envFiles = [];
1379
1819
  try {
1380
- envFiles = await fs8.readdir(envsDir);
1820
+ envFiles = await fs9.readdir(envsDir);
1381
1821
  } catch {
1382
1822
  }
1383
1823
  match = envFiles.find(
@@ -1388,7 +1828,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1388
1828
  } else {
1389
1829
  let templateFiles = [];
1390
1830
  try {
1391
- templateFiles = await fs8.readdir(templatesDir);
1831
+ templateFiles = await fs9.readdir(templatesDir);
1392
1832
  } catch {
1393
1833
  }
1394
1834
  match = templateFiles.find(
@@ -1399,16 +1839,16 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1399
1839
  fromTemplate = true;
1400
1840
  } else {
1401
1841
  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"));
1842
+ console.log(chalk7.dim(" Run kairn list to see saved environments."));
1843
+ console.log(chalk7.dim(" Run kairn templates to see available templates.\n"));
1404
1844
  process.exit(1);
1405
1845
  }
1406
1846
  }
1407
- const data = await fs8.readFile(path8.join(sourceDir, match), "utf-8");
1847
+ const data = await fs9.readFile(path9.join(sourceDir, match), "utf-8");
1408
1848
  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}
1849
+ const label = fromTemplate ? chalk7.dim(" (template)") : "";
1850
+ console.log(chalk7.cyan(` Activating: ${spec.name}`) + label);
1851
+ console.log(chalk7.dim(` ${spec.description}
1412
1852
  `));
1413
1853
  const targetDir = process.cwd();
1414
1854
  const written = await writeEnvironment(spec, targetDir);
@@ -1421,22 +1861,22 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1421
1861
 
1422
1862
  // src/commands/update-registry.ts
1423
1863
  import { Command as Command5 } from "commander";
1424
- import chalk7 from "chalk";
1425
- import fs9 from "fs/promises";
1426
- import path9 from "path";
1864
+ import chalk8 from "chalk";
1865
+ import fs10 from "fs/promises";
1866
+ import path10 from "path";
1427
1867
  import { fileURLToPath as fileURLToPath3 } from "url";
1428
1868
  var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
1429
1869
  async function getLocalRegistryPath() {
1430
1870
  const __filename3 = fileURLToPath3(import.meta.url);
1431
- const __dirname3 = path9.dirname(__filename3);
1871
+ const __dirname3 = path10.dirname(__filename3);
1432
1872
  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")
1873
+ path10.resolve(__dirname3, "../registry/tools.json"),
1874
+ path10.resolve(__dirname3, "../src/registry/tools.json"),
1875
+ path10.resolve(__dirname3, "../../src/registry/tools.json")
1436
1876
  ];
1437
1877
  for (const candidate of candidates) {
1438
1878
  try {
1439
- await fs9.access(candidate);
1879
+ await fs10.access(candidate);
1440
1880
  return candidate;
1441
1881
  } catch {
1442
1882
  continue;
@@ -1447,15 +1887,15 @@ async function getLocalRegistryPath() {
1447
1887
  var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
1448
1888
  printCompactBanner();
1449
1889
  const url = options.url || REGISTRY_URL;
1450
- console.log(chalk7.dim(` Fetching registry from ${url}...`));
1890
+ console.log(chalk8.dim(` Fetching registry from ${url}...`));
1451
1891
  try {
1452
1892
  const response = await fetch(url);
1453
1893
  if (!response.ok) {
1454
1894
  console.log(
1455
1895
  ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
1456
1896
  );
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"));
1897
+ console.log(chalk8.dim(" The remote registry may not be available yet."));
1898
+ console.log(chalk8.dim(" Your local registry is still active.\n"));
1459
1899
  return;
1460
1900
  }
1461
1901
  const text = await response.text();
@@ -1473,35 +1913,35 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1473
1913
  const registryPath = await getLocalRegistryPath();
1474
1914
  const backupPath = registryPath + ".bak";
1475
1915
  try {
1476
- await fs9.copyFile(registryPath, backupPath);
1916
+ await fs10.copyFile(registryPath, backupPath);
1477
1917
  } catch {
1478
1918
  }
1479
- await fs9.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
1919
+ await fs10.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
1480
1920
  console.log(ui.success(`Registry updated: ${tools.length} tools`));
1481
- console.log(chalk7.dim(` Saved to: ${registryPath}`));
1482
- console.log(chalk7.dim(` Backup: ${backupPath}
1921
+ console.log(chalk8.dim(` Saved to: ${registryPath}`));
1922
+ console.log(chalk8.dim(` Backup: ${backupPath}
1483
1923
  `));
1484
1924
  } catch (err) {
1485
1925
  const msg = err instanceof Error ? err.message : String(err);
1486
1926
  console.log(ui.error(`Network error: ${msg}`));
1487
- console.log(chalk7.dim(" Your local registry is still active.\n"));
1927
+ console.log(chalk8.dim(" Your local registry is still active.\n"));
1488
1928
  }
1489
1929
  });
1490
1930
 
1491
1931
  // src/commands/optimize.ts
1492
1932
  import { Command as Command6 } from "commander";
1493
1933
  import { confirm as confirm2 } from "@inquirer/prompts";
1494
- import chalk8 from "chalk";
1934
+ import chalk9 from "chalk";
1495
1935
  import ora2 from "ora";
1496
- import fs11 from "fs/promises";
1497
- import path11 from "path";
1936
+ import fs12 from "fs/promises";
1937
+ import path12 from "path";
1498
1938
 
1499
1939
  // src/scanner/scan.ts
1500
- import fs10 from "fs/promises";
1501
- import path10 from "path";
1940
+ import fs11 from "fs/promises";
1941
+ import path11 from "path";
1502
1942
  async function fileExists(p) {
1503
1943
  try {
1504
- await fs10.access(p);
1944
+ await fs11.access(p);
1505
1945
  return true;
1506
1946
  } catch {
1507
1947
  return false;
@@ -1509,7 +1949,7 @@ async function fileExists(p) {
1509
1949
  }
1510
1950
  async function readJsonSafe(p) {
1511
1951
  try {
1512
- const data = await fs10.readFile(p, "utf-8");
1952
+ const data = await fs11.readFile(p, "utf-8");
1513
1953
  return JSON.parse(data);
1514
1954
  } catch {
1515
1955
  return null;
@@ -1517,14 +1957,14 @@ async function readJsonSafe(p) {
1517
1957
  }
1518
1958
  async function readFileSafe(p) {
1519
1959
  try {
1520
- return await fs10.readFile(p, "utf-8");
1960
+ return await fs11.readFile(p, "utf-8");
1521
1961
  } catch {
1522
1962
  return null;
1523
1963
  }
1524
1964
  }
1525
1965
  async function listDirSafe(p) {
1526
1966
  try {
1527
- const entries = await fs10.readdir(p);
1967
+ const entries = await fs11.readdir(p);
1528
1968
  return entries.filter((e) => !e.startsWith("."));
1529
1969
  } catch {
1530
1970
  return [];
@@ -1576,7 +2016,7 @@ function extractEnvKeys(content) {
1576
2016
  return keys;
1577
2017
  }
1578
2018
  async function scanProject(dir) {
1579
- const pkg = await readJsonSafe(path10.join(dir, "package.json"));
2019
+ const pkg = await readJsonSafe(path11.join(dir, "package.json"));
1580
2020
  const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
1581
2021
  const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
1582
2022
  const allDeps = [...deps, ...devDeps];
@@ -1604,19 +2044,19 @@ async function scanProject(dir) {
1604
2044
  const framework = detectFramework(allDeps);
1605
2045
  const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
1606
2046
  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"));
2047
+ const hasTests = testCommand !== null || await fileExists(path11.join(dir, "tests")) || await fileExists(path11.join(dir, "__tests__")) || await fileExists(path11.join(dir, "test"));
1608
2048
  const buildCommand = scripts.build || null;
1609
2049
  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"));
2050
+ const hasSrc = await fileExists(path11.join(dir, "src"));
2051
+ const hasDocker = await fileExists(path11.join(dir, "docker-compose.yml")) || await fileExists(path11.join(dir, "Dockerfile"));
2052
+ const hasCi = await fileExists(path11.join(dir, ".github/workflows"));
2053
+ const hasEnvFile = await fileExists(path11.join(dir, ".env")) || await fileExists(path11.join(dir, ".env.example"));
1614
2054
  let envKeys = [];
1615
- const envExample = await readFileSafe(path10.join(dir, ".env.example"));
2055
+ const envExample = await readFileSafe(path11.join(dir, ".env.example"));
1616
2056
  if (envExample) {
1617
2057
  envKeys = extractEnvKeys(envExample);
1618
2058
  }
1619
- const claudeDir = path10.join(dir, ".claude");
2059
+ const claudeDir = path11.join(dir, ".claude");
1620
2060
  const hasClaudeDir = await fileExists(claudeDir);
1621
2061
  let existingClaudeMd = null;
1622
2062
  let existingSettings = null;
@@ -1628,21 +2068,21 @@ async function scanProject(dir) {
1628
2068
  let mcpServerCount = 0;
1629
2069
  let claudeMdLineCount = 0;
1630
2070
  if (hasClaudeDir) {
1631
- existingClaudeMd = await readFileSafe(path10.join(claudeDir, "CLAUDE.md"));
2071
+ existingClaudeMd = await readFileSafe(path11.join(claudeDir, "CLAUDE.md"));
1632
2072
  if (existingClaudeMd) {
1633
2073
  claudeMdLineCount = existingClaudeMd.split("\n").length;
1634
2074
  }
1635
- existingSettings = await readJsonSafe(path10.join(claudeDir, "settings.json"));
1636
- existingMcpConfig = await readJsonSafe(path10.join(dir, ".mcp.json"));
2075
+ existingSettings = await readJsonSafe(path11.join(claudeDir, "settings.json"));
2076
+ existingMcpConfig = await readJsonSafe(path11.join(dir, ".mcp.json"));
1637
2077
  if (existingMcpConfig?.mcpServers) {
1638
2078
  mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
1639
2079
  }
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", ""));
2080
+ existingCommands = (await listDirSafe(path11.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
2081
+ existingRules = (await listDirSafe(path11.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
2082
+ existingSkills = await listDirSafe(path11.join(claudeDir, "skills"));
2083
+ existingAgents = (await listDirSafe(path11.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
1644
2084
  }
1645
- const name = pkg?.name || path10.basename(dir);
2085
+ const name = pkg?.name || path11.basename(dir);
1646
2086
  const description = pkg?.description || "";
1647
2087
  return {
1648
2088
  name,
@@ -1687,31 +2127,31 @@ function simpleDiff(oldContent, newContent) {
1687
2127
  const oldLine = oldLines[i];
1688
2128
  const newLine = newLines[i];
1689
2129
  if (oldLine === void 0) {
1690
- output.push(chalk8.green(`+ ${newLine}`));
2130
+ output.push(chalk9.green(`+ ${newLine}`));
1691
2131
  } else if (newLine === void 0) {
1692
- output.push(chalk8.red(`- ${oldLine}`));
2132
+ output.push(chalk9.red(`- ${oldLine}`));
1693
2133
  } else if (oldLine !== newLine) {
1694
- output.push(chalk8.red(`- ${oldLine}`));
1695
- output.push(chalk8.green(`+ ${newLine}`));
2134
+ output.push(chalk9.red(`- ${oldLine}`));
2135
+ output.push(chalk9.green(`+ ${newLine}`));
1696
2136
  }
1697
2137
  }
1698
2138
  return output;
1699
2139
  }
1700
- async function generateDiff(spec, targetDir) {
1701
- const fileMap = buildFileMap(spec);
2140
+ async function generateDiff(spec, targetDir, options) {
2141
+ const fileMap = buildFileMap(spec, options);
1702
2142
  const results = [];
1703
2143
  for (const [relativePath, newContent] of fileMap) {
1704
- const absolutePath = path11.join(targetDir, relativePath);
2144
+ const absolutePath = path12.join(targetDir, relativePath);
1705
2145
  let oldContent = null;
1706
2146
  try {
1707
- oldContent = await fs11.readFile(absolutePath, "utf-8");
2147
+ oldContent = await fs12.readFile(absolutePath, "utf-8");
1708
2148
  } catch {
1709
2149
  }
1710
2150
  if (oldContent === null) {
1711
2151
  results.push({
1712
2152
  path: relativePath,
1713
2153
  status: "new",
1714
- diff: chalk8.green("+ NEW FILE")
2154
+ diff: chalk9.green("+ NEW FILE")
1715
2155
  });
1716
2156
  } else if (oldContent === newContent) {
1717
2157
  results.push({
@@ -1857,7 +2297,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1857
2297
  console.log(ui.success("No obvious issues found"));
1858
2298
  }
1859
2299
  if (options.auditOnly) {
1860
- console.log(chalk8.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
2300
+ console.log(chalk9.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1861
2301
  return;
1862
2302
  }
1863
2303
  if (!options.yes) {
@@ -1867,19 +2307,19 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1867
2307
  default: false
1868
2308
  });
1869
2309
  if (!proceed) {
1870
- console.log(chalk8.dim("\n Aborted.\n"));
2310
+ console.log(chalk9.dim("\n Aborted.\n"));
1871
2311
  return;
1872
2312
  }
1873
2313
  }
1874
2314
  } else {
1875
- console.log(chalk8.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
2315
+ console.log(chalk9.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1876
2316
  if (!options.yes) {
1877
2317
  const proceed = await confirm2({
1878
2318
  message: "Generate Claude Code environment for this project?",
1879
2319
  default: true
1880
2320
  });
1881
2321
  if (!proceed) {
1882
- console.log(chalk8.dim("\n Aborted.\n"));
2322
+ console.log(chalk9.dim("\n Aborted.\n"));
1883
2323
  return;
1884
2324
  }
1885
2325
  }
@@ -1915,8 +2355,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1915
2355
  console.log(ui.tool(name, tool.reason));
1916
2356
  }
1917
2357
  }
2358
+ const hasEnvVars = summary.envSetup.length > 0;
1918
2359
  if (options.diff) {
1919
- const diffs = await generateDiff(spec, targetDir);
2360
+ const diffs = await generateDiff(spec, targetDir, { hasEnvVars });
1920
2361
  const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
1921
2362
  if (changedDiffs.length === 0) {
1922
2363
  console.log(ui.success("No changes needed \u2014 environment is already up to date."));
@@ -1925,7 +2366,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1925
2366
  }
1926
2367
  console.log(ui.section("Changes Preview"));
1927
2368
  for (const d of changedDiffs) {
1928
- console.log(chalk8.cyan(`
2369
+ console.log(chalk9.cyan(`
1929
2370
  --- ${d.path}`));
1930
2371
  if (d.status === "new") {
1931
2372
  console.log(` ${d.diff}`);
@@ -1941,7 +2382,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1941
2382
  default: true
1942
2383
  });
1943
2384
  if (!apply) {
1944
- console.log(chalk8.dim("\n Aborted.\n"));
2385
+ console.log(chalk9.dim("\n Aborted.\n"));
1945
2386
  return;
1946
2387
  }
1947
2388
  }
@@ -1952,20 +2393,14 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1952
2393
  console.log(ui.success(`Ready! Run: $ hermes`));
1953
2394
  console.log("");
1954
2395
  } else {
1955
- const written = await writeEnvironment(spec, targetDir);
2396
+ const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
1956
2397
  console.log(ui.section("Files Written"));
1957
2398
  for (const file of written) {
1958
2399
  console.log(ui.file(file));
1959
2400
  }
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
- }
2401
+ if (hasEnvVars) {
2402
+ await collectAndWriteKeys(summary.envSetup, targetDir);
2403
+ console.log("");
1969
2404
  }
1970
2405
  if (summary.pluginCommands.length > 0) {
1971
2406
  console.log(ui.section("Plugins"));
@@ -1982,7 +2417,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1982
2417
 
1983
2418
  // src/commands/doctor.ts
1984
2419
  import { Command as Command7 } from "commander";
1985
- import chalk9 from "chalk";
2420
+ import chalk10 from "chalk";
1986
2421
  function runChecks(profile) {
1987
2422
  const checks = [];
1988
2423
  if (!profile.existingClaudeMd) {
@@ -2114,12 +2549,12 @@ var doctorCommand = new Command7("doctor").description(
2114
2549
  ).action(async () => {
2115
2550
  printFullBanner("Doctor");
2116
2551
  const targetDir = process.cwd();
2117
- console.log(chalk9.dim(" Checking .claude/ environment...\n"));
2552
+ console.log(chalk10.dim(" Checking .claude/ environment...\n"));
2118
2553
  const profile = await scanProject(targetDir);
2119
2554
  if (!profile.hasClaudeDir) {
2120
2555
  console.log(ui.error("No .claude/ directory found.\n"));
2121
2556
  console.log(
2122
- chalk9.dim(" Run ") + chalk9.bold("kairn describe") + chalk9.dim(" or ") + chalk9.bold("kairn optimize") + chalk9.dim(" to generate one.\n")
2557
+ chalk10.dim(" Run ") + chalk10.bold("kairn describe") + chalk10.dim(" or ") + chalk10.bold("kairn optimize") + chalk10.dim(" to generate one.\n")
2123
2558
  );
2124
2559
  process.exit(1);
2125
2560
  }
@@ -2142,7 +2577,7 @@ var doctorCommand = new Command7("doctor").description(
2142
2577
  return sum;
2143
2578
  }, 0);
2144
2579
  const percentage = Math.round(score / maxScore * 100);
2145
- const scoreColor = percentage >= 80 ? chalk9.green : percentage >= 50 ? chalk9.yellow : chalk9.red;
2580
+ const scoreColor = percentage >= 80 ? chalk10.green : percentage >= 50 ? chalk10.yellow : chalk10.red;
2146
2581
  console.log(
2147
2582
  `
2148
2583
  Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
@@ -2150,15 +2585,15 @@ var doctorCommand = new Command7("doctor").description(
2150
2585
  );
2151
2586
  if (percentage < 80) {
2152
2587
  console.log(
2153
- chalk9.dim(" Run ") + chalk9.bold("kairn optimize") + chalk9.dim(" to fix issues.\n")
2588
+ chalk10.dim(" Run ") + chalk10.bold("kairn optimize") + chalk10.dim(" to fix issues.\n")
2154
2589
  );
2155
2590
  }
2156
2591
  });
2157
2592
 
2158
2593
  // src/commands/registry.ts
2159
2594
  import { Command as Command8 } from "commander";
2160
- import chalk10 from "chalk";
2161
- import { input as input2, select as select2 } from "@inquirer/prompts";
2595
+ import chalk11 from "chalk";
2596
+ import { input as input2, select as select3 } from "@inquirer/prompts";
2162
2597
  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
2598
  printCompactBanner();
2164
2599
  let all;
@@ -2182,7 +2617,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2182
2617
  );
2183
2618
  }
2184
2619
  if (tools.length === 0) {
2185
- console.log(chalk10.dim("\n No tools found.\n"));
2620
+ console.log(chalk11.dim("\n No tools found.\n"));
2186
2621
  return;
2187
2622
  }
2188
2623
  const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
@@ -2196,13 +2631,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2196
2631
  `tier ${tool.tier}`,
2197
2632
  tool.auth
2198
2633
  ].join(", ");
2199
- console.log(` ${ui.accent(tool.id)}` + chalk10.dim(` (${meta})`));
2200
- console.log(chalk10.dim(` ${tool.description}`));
2634
+ console.log(` ${ui.accent(tool.id)}` + chalk11.dim(` (${meta})`));
2635
+ console.log(chalk11.dim(` ${tool.description}`));
2201
2636
  if (tool.best_for.length > 0) {
2202
- console.log(chalk10.dim(` Best for: ${tool.best_for.join(", ")}`));
2637
+ console.log(chalk11.dim(` Best for: ${tool.best_for.join(", ")}`));
2203
2638
  }
2204
2639
  if (isUser) {
2205
- console.log(chalk10.yellow(" [USER-DEFINED]"));
2640
+ console.log(chalk11.yellow(" [USER-DEFINED]"));
2206
2641
  }
2207
2642
  console.log("");
2208
2643
  }
@@ -2210,7 +2645,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2210
2645
  const shownUser = tools.filter((t) => userIds.has(t.id)).length;
2211
2646
  const shownBundled = totalShown - shownUser;
2212
2647
  console.log(
2213
- chalk10.dim(
2648
+ chalk11.dim(
2214
2649
  ` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
2215
2650
  ) + "\n"
2216
2651
  );
@@ -2228,7 +2663,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2228
2663
  });
2229
2664
  const name = await input2({ message: "Display name" });
2230
2665
  const description = await input2({ message: "Description" });
2231
- const category = await select2({
2666
+ const category = await select3({
2232
2667
  message: "Category",
2233
2668
  choices: [
2234
2669
  { value: "universal" },
@@ -2242,7 +2677,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2242
2677
  { value: "sandbox" }
2243
2678
  ]
2244
2679
  });
2245
- const tier = await select2({
2680
+ const tier = await select3({
2246
2681
  message: "Tier",
2247
2682
  choices: [
2248
2683
  { name: "1 \u2014 Universal", value: 1 },
@@ -2250,7 +2685,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2250
2685
  { name: "3 \u2014 Specialized", value: 3 }
2251
2686
  ]
2252
2687
  });
2253
- const type = await select2({
2688
+ const type = await select3({
2254
2689
  message: "Type",
2255
2690
  choices: [
2256
2691
  { value: "mcp_server" },
@@ -2258,7 +2693,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2258
2693
  { value: "hook" }
2259
2694
  ]
2260
2695
  });
2261
- const auth = await select2({
2696
+ const auth = await select3({
2262
2697
  message: "Auth",
2263
2698
  choices: [
2264
2699
  { value: "none" },
@@ -2274,7 +2709,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2274
2709
  const varName = await input2({ message: "Env var name" });
2275
2710
  const varDesc = await input2({ message: "Env var description" });
2276
2711
  env_vars.push({ name: varName, description: varDesc });
2277
- const another = await select2({
2712
+ const another = await select3({
2278
2713
  message: "Add another env var?",
2279
2714
  choices: [
2280
2715
  { name: "No", value: false },
@@ -2334,20 +2769,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
2334
2769
 
2335
2770
  // src/commands/templates.ts
2336
2771
  import { Command as Command9 } from "commander";
2337
- import chalk11 from "chalk";
2338
- import fs12 from "fs/promises";
2339
- import path12 from "path";
2772
+ import chalk12 from "chalk";
2773
+ import fs13 from "fs/promises";
2774
+ import path13 from "path";
2340
2775
  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
2776
  printCompactBanner();
2342
2777
  const templatesDir = getTemplatesDir();
2343
2778
  let files;
2344
2779
  try {
2345
- files = await fs12.readdir(templatesDir);
2780
+ files = await fs13.readdir(templatesDir);
2346
2781
  } catch {
2347
2782
  console.log(
2348
- chalk11.dim(
2783
+ chalk12.dim(
2349
2784
  " No templates found. Templates will be installed with "
2350
- ) + chalk11.bold("kairn init") + chalk11.dim(
2785
+ ) + chalk12.bold("kairn init") + chalk12.dim(
2351
2786
  " or you can add .json files to ~/.kairn/templates/\n"
2352
2787
  )
2353
2788
  );
@@ -2356,9 +2791,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
2356
2791
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2357
2792
  if (jsonFiles.length === 0) {
2358
2793
  console.log(
2359
- chalk11.dim(
2794
+ chalk12.dim(
2360
2795
  " No templates found. Templates will be installed with "
2361
- ) + chalk11.bold("kairn init") + chalk11.dim(
2796
+ ) + chalk12.bold("kairn init") + chalk12.dim(
2362
2797
  " or you can add .json files to ~/.kairn/templates/\n"
2363
2798
  )
2364
2799
  );
@@ -2367,8 +2802,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
2367
2802
  const templates = [];
2368
2803
  for (const file of jsonFiles) {
2369
2804
  try {
2370
- const data = await fs12.readFile(
2371
- path12.join(templatesDir, file),
2805
+ const data = await fs13.readFile(
2806
+ path13.join(templatesDir, file),
2372
2807
  "utf-8"
2373
2808
  );
2374
2809
  const spec = JSON.parse(data);
@@ -2386,7 +2821,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
2386
2821
  }
2387
2822
  if (filtered.length === 0) {
2388
2823
  console.log(
2389
- chalk11.dim(` No templates matched category "${options.category}".
2824
+ chalk12.dim(` No templates matched category "${options.category}".
2390
2825
  `)
2391
2826
  );
2392
2827
  return;
@@ -2397,8 +2832,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
2397
2832
  const toolCount = spec.tools?.length ?? 0;
2398
2833
  const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
2399
2834
  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)));
2835
+ console.log(ui.kv("Name", chalk12.bold(spec.name)));
2836
+ console.log(ui.kv("ID", chalk12.dim(spec.id)));
2402
2837
  console.log(ui.kv("Description", spec.description));
2403
2838
  console.log(
2404
2839
  ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
@@ -2406,16 +2841,139 @@ var templatesCommand = new Command9("templates").description("Browse available t
2406
2841
  console.log("");
2407
2842
  }
2408
2843
  console.log(
2409
- chalk11.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2844
+ chalk12.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2410
2845
  `)
2411
2846
  );
2412
2847
  });
2413
2848
 
2849
+ // src/commands/keys.ts
2850
+ import { Command as Command10 } from "commander";
2851
+ import { password as password3 } from "@inquirer/prompts";
2852
+ import chalk13 from "chalk";
2853
+ import fs14 from "fs/promises";
2854
+ import path14 from "path";
2855
+ 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) => {
2856
+ printCompactBanner();
2857
+ const targetDir = process.cwd();
2858
+ const requiredVars = await detectRequiredEnvVars(targetDir);
2859
+ if (requiredVars.length === 0) {
2860
+ console.log(
2861
+ ui.info("No MCP servers found in .mcp.json \u2014 no API keys needed.")
2862
+ );
2863
+ console.log("");
2864
+ return;
2865
+ }
2866
+ const existing = await readEnvFile(targetDir);
2867
+ const registry = await loadRegistry();
2868
+ const envSetupMap = /* @__PURE__ */ new Map();
2869
+ for (const tool of registry) {
2870
+ if (!tool.env_vars) continue;
2871
+ for (const ev of tool.env_vars) {
2872
+ if (requiredVars.includes(ev.name)) {
2873
+ envSetupMap.set(ev.name, {
2874
+ toolName: tool.name,
2875
+ envVar: ev.name,
2876
+ description: ev.description,
2877
+ signupUrl: tool.signup_url
2878
+ });
2879
+ }
2880
+ }
2881
+ }
2882
+ for (const varName of requiredVars) {
2883
+ if (!envSetupMap.has(varName)) {
2884
+ envSetupMap.set(varName, {
2885
+ toolName: "unknown",
2886
+ envVar: varName,
2887
+ description: "Required by MCP server"
2888
+ });
2889
+ }
2890
+ }
2891
+ if (options.show) {
2892
+ console.log(ui.section("API Key Status"));
2893
+ console.log("");
2894
+ for (const varName of requiredVars) {
2895
+ const value = existing.get(varName);
2896
+ const info = envSetupMap.get(varName);
2897
+ const toolLabel = info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "";
2898
+ if (value && value.length > 0) {
2899
+ const masked = value.slice(0, 4) + "\u2022".repeat(Math.max(0, value.length - 4));
2900
+ console.log(chalk13.green(` \u2713 ${varName}`) + toolLabel + chalk13.dim(` = ${masked}`));
2901
+ } else {
2902
+ console.log(chalk13.yellow(` \u2717 ${varName}`) + toolLabel + chalk13.dim(" = (not set)"));
2903
+ if (info?.signupUrl) {
2904
+ console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
2905
+ }
2906
+ }
2907
+ }
2908
+ const setCount = requiredVars.filter((v) => {
2909
+ const val = existing.get(v);
2910
+ return val && val.length > 0;
2911
+ }).length;
2912
+ const missingCount = requiredVars.length - setCount;
2913
+ console.log("");
2914
+ if (missingCount === 0) {
2915
+ console.log(ui.success(`All ${setCount} key(s) configured`));
2916
+ } else {
2917
+ console.log(
2918
+ ui.warn(`${missingCount} key(s) missing \u2014 run ${chalk13.bold("kairn keys")} to add them`)
2919
+ );
2920
+ }
2921
+ console.log("");
2922
+ return;
2923
+ }
2924
+ const missing = requiredVars.filter((v) => {
2925
+ const val = existing.get(v);
2926
+ return !val || val.length === 0;
2927
+ });
2928
+ if (missing.length === 0) {
2929
+ console.log(ui.success("All API keys are already configured."));
2930
+ console.log(chalk13.dim(" Use --show to see current keys.\n"));
2931
+ return;
2932
+ }
2933
+ console.log(ui.section("API Keys"));
2934
+ console.log(chalk13.dim(` ${missing.length} key(s) need to be set. Press Enter to skip.
2935
+ `));
2936
+ const envEntries = new Map(existing);
2937
+ let keysEntered = 0;
2938
+ for (const varName of missing) {
2939
+ const info = envSetupMap.get(varName);
2940
+ console.log(
2941
+ chalk13.bold(` ${varName}`) + (info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "")
2942
+ );
2943
+ if (info?.signupUrl) {
2944
+ console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
2945
+ }
2946
+ const value = await password3({
2947
+ message: varName,
2948
+ mask: "\u2022"
2949
+ });
2950
+ if (value && value.trim()) {
2951
+ envEntries.set(varName, value.trim());
2952
+ console.log(chalk13.green(" \u2713 saved\n"));
2953
+ keysEntered++;
2954
+ } else {
2955
+ console.log(chalk13.dim(" (skipped)\n"));
2956
+ }
2957
+ }
2958
+ const envLines = [
2959
+ "# Generated by Kairn \u2014 API keys for MCP servers",
2960
+ "# Do NOT commit this file to git",
2961
+ ""
2962
+ ];
2963
+ for (const [key, value] of envEntries) {
2964
+ envLines.push(`${key}=${value}`);
2965
+ }
2966
+ const envPath = path14.join(targetDir, ".env");
2967
+ await fs14.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
2968
+ console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
2969
+ console.log("");
2970
+ });
2971
+
2414
2972
  // src/cli.ts
2415
- var program = new Command10();
2973
+ var program = new Command11();
2416
2974
  program.name("kairn").description(
2417
2975
  "Compile natural language intent into optimized Claude Code environments"
2418
- ).version("1.7.0").option("--no-color", "Disable colored output");
2976
+ ).version("1.9.0").option("--no-color", "Disable colored output");
2419
2977
  program.addCommand(initCommand);
2420
2978
  program.addCommand(describeCommand);
2421
2979
  program.addCommand(optimizeCommand);
@@ -2425,8 +2983,9 @@ program.addCommand(updateRegistryCommand);
2425
2983
  program.addCommand(doctorCommand);
2426
2984
  program.addCommand(registryCommand);
2427
2985
  program.addCommand(templatesCommand);
2986
+ program.addCommand(keysCommand);
2428
2987
  if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
2429
- chalk12.level = 0;
2988
+ chalk14.level = 0;
2430
2989
  }
2431
2990
  program.parse();
2432
2991
  //# sourceMappingURL=cli.js.map