kairn-cli 1.5.1 → 1.6.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,10 +1,11 @@
1
1
  // src/cli.ts
2
2
  import { Command as Command10 } from "commander";
3
+ import chalk12 from "chalk";
3
4
 
4
5
  // src/commands/init.ts
5
6
  import { Command } from "commander";
6
7
  import { password, select } from "@inquirer/prompts";
7
- import chalk from "chalk";
8
+ import chalk3 from "chalk";
8
9
  import Anthropic from "@anthropic-ai/sdk";
9
10
  import OpenAI from "openai";
10
11
  import { execFileSync } from "child_process";
@@ -61,6 +62,115 @@ async function saveConfig(config) {
61
62
  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
62
63
  }
63
64
 
65
+ // src/ui.ts
66
+ import chalk from "chalk";
67
+ var maroon = chalk.rgb(139, 0, 0);
68
+ var warm = chalk.rgb(212, 165, 116);
69
+ var ui = {
70
+ // Brand
71
+ brand: (text) => maroon.bold(text),
72
+ accent: (text) => warm(text),
73
+ // Headers
74
+ header: (text) => {
75
+ const line = "\u2500".repeat(50);
76
+ return `
77
+ ${maroon("\u250C" + line + "\u2510")}
78
+ ${maroon("\u2502")} ${maroon.bold(text.padEnd(49))}${maroon("\u2502")}
79
+ ${maroon("\u2514" + line + "\u2518")}
80
+ `;
81
+ },
82
+ // Sections
83
+ section: (title) => `
84
+ ${warm("\u2501\u2501")} ${chalk.bold(title)} ${warm("\u2501".repeat(Math.max(0, 44 - title.length)))}`,
85
+ // Status
86
+ success: (text) => chalk.green(` \u2713 ${text}`),
87
+ warn: (text) => chalk.yellow(` \u26A0 ${text}`),
88
+ error: (text) => chalk.red(` \u2717 ${text}`),
89
+ info: (text) => chalk.cyan(` \u2139 ${text}`),
90
+ // Key-value pairs
91
+ kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
92
+ // File list
93
+ file: (path13) => chalk.dim(` ${path13}`),
94
+ // Tool display
95
+ tool: (name, reason) => ` ${warm("\u25CF")} ${chalk.bold(name)}
96
+ ${chalk.dim(reason)}`,
97
+ // Divider
98
+ divider: () => chalk.dim(` ${"\u2500".repeat(50)}`),
99
+ // Command suggestion
100
+ cmd: (command) => ` ${chalk.bold.white("$ " + command)}`,
101
+ // Env var setup
102
+ envVar: (name, desc, url) => {
103
+ let out = ` ${chalk.bold(`export ${name}=`)}${chalk.dim('"your-key-here"')}
104
+ `;
105
+ out += chalk.dim(` ${desc}`);
106
+ if (url) out += `
107
+ ${chalk.dim("Get one at:")} ${warm(url)}`;
108
+ return out;
109
+ },
110
+ // Clarification question display
111
+ question: (q, suggestion) => ` ${warm("?")} ${chalk.bold(q)}
112
+ ${chalk.dim(`suggested: ${suggestion}`)}`,
113
+ // Branded error box
114
+ errorBox: (title, message) => {
115
+ const line = "\u2500".repeat(50);
116
+ return `
117
+ ${chalk.red("\u250C" + line + "\u2510")}
118
+ ${chalk.red("\u2502")} ${chalk.red.bold(title.padEnd(49))}${chalk.red("\u2502")}
119
+ ${chalk.red("\u2514" + line + "\u2518")}
120
+
121
+ ${chalk.red("\u2717")} ${message}
122
+ `;
123
+ }
124
+ };
125
+
126
+ // src/logo.ts
127
+ import chalk2 from "chalk";
128
+ var maroon2 = chalk2.rgb(139, 0, 0);
129
+ var darkMaroon = chalk2.rgb(100, 0, 0);
130
+ var warmStone = chalk2.rgb(180, 120, 80);
131
+ var lightStone = chalk2.rgb(212, 165, 116);
132
+ var dimStone = chalk2.rgb(140, 100, 70);
133
+ var KAIRN_WORDMARK = [
134
+ maroon2("\u2588\u2588\u2557 \u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon(" ") + maroon2("\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557"),
135
+ maroon2("\u2588\u2588\u2551 \u2588\u2588\u2554\u255D") + darkMaroon(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2551") + darkMaroon(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551"),
136
+ warmStone("\u2588\u2588\u2588\u2588\u2588\u2554\u255D ") + dimStone(" ") + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551"),
137
+ warmStone("\u2588\u2588\u2554\u2550\u2588\u2588\u2557 ") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + dimStone(" ") + warmStone("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551"),
138
+ lightStone("\u2588\u2588\u2551 \u2588\u2588\u2557") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551"),
139
+ lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D")
140
+ ];
141
+ var CAIRN_ART = [
142
+ dimStone(" \u28C0\u28C0\u28C0 "),
143
+ warmStone(" \u28F4\u28FF\u28FF\u28FF\u28E6 "),
144
+ warmStone(" \u2819\u283F\u283F\u280B "),
145
+ dimStone(" \u28C0\u28E4\u28E4\u28E4\u28E4\u28C0 "),
146
+ lightStone(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
147
+ lightStone(" \u2819\u283B\u283F\u283F\u283F\u281F\u280B "),
148
+ dimStone(" \u28C0\u28E4\u28E4\u28F6\u28F6\u28F6\u28F6\u28E4\u28E4\u28C0 "),
149
+ warmStone(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
150
+ warmStone(" \u2819\u283B\u283F\u283F\u283F\u283F\u283F\u283F\u281F\u280B "),
151
+ dimStone(" \u28C0\u28E4\u28F6\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F6\u28F6\u28E4\u28C0 "),
152
+ lightStone(" \u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF "),
153
+ dimStone(" \u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809 ")
154
+ ];
155
+ function printFullBanner(subtitle) {
156
+ console.log("");
157
+ for (const line of KAIRN_WORDMARK) {
158
+ console.log(" " + line);
159
+ }
160
+ if (subtitle) {
161
+ console.log(dimStone(` ${subtitle}`));
162
+ }
163
+ console.log("");
164
+ }
165
+ function printCompactBanner() {
166
+ const line = maroon2("\u2501").repeat(50);
167
+ console.log(`
168
+ ${line}`);
169
+ console.log(` ${maroon2(" \u25C6")} ${chalk2.bold.rgb(139, 0, 0)("KAIRN")} ${dimStone("\u2014 Agent Environment Compiler")}`);
170
+ console.log(` ${line}
171
+ `);
172
+ }
173
+
64
174
  // src/commands/init.ts
65
175
  var __filename = fileURLToPath(import.meta.url);
66
176
  var __dirname = path2.dirname(__filename);
@@ -95,7 +205,7 @@ async function installSeedTemplates() {
95
205
  }
96
206
  }
97
207
  if (installed > 0) {
98
- console.log(chalk.green(` \u2713 ${installed} template${installed === 1 ? "" : "s"} installed`));
208
+ console.log(ui.success(`${installed} template${installed === 1 ? "" : "s"} installed`));
99
209
  }
100
210
  }
101
211
  var PROVIDER_MODELS = {
@@ -167,13 +277,11 @@ function detectClaudeCode() {
167
277
  }
168
278
  }
169
279
  var initCommand = new Command("init").description("Set up Kairn with your API key").action(async () => {
170
- console.log(chalk.cyan("\n Kairn Setup\n"));
280
+ printFullBanner("Setup");
171
281
  const existing = await loadConfig();
172
282
  if (existing) {
173
- console.log(
174
- chalk.yellow(" Config already exists at ") + chalk.dim(getConfigPath())
175
- );
176
- console.log(chalk.yellow(" Running setup will overwrite it.\n"));
283
+ console.log(ui.warn(`Config already exists at ${chalk3.dim(getConfigPath())}`));
284
+ console.log(ui.warn("Running setup will overwrite it.\n"));
177
285
  }
178
286
  const provider = await select({
179
287
  message: "LLM provider",
@@ -193,18 +301,16 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
193
301
  mask: "*"
194
302
  });
195
303
  if (!apiKey) {
196
- console.log(chalk.red("\n No API key provided. Aborting."));
304
+ console.log(ui.error("No API key provided. Aborting."));
197
305
  process.exit(1);
198
306
  }
199
- console.log(chalk.dim("\n Verifying API key..."));
307
+ console.log(chalk3.dim("\n Verifying API key..."));
200
308
  const valid = await verifyKey(provider, apiKey, model);
201
309
  if (!valid) {
202
- console.log(
203
- chalk.red(" Invalid API key. Check your key and try again.")
204
- );
310
+ console.log(ui.error("Invalid API key. Check your key and try again."));
205
311
  process.exit(1);
206
312
  }
207
- console.log(chalk.green(" \u2713 API key verified"));
313
+ console.log(ui.success("API key verified"));
208
314
  const config = {
209
315
  provider,
210
316
  api_key: apiKey,
@@ -213,32 +319,28 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
213
319
  created_at: (/* @__PURE__ */ new Date()).toISOString()
214
320
  };
215
321
  await saveConfig(config);
216
- console.log(
217
- chalk.green(" \u2713 Config saved to ") + chalk.dim(getConfigPath())
218
- );
219
- console.log(
220
- chalk.dim(` \u2713 Provider: ${providerInfo.name}, Model: ${model}`)
221
- );
322
+ console.log(ui.success(`Config saved to ${chalk3.dim(getConfigPath())}`));
323
+ console.log(ui.kv("Provider", providerInfo.name));
324
+ console.log(ui.kv("Model", model));
222
325
  await installSeedTemplates();
223
326
  const hasClaude = detectClaudeCode();
224
327
  if (hasClaude) {
225
- console.log(chalk.green(" \u2713 Claude Code detected"));
328
+ console.log(ui.success("Claude Code detected"));
226
329
  } else {
227
330
  console.log(
228
- chalk.yellow(
229
- " \u26A0 Claude Code not found. Install it: npm install -g @anthropic-ai/claude-code"
230
- )
331
+ ui.warn("Claude Code not found. Install it: npm install -g @anthropic-ai/claude-code")
231
332
  );
232
333
  }
233
334
  console.log(
234
- chalk.cyan("\n Ready! Run ") + chalk.bold("kairn describe") + chalk.cyan(" to create your first environment.\n")
335
+ "\n" + ui.success(`Ready! Run ${chalk3.bold("kairn describe")} to create your first environment.`) + "\n"
235
336
  );
236
337
  });
237
338
 
238
339
  // src/commands/describe.ts
239
340
  import { Command as Command2 } from "commander";
240
341
  import { input, confirm } from "@inquirer/prompts";
241
- import chalk2 from "chalk";
342
+ import chalk4 from "chalk";
343
+ import ora from "ora";
242
344
 
243
345
  // src/compiler/compile.ts
244
346
  import fs4 from "fs/promises";
@@ -531,6 +633,28 @@ Return ONLY valid JSON matching this structure:
531
633
  \`\`\`
532
634
 
533
635
  Do not include any text outside the JSON object. Do not wrap in markdown code fences.`;
636
+ var CLARIFICATION_PROMPT = `You are helping a user define their project for environment compilation.
637
+
638
+ Given their initial description, generate 3-5 clarifying questions to understand:
639
+ 1. Language and framework
640
+ 2. What the project specifically does (be precise)
641
+ 3. Primary workflow (build, research, write, analyze?)
642
+ 4. Key dependencies or integrations
643
+ 5. Target audience
644
+
645
+ For each question, provide a reasonable suggestion based on the description.
646
+
647
+ Output ONLY a JSON array:
648
+ [
649
+ { "question": "Language/framework?", "suggestion": "TypeScript + Node.js" },
650
+ ...
651
+ ]
652
+
653
+ Rules:
654
+ - Suggestions should be reasonable guesses, clearly marked as suggestions
655
+ - Keep questions short (under 10 words)
656
+ - Maximum 5 questions
657
+ - If the description is already very detailed, ask fewer questions`;
534
658
 
535
659
  // src/registry/loader.ts
536
660
  import fs3 from "fs/promises";
@@ -731,6 +855,29 @@ async function compile(intent, onProgress) {
731
855
  await fs4.writeFile(envPath, JSON.stringify(spec, null, 2), "utf-8");
732
856
  return spec;
733
857
  }
858
+ async function generateClarifications(intent, onProgress) {
859
+ const config = await loadConfig();
860
+ if (!config) {
861
+ throw new Error("No config found. Run `kairn init` first.");
862
+ }
863
+ onProgress?.("Analyzing your request...");
864
+ const clarificationConfig = { ...config };
865
+ if (config.provider === "anthropic") {
866
+ clarificationConfig.model = "claude-haiku-4-5-20251001";
867
+ }
868
+ const response = await callLLM(clarificationConfig, CLARIFICATION_PROMPT + "\n\nUser description: " + intent);
869
+ try {
870
+ let cleaned = response.trim();
871
+ if (cleaned.startsWith("```")) {
872
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
873
+ }
874
+ const jsonMatch = cleaned.match(/\[[\s\S]*\]/);
875
+ if (!jsonMatch) return [];
876
+ return JSON.parse(jsonMatch[0]);
877
+ } catch {
878
+ return [];
879
+ }
880
+ }
734
881
 
735
882
  // src/adapter/claude-code.ts
736
883
  import fs5 from "fs/promises";
@@ -994,138 +1141,170 @@ async function writeHermesEnvironment(spec, registry) {
994
1141
  }
995
1142
 
996
1143
  // src/commands/describe.ts
997
- 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("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (intentArg, options) => {
1144
+ 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) => {
1145
+ printFullBanner("The Agent Environment Compiler");
998
1146
  const config = await loadConfig();
999
1147
  if (!config) {
1000
1148
  console.log(
1001
- chalk2.red("\n No config found. Run ") + chalk2.bold("kairn init") + chalk2.red(" first.\n")
1149
+ ui.errorBox(
1150
+ "No configuration found",
1151
+ `Run ${chalk4.bold("kairn init")} to set up your API key.`
1152
+ )
1002
1153
  );
1003
1154
  process.exit(1);
1004
1155
  }
1005
- const intent = intentArg || await input({
1156
+ const intentRaw = intentArg || await input({
1006
1157
  message: "What do you want your agent to do?"
1007
1158
  });
1008
- if (!intent.trim()) {
1009
- console.log(chalk2.red("\n No description provided. Aborting.\n"));
1159
+ if (!intentRaw.trim()) {
1160
+ console.log(chalk4.red("\n No description provided. Aborting.\n"));
1010
1161
  process.exit(1);
1011
1162
  }
1012
- console.log("");
1163
+ let finalIntent = intentRaw;
1164
+ if (!options.quick) {
1165
+ console.log(ui.section("Clarification"));
1166
+ console.log(chalk4.dim(" Let me understand your project better."));
1167
+ console.log(chalk4.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
1168
+ let clarifications = [];
1169
+ try {
1170
+ clarifications = await generateClarifications(intentRaw);
1171
+ } catch {
1172
+ }
1173
+ if (clarifications.length > 0) {
1174
+ const answers = [];
1175
+ for (const c of clarifications) {
1176
+ const answer = await input({
1177
+ message: c.question,
1178
+ default: c.suggestion
1179
+ });
1180
+ answers.push({ question: c.question, answer });
1181
+ }
1182
+ const clarificationLines = answers.map((a) => `- ${a.question}: ${a.answer}`).join("\n");
1183
+ finalIntent = `User intent: "${intentRaw}"
1184
+
1185
+ Clarifications:
1186
+ ${clarificationLines}`;
1187
+ }
1188
+ }
1189
+ console.log(ui.section("Compilation"));
1190
+ const spinner = ora({ text: "Loading tool registry...", indent: 2 }).start();
1013
1191
  let spec;
1014
1192
  try {
1015
- spec = await compile(intent, (msg) => {
1016
- process.stdout.write(`\r ${chalk2.dim(msg)} `);
1193
+ spec = await compile(finalIntent, (msg) => {
1194
+ spinner.text = msg;
1017
1195
  });
1018
- process.stdout.write("\r \r");
1196
+ spinner.succeed("Environment compiled");
1019
1197
  } catch (err) {
1020
- process.stdout.write("\r \r");
1198
+ spinner.fail("Compilation failed");
1021
1199
  const msg = err instanceof Error ? err.message : String(err);
1022
- console.log(chalk2.red(`
1023
- Compilation failed: ${msg}
1200
+ console.log(chalk4.red(`
1201
+ ${msg}
1024
1202
  `));
1025
1203
  process.exit(1);
1026
1204
  }
1027
1205
  const registry = await loadRegistry();
1028
1206
  const summary = summarizeSpec(spec, registry);
1029
- console.log(chalk2.green("\n \u2713 Environment compiled\n"));
1030
- console.log(chalk2.cyan(" Name: ") + spec.name);
1031
- console.log(chalk2.cyan(" Description: ") + spec.description);
1032
- console.log(chalk2.cyan(" Tools: ") + summary.toolCount);
1033
- console.log(chalk2.cyan(" Commands: ") + summary.commandCount);
1034
- console.log(chalk2.cyan(" Rules: ") + summary.ruleCount);
1035
- console.log(chalk2.cyan(" Skills: ") + summary.skillCount);
1036
- console.log(chalk2.cyan(" Agents: ") + summary.agentCount);
1207
+ console.log("");
1208
+ console.log(ui.kv("Name:", spec.name));
1209
+ console.log(ui.kv("Description:", spec.description));
1210
+ console.log(ui.kv("Tools:", String(summary.toolCount)));
1211
+ console.log(ui.kv("Commands:", String(summary.commandCount)));
1212
+ console.log(ui.kv("Rules:", String(summary.ruleCount)));
1213
+ console.log(ui.kv("Skills:", String(summary.skillCount)));
1214
+ console.log(ui.kv("Agents:", String(summary.agentCount)));
1037
1215
  if (spec.tools.length > 0) {
1038
- console.log(chalk2.dim("\n Selected tools:"));
1216
+ console.log(ui.section("Selected Tools"));
1217
+ console.log("");
1039
1218
  for (const tool of spec.tools) {
1040
1219
  const regTool = registry.find((t) => t.id === tool.tool_id);
1041
1220
  const name = regTool?.name || tool.tool_id;
1042
- console.log(chalk2.dim(` - ${name}: ${tool.reason}`));
1043
- }
1044
- }
1045
- if (summary.pluginCommands.length > 0) {
1046
- console.log(chalk2.yellow("\n Plugins to install manually:"));
1047
- for (const cmd of summary.pluginCommands) {
1048
- console.log(chalk2.yellow(` ${cmd}`));
1221
+ console.log(ui.tool(name, tool.reason));
1222
+ console.log("");
1049
1223
  }
1050
1224
  }
1051
- console.log("");
1052
1225
  const proceed = options.yes || await confirm({
1053
1226
  message: "Generate environment in current directory?",
1054
1227
  default: true
1055
1228
  });
1056
1229
  if (!proceed) {
1057
- console.log(chalk2.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1230
+ console.log(chalk4.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1058
1231
  return;
1059
1232
  }
1060
1233
  const targetDir = process.cwd();
1061
1234
  const runtime = options.runtime ?? "claude-code";
1062
1235
  if (runtime === "hermes") {
1063
1236
  await writeHermesEnvironment(spec, registry);
1064
- console.log(chalk2.green("\n \u2713 Environment written for Hermes\n"));
1065
- console.log(chalk2.cyan("\n Ready! Run ") + chalk2.bold("hermes") + chalk2.cyan(" to start.\n"));
1237
+ console.log("\n" + ui.success("Environment written for Hermes"));
1238
+ console.log(
1239
+ chalk4.cyan("\n Ready! Run ") + chalk4.bold("hermes") + chalk4.cyan(" to start.\n")
1240
+ );
1066
1241
  } else {
1067
1242
  const written = await writeEnvironment(spec, targetDir);
1068
- console.log(chalk2.green("\n \u2713 Environment written\n"));
1243
+ console.log(ui.section("Files Written"));
1244
+ console.log("");
1069
1245
  for (const file of written) {
1070
- console.log(chalk2.dim(` ${file}`));
1246
+ console.log(ui.file(file));
1071
1247
  }
1072
1248
  if (summary.envSetup.length > 0) {
1073
- console.log(chalk2.yellow("\n API keys needed (set these environment variables):\n"));
1249
+ console.log(ui.section("Setup Required"));
1250
+ console.log("");
1074
1251
  const seen = /* @__PURE__ */ new Set();
1075
1252
  for (const env of summary.envSetup) {
1076
1253
  if (seen.has(env.envVar)) continue;
1077
1254
  seen.add(env.envVar);
1078
- console.log(chalk2.bold(` export ${env.envVar}="your-key-here"`));
1079
- console.log(chalk2.dim(` ${env.description}`));
1080
- if (env.signupUrl) {
1081
- console.log(chalk2.dim(` Get one at: ${env.signupUrl}`));
1082
- }
1255
+ console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1083
1256
  console.log("");
1084
1257
  }
1085
1258
  }
1086
1259
  if (summary.pluginCommands.length > 0) {
1087
- console.log(chalk2.yellow(" Install plugins by running these in Claude Code:"));
1260
+ console.log(ui.section("Plugins"));
1261
+ console.log("");
1088
1262
  for (const cmd of summary.pluginCommands) {
1089
- console.log(chalk2.bold(` ${cmd}`));
1263
+ console.log(ui.cmd(cmd));
1090
1264
  }
1265
+ console.log("");
1091
1266
  }
1092
- console.log(
1093
- chalk2.cyan("\n Ready! Run ") + chalk2.bold("claude") + chalk2.cyan(" to start.\n")
1094
- );
1267
+ console.log(ui.divider());
1268
+ console.log(ui.success("Ready! Run: $ claude"));
1269
+ console.log("");
1095
1270
  }
1096
1271
  });
1097
1272
 
1098
1273
  // src/commands/list.ts
1099
1274
  import { Command as Command3 } from "commander";
1100
- import chalk3 from "chalk";
1275
+ import chalk5 from "chalk";
1101
1276
  import fs7 from "fs/promises";
1102
1277
  import path7 from "path";
1103
1278
  var listCommand = new Command3("list").description("Show saved environments").action(async () => {
1279
+ printCompactBanner();
1104
1280
  const envsDir = getEnvsDir();
1105
1281
  let files;
1106
1282
  try {
1107
1283
  files = await fs7.readdir(envsDir);
1108
1284
  } catch {
1109
- console.log(chalk3.dim("\n No environments yet. Run ") + chalk3.bold("kairn describe") + chalk3.dim(" to create one.\n"));
1285
+ console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1110
1286
  return;
1111
1287
  }
1112
1288
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
1113
1289
  if (jsonFiles.length === 0) {
1114
- console.log(chalk3.dim("\n No environments yet. Run ") + chalk3.bold("kairn describe") + chalk3.dim(" to create one.\n"));
1290
+ console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1115
1291
  return;
1116
1292
  }
1117
- console.log(chalk3.cyan("\n Saved Environments\n"));
1293
+ let first = true;
1118
1294
  for (const file of jsonFiles) {
1119
1295
  try {
1120
1296
  const data = await fs7.readFile(path7.join(envsDir, file), "utf-8");
1121
1297
  const spec = JSON.parse(data);
1122
1298
  const date = new Date(spec.created_at).toLocaleDateString();
1123
1299
  const toolCount = spec.tools?.length ?? 0;
1124
- console.log(chalk3.bold(` ${spec.name}`));
1125
- console.log(chalk3.dim(` ${spec.description}`));
1126
- console.log(
1127
- chalk3.dim(` ${date} \xB7 ${toolCount} tools \xB7 ${spec.id}`)
1128
- );
1300
+ if (!first) {
1301
+ console.log(ui.divider());
1302
+ }
1303
+ first = false;
1304
+ console.log(ui.kv("Name", chalk5.bold(spec.name)));
1305
+ console.log(ui.kv("Description", spec.description));
1306
+ console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
1307
+ console.log(ui.kv("ID", chalk5.dim(spec.id)));
1129
1308
  console.log("");
1130
1309
  } catch {
1131
1310
  }
@@ -1134,10 +1313,11 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1134
1313
 
1135
1314
  // src/commands/activate.ts
1136
1315
  import { Command as Command4 } from "commander";
1137
- import chalk4 from "chalk";
1316
+ import chalk6 from "chalk";
1138
1317
  import fs8 from "fs/promises";
1139
1318
  import path8 from "path";
1140
1319
  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) => {
1320
+ printCompactBanner();
1141
1321
  const envsDir = getEnvsDir();
1142
1322
  const templatesDir = getTemplatesDir();
1143
1323
  let sourceDir;
@@ -1166,34 +1346,30 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1166
1346
  sourceDir = templatesDir;
1167
1347
  fromTemplate = true;
1168
1348
  } else {
1169
- console.log(chalk4.red(`
1170
- Environment "${envId}" not found.`));
1171
- console.log(chalk4.dim(" Run kairn list to see saved environments."));
1172
- console.log(chalk4.dim(" Run kairn templates to see available templates.\n"));
1349
+ console.log(ui.error(`Environment "${envId}" not found.`));
1350
+ console.log(chalk6.dim(" Run kairn list to see saved environments."));
1351
+ console.log(chalk6.dim(" Run kairn templates to see available templates.\n"));
1173
1352
  process.exit(1);
1174
1353
  }
1175
1354
  }
1176
1355
  const data = await fs8.readFile(path8.join(sourceDir, match), "utf-8");
1177
1356
  const spec = JSON.parse(data);
1178
- const label = fromTemplate ? chalk4.dim(" (template)") : "";
1179
- console.log(chalk4.cyan(`
1180
- Activating: ${spec.name}`) + label);
1181
- console.log(chalk4.dim(` ${spec.description}
1357
+ const label = fromTemplate ? chalk6.dim(" (template)") : "";
1358
+ console.log(chalk6.cyan(` Activating: ${spec.name}`) + label);
1359
+ console.log(chalk6.dim(` ${spec.description}
1182
1360
  `));
1183
1361
  const targetDir = process.cwd();
1184
1362
  const written = await writeEnvironment(spec, targetDir);
1185
- console.log(chalk4.green(" \u2713 Environment written\n"));
1363
+ console.log(ui.success("Environment written\n"));
1186
1364
  for (const file of written) {
1187
- console.log(chalk4.dim(` ${file}`));
1365
+ console.log(ui.file(file));
1188
1366
  }
1189
- console.log(
1190
- chalk4.cyan("\n Ready! Run ") + chalk4.bold("claude") + chalk4.cyan(" to start.\n")
1191
- );
1367
+ console.log("\n" + ui.success(`Ready! Run: $ claude`) + "\n");
1192
1368
  });
1193
1369
 
1194
1370
  // src/commands/update-registry.ts
1195
1371
  import { Command as Command5 } from "commander";
1196
- import chalk5 from "chalk";
1372
+ import chalk7 from "chalk";
1197
1373
  import fs9 from "fs/promises";
1198
1374
  import path9 from "path";
1199
1375
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -1217,21 +1393,17 @@ async function getLocalRegistryPath() {
1217
1393
  throw new Error("Could not find local tools.json registry");
1218
1394
  }
1219
1395
  var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
1396
+ printCompactBanner();
1220
1397
  const url = options.url || REGISTRY_URL;
1221
- console.log(chalk5.dim(`
1222
- Fetching registry from ${url}...`));
1398
+ console.log(chalk7.dim(` Fetching registry from ${url}...`));
1223
1399
  try {
1224
1400
  const response = await fetch(url);
1225
1401
  if (!response.ok) {
1226
1402
  console.log(
1227
- chalk5.red(` Failed to fetch registry: ${response.status} ${response.statusText}`)
1228
- );
1229
- console.log(
1230
- chalk5.dim(" The remote registry may not be available yet.")
1231
- );
1232
- console.log(
1233
- chalk5.dim(" Your local registry is still active.\n")
1403
+ ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
1234
1404
  );
1405
+ console.log(chalk7.dim(" The remote registry may not be available yet."));
1406
+ console.log(chalk7.dim(" Your local registry is still active.\n"));
1235
1407
  return;
1236
1408
  }
1237
1409
  const text = await response.text();
@@ -1242,7 +1414,7 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1242
1414
  if (tools.length === 0) throw new Error("Empty registry");
1243
1415
  } catch (err) {
1244
1416
  const msg = err instanceof Error ? err.message : String(err);
1245
- console.log(chalk5.red(` Invalid registry format: ${msg}
1417
+ console.log(ui.error(`Invalid registry format: ${msg}
1246
1418
  `));
1247
1419
  return;
1248
1420
  }
@@ -1253,21 +1425,22 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1253
1425
  } catch {
1254
1426
  }
1255
1427
  await fs9.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
1256
- console.log(chalk5.green(` \u2713 Registry updated: ${tools.length} tools`));
1257
- console.log(chalk5.dim(` Saved to: ${registryPath}`));
1258
- console.log(chalk5.dim(` Backup: ${backupPath}
1428
+ console.log(ui.success(`Registry updated: ${tools.length} tools`));
1429
+ console.log(chalk7.dim(` Saved to: ${registryPath}`));
1430
+ console.log(chalk7.dim(` Backup: ${backupPath}
1259
1431
  `));
1260
1432
  } catch (err) {
1261
1433
  const msg = err instanceof Error ? err.message : String(err);
1262
- console.log(chalk5.red(` Network error: ${msg}`));
1263
- console.log(chalk5.dim(" Your local registry is still active.\n"));
1434
+ console.log(ui.error(`Network error: ${msg}`));
1435
+ console.log(chalk7.dim(" Your local registry is still active.\n"));
1264
1436
  }
1265
1437
  });
1266
1438
 
1267
1439
  // src/commands/optimize.ts
1268
1440
  import { Command as Command6 } from "commander";
1269
1441
  import { confirm as confirm2 } from "@inquirer/prompts";
1270
- import chalk6 from "chalk";
1442
+ import chalk8 from "chalk";
1443
+ import ora2 from "ora";
1271
1444
  import fs11 from "fs/promises";
1272
1445
  import path11 from "path";
1273
1446
 
@@ -1462,12 +1635,12 @@ function simpleDiff(oldContent, newContent) {
1462
1635
  const oldLine = oldLines[i];
1463
1636
  const newLine = newLines[i];
1464
1637
  if (oldLine === void 0) {
1465
- output.push(chalk6.green(`+ ${newLine}`));
1638
+ output.push(chalk8.green(`+ ${newLine}`));
1466
1639
  } else if (newLine === void 0) {
1467
- output.push(chalk6.red(`- ${oldLine}`));
1640
+ output.push(chalk8.red(`- ${oldLine}`));
1468
1641
  } else if (oldLine !== newLine) {
1469
- output.push(chalk6.red(`- ${oldLine}`));
1470
- output.push(chalk6.green(`+ ${newLine}`));
1642
+ output.push(chalk8.red(`- ${oldLine}`));
1643
+ output.push(chalk8.green(`+ ${newLine}`));
1471
1644
  }
1472
1645
  }
1473
1646
  return output;
@@ -1486,7 +1659,7 @@ async function generateDiff(spec, targetDir) {
1486
1659
  results.push({
1487
1660
  path: relativePath,
1488
1661
  status: "new",
1489
- diff: chalk6.green("+ NEW FILE")
1662
+ diff: chalk8.green("+ NEW FILE")
1490
1663
  });
1491
1664
  } else if (oldContent === newContent) {
1492
1665
  results.push({
@@ -1584,33 +1757,33 @@ ${profile.existingClaudeMd}`);
1584
1757
  return parts.join("\n");
1585
1758
  }
1586
1759
  var optimizeCommand = new Command6("optimize").description("Scan an existing project and generate or optimize its Claude Code environment").option("-y, --yes", "Skip confirmation prompts").option("--audit-only", "Only audit the existing harness, don't generate changes").option("--diff", "Preview changes as a diff without writing").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (options) => {
1760
+ printCompactBanner();
1587
1761
  const config = await loadConfig();
1588
1762
  if (!config) {
1589
- console.log(
1590
- chalk6.red("\n No config found. Run ") + chalk6.bold("kairn init") + chalk6.red(" first.\n")
1591
- );
1763
+ console.log(ui.errorBox("KAIRN \u2014 Error", "No config found. Run kairn init first."));
1592
1764
  process.exit(1);
1593
1765
  }
1594
1766
  const targetDir = process.cwd();
1595
- console.log(chalk6.dim("\n Scanning project..."));
1767
+ console.log(ui.section("Project Scan"));
1768
+ const scanSpinner = ora2({ text: "Scanning project...", indent: 2 }).start();
1596
1769
  const profile = await scanProject(targetDir);
1597
- console.log(chalk6.cyan("\n Project Profile\n"));
1598
- if (profile.language) console.log(chalk6.dim(` Language: ${profile.language}`));
1599
- if (profile.framework) console.log(chalk6.dim(` Framework: ${profile.framework}`));
1600
- console.log(chalk6.dim(` Dependencies: ${profile.dependencies.length}`));
1601
- if (profile.testCommand) console.log(chalk6.dim(` Tests: ${profile.testCommand}`));
1602
- if (profile.buildCommand) console.log(chalk6.dim(` Build: ${profile.buildCommand}`));
1603
- if (profile.hasDocker) console.log(chalk6.dim(" Docker: yes"));
1604
- if (profile.hasCi) console.log(chalk6.dim(" CI/CD: yes"));
1605
- if (profile.envKeys.length > 0) console.log(chalk6.dim(` Env keys: ${profile.envKeys.join(", ")}`));
1770
+ scanSpinner.stop();
1771
+ if (profile.language) console.log(ui.kv("Language:", profile.language));
1772
+ if (profile.framework) console.log(ui.kv("Framework:", profile.framework));
1773
+ console.log(ui.kv("Dependencies:", String(profile.dependencies.length)));
1774
+ if (profile.testCommand) console.log(ui.kv("Tests:", profile.testCommand));
1775
+ if (profile.buildCommand) console.log(ui.kv("Build:", profile.buildCommand));
1776
+ if (profile.hasDocker) console.log(ui.kv("Docker:", "yes"));
1777
+ if (profile.hasCi) console.log(ui.kv("CI/CD:", "yes"));
1778
+ if (profile.envKeys.length > 0) console.log(ui.kv("Env keys:", profile.envKeys.join(", ")));
1606
1779
  if (profile.hasClaudeDir) {
1607
- console.log(chalk6.yellow("\n Existing .claude/ harness detected\n"));
1608
- console.log(chalk6.dim(` CLAUDE.md: ${profile.claudeMdLineCount} lines${profile.claudeMdLineCount > 200 ? chalk6.yellow(" \u26A0 bloated") : chalk6.green(" \u2713")}`));
1609
- console.log(chalk6.dim(` MCP servers: ${profile.mcpServerCount}`));
1610
- console.log(chalk6.dim(` Commands: ${profile.existingCommands.length > 0 ? profile.existingCommands.map((c) => c).join(", ") : "none"}`));
1611
- console.log(chalk6.dim(` Rules: ${profile.existingRules.length > 0 ? profile.existingRules.join(", ") : "none"}`));
1612
- console.log(chalk6.dim(` Skills: ${profile.existingSkills.length > 0 ? profile.existingSkills.join(", ") : "none"}`));
1613
- console.log(chalk6.dim(` Agents: ${profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"}`));
1780
+ console.log(ui.section("Harness Audit"));
1781
+ console.log(ui.kv("CLAUDE.md:", `${profile.claudeMdLineCount} lines${profile.claudeMdLineCount > 200 ? " \u26A0 bloated" : " \u2713"}`));
1782
+ console.log(ui.kv("MCP servers:", String(profile.mcpServerCount)));
1783
+ console.log(ui.kv("Commands:", profile.existingCommands.length > 0 ? profile.existingCommands.join(", ") : "none"));
1784
+ console.log(ui.kv("Rules:", profile.existingRules.length > 0 ? profile.existingRules.join(", ") : "none"));
1785
+ console.log(ui.kv("Skills:", profile.existingSkills.length > 0 ? profile.existingSkills.join(", ") : "none"));
1786
+ console.log(ui.kv("Agents:", profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"));
1614
1787
  const issues = [];
1615
1788
  if (profile.claudeMdLineCount > 200) issues.push("CLAUDE.md over 200 lines \u2014 move detail to rules/ or docs/");
1616
1789
  if (!profile.existingCommands.includes("help")) issues.push("Missing /project:help command");
@@ -1624,15 +1797,15 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1624
1797
  const scopedRules = profile.existingRules.filter((r) => r !== "security" && r !== "continuity");
1625
1798
  if (profile.hasSrc && scopedRules.length === 0) issues.push("No path-scoped rules \u2014 consider adding api.md, testing.md, or frontend.md rules");
1626
1799
  if (issues.length > 0) {
1627
- console.log(chalk6.yellow("\n Issues Found:\n"));
1800
+ console.log("");
1628
1801
  for (const issue of issues) {
1629
- console.log(chalk6.yellow(` \u26A0 ${issue}`));
1802
+ console.log(ui.warn(issue));
1630
1803
  }
1631
1804
  } else {
1632
- console.log(chalk6.green("\n \u2713 No obvious issues found"));
1805
+ console.log(ui.success("No obvious issues found"));
1633
1806
  }
1634
1807
  if (options.auditOnly) {
1635
- console.log(chalk6.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1808
+ console.log(chalk8.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1636
1809
  return;
1637
1810
  }
1638
1811
  if (!options.yes) {
@@ -1642,71 +1815,66 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1642
1815
  default: false
1643
1816
  });
1644
1817
  if (!proceed) {
1645
- console.log(chalk6.dim("\n Aborted.\n"));
1818
+ console.log(chalk8.dim("\n Aborted.\n"));
1646
1819
  return;
1647
1820
  }
1648
1821
  }
1649
1822
  } else {
1650
- console.log(chalk6.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1823
+ console.log(chalk8.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1651
1824
  if (!options.yes) {
1652
1825
  const proceed = await confirm2({
1653
1826
  message: "Generate Claude Code environment for this project?",
1654
1827
  default: true
1655
1828
  });
1656
1829
  if (!proceed) {
1657
- console.log(chalk6.dim("\n Aborted.\n"));
1830
+ console.log(chalk8.dim("\n Aborted.\n"));
1658
1831
  return;
1659
1832
  }
1660
1833
  }
1661
1834
  }
1662
1835
  const intent = buildOptimizeIntent(profile);
1663
1836
  let spec;
1837
+ const spinner = ora2({ text: "Compiling optimized environment...", indent: 2 }).start();
1664
1838
  try {
1665
1839
  spec = await compile(intent, (msg) => {
1666
- process.stdout.write(`\r ${chalk6.dim(msg)} `);
1840
+ spinner.text = msg;
1667
1841
  });
1668
- process.stdout.write("\r \r");
1842
+ spinner.succeed("Environment compiled");
1669
1843
  } catch (err) {
1670
- process.stdout.write("\r \r");
1844
+ spinner.fail("Compilation failed");
1671
1845
  const msg = err instanceof Error ? err.message : String(err);
1672
- console.log(chalk6.red(`
1673
- Optimization failed: ${msg}
1674
- `));
1846
+ console.log(ui.errorBox("KAIRN \u2014 Error", `Optimization failed: ${msg}`));
1675
1847
  process.exit(1);
1676
1848
  }
1677
1849
  const registry = await loadRegistry();
1678
1850
  const summary = summarizeSpec(spec, registry);
1679
- console.log(chalk6.green(" \u2713 Environment compiled\n"));
1680
- console.log(chalk6.cyan(" Name: ") + spec.name);
1681
- console.log(chalk6.cyan(" Tools: ") + summary.toolCount);
1682
- console.log(chalk6.cyan(" Commands: ") + summary.commandCount);
1683
- console.log(chalk6.cyan(" Rules: ") + summary.ruleCount);
1684
- console.log(chalk6.cyan(" Skills: ") + summary.skillCount);
1685
- console.log(chalk6.cyan(" Agents: ") + summary.agentCount);
1851
+ console.log("");
1852
+ console.log(ui.kv("Name:", spec.name));
1853
+ console.log(ui.kv("Tools:", String(summary.toolCount)));
1854
+ console.log(ui.kv("Commands:", String(summary.commandCount)));
1855
+ console.log(ui.kv("Rules:", String(summary.ruleCount)));
1856
+ console.log(ui.kv("Skills:", String(summary.skillCount)));
1857
+ console.log(ui.kv("Agents:", String(summary.agentCount)));
1686
1858
  if (spec.tools.length > 0) {
1687
- console.log(chalk6.dim("\n Selected tools:"));
1859
+ console.log(ui.section("Selected Tools"));
1688
1860
  for (const tool of spec.tools) {
1689
1861
  const regTool = registry.find((t) => t.id === tool.tool_id);
1690
1862
  const name = regTool?.name || tool.tool_id;
1691
- console.log(chalk6.dim(` - ${name}: ${tool.reason}`));
1692
- }
1693
- }
1694
- if (summary.pluginCommands.length > 0) {
1695
- console.log(chalk6.yellow("\n Plugins to install manually:"));
1696
- for (const cmd of summary.pluginCommands) {
1697
- console.log(chalk6.yellow(` ${cmd}`));
1863
+ console.log(ui.tool(name, tool.reason));
1698
1864
  }
1699
1865
  }
1700
1866
  if (options.diff) {
1701
1867
  const diffs = await generateDiff(spec, targetDir);
1702
1868
  const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
1703
1869
  if (changedDiffs.length === 0) {
1704
- console.log(chalk6.green("\n \u2713 No changes needed \u2014 environment is already up to date.\n"));
1870
+ console.log(ui.success("No changes needed \u2014 environment is already up to date."));
1871
+ console.log("");
1705
1872
  return;
1706
1873
  }
1707
- console.log(chalk6.cyan("\n Changes preview:\n"));
1874
+ console.log(ui.section("Changes Preview"));
1708
1875
  for (const d of changedDiffs) {
1709
- console.log(chalk6.cyan(` --- ${d.path}`));
1876
+ console.log(chalk8.cyan(`
1877
+ --- ${d.path}`));
1710
1878
  if (d.status === "new") {
1711
1879
  console.log(` ${d.diff}`);
1712
1880
  } else {
@@ -1714,57 +1882,55 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1714
1882
  console.log(` ${line}`);
1715
1883
  }
1716
1884
  }
1717
- console.log("");
1718
1885
  }
1886
+ console.log("");
1719
1887
  const apply = await confirm2({
1720
1888
  message: "Apply these changes?",
1721
1889
  default: true
1722
1890
  });
1723
1891
  if (!apply) {
1724
- console.log(chalk6.dim("\n Aborted.\n"));
1892
+ console.log(chalk8.dim("\n Aborted.\n"));
1725
1893
  return;
1726
1894
  }
1727
1895
  }
1728
1896
  const runtime = options.runtime ?? "claude-code";
1729
1897
  if (runtime === "hermes") {
1730
1898
  await writeHermesEnvironment(spec, registry);
1731
- console.log(chalk6.green("\n \u2713 Environment written for Hermes\n"));
1732
- console.log(chalk6.cyan("\n Ready! Run ") + chalk6.bold("hermes") + chalk6.cyan(" to start.\n"));
1899
+ console.log(ui.divider());
1900
+ console.log(ui.success(`Ready! Run: $ hermes`));
1901
+ console.log("");
1733
1902
  } else {
1734
1903
  const written = await writeEnvironment(spec, targetDir);
1735
- console.log(chalk6.green("\n \u2713 Environment written\n"));
1904
+ console.log(ui.section("Files Written"));
1736
1905
  for (const file of written) {
1737
- console.log(chalk6.dim(` ${file}`));
1906
+ console.log(ui.file(file));
1738
1907
  }
1739
1908
  if (summary.envSetup.length > 0) {
1740
- console.log(chalk6.yellow("\n API keys needed (set these environment variables):\n"));
1909
+ console.log(ui.section("Setup Required"));
1741
1910
  const seen = /* @__PURE__ */ new Set();
1742
1911
  for (const env of summary.envSetup) {
1743
1912
  if (seen.has(env.envVar)) continue;
1744
1913
  seen.add(env.envVar);
1745
- console.log(chalk6.bold(` export ${env.envVar}="your-key-here"`));
1746
- console.log(chalk6.dim(` ${env.description}`));
1747
- if (env.signupUrl) {
1748
- console.log(chalk6.dim(` Get one at: ${env.signupUrl}`));
1749
- }
1914
+ console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1750
1915
  console.log("");
1751
1916
  }
1752
1917
  }
1753
1918
  if (summary.pluginCommands.length > 0) {
1754
- console.log(chalk6.yellow(" Install plugins by running these in Claude Code:"));
1919
+ console.log(ui.section("Plugins"));
1755
1920
  for (const cmd of summary.pluginCommands) {
1756
- console.log(chalk6.bold(` ${cmd}`));
1921
+ console.log(ui.cmd(cmd));
1757
1922
  }
1923
+ console.log("");
1758
1924
  }
1759
- console.log(
1760
- chalk6.cyan("\n Ready! Run ") + chalk6.bold("claude") + chalk6.cyan(" to start.\n")
1761
- );
1925
+ console.log(ui.divider());
1926
+ console.log(ui.success("Ready! Run: $ claude"));
1927
+ console.log("");
1762
1928
  }
1763
1929
  });
1764
1930
 
1765
1931
  // src/commands/doctor.ts
1766
1932
  import { Command as Command7 } from "commander";
1767
- import chalk7 from "chalk";
1933
+ import chalk9 from "chalk";
1768
1934
  function runChecks(profile) {
1769
1935
  const checks = [];
1770
1936
  if (!profile.existingClaudeMd) {
@@ -1894,21 +2060,28 @@ function runChecks(profile) {
1894
2060
  var doctorCommand = new Command7("doctor").description(
1895
2061
  "Validate the current Claude Code environment against best practices"
1896
2062
  ).action(async () => {
2063
+ printFullBanner("Doctor");
1897
2064
  const targetDir = process.cwd();
1898
- console.log(chalk7.dim("\n Checking .claude/ environment...\n"));
2065
+ console.log(chalk9.dim(" Checking .claude/ environment...\n"));
1899
2066
  const profile = await scanProject(targetDir);
1900
2067
  if (!profile.hasClaudeDir) {
1901
- console.log(chalk7.red(" \u274C No .claude/ directory found.\n"));
2068
+ console.log(ui.error("No .claude/ directory found.\n"));
1902
2069
  console.log(
1903
- chalk7.dim(" Run ") + chalk7.bold("kairn describe") + chalk7.dim(" or ") + chalk7.bold("kairn optimize") + chalk7.dim(" to generate one.\n")
2070
+ chalk9.dim(" Run ") + chalk9.bold("kairn describe") + chalk9.dim(" or ") + chalk9.bold("kairn optimize") + chalk9.dim(" to generate one.\n")
1904
2071
  );
1905
2072
  process.exit(1);
1906
2073
  }
1907
2074
  const checks = runChecks(profile);
2075
+ console.log(ui.section("Health Check"));
2076
+ console.log("");
1908
2077
  for (const check of checks) {
1909
- const icon = check.status === "pass" ? chalk7.green("\u2705") : check.status === "warn" ? chalk7.yellow("\u26A0\uFE0F ") : chalk7.red("\u274C");
1910
- const msg = check.status === "pass" ? chalk7.dim(check.message) : check.status === "warn" ? chalk7.yellow(check.message) : chalk7.red(check.message);
1911
- console.log(` ${icon} ${check.name}: ${msg}`);
2078
+ if (check.status === "pass") {
2079
+ console.log(ui.success(`${check.name}: ${check.message}`));
2080
+ } else if (check.status === "warn") {
2081
+ console.log(ui.warn(`${check.name}: ${check.message}`));
2082
+ } else {
2083
+ console.log(ui.error(`${check.name}: ${check.message}`));
2084
+ }
1912
2085
  }
1913
2086
  const maxScore = checks.reduce((sum, c) => sum + c.weight, 0);
1914
2087
  const score = checks.reduce((sum, c) => {
@@ -1917,7 +2090,7 @@ var doctorCommand = new Command7("doctor").description(
1917
2090
  return sum;
1918
2091
  }, 0);
1919
2092
  const percentage = Math.round(score / maxScore * 100);
1920
- const scoreColor = percentage >= 80 ? chalk7.green : percentage >= 50 ? chalk7.yellow : chalk7.red;
2093
+ const scoreColor = percentage >= 80 ? chalk9.green : percentage >= 50 ? chalk9.yellow : chalk9.red;
1921
2094
  console.log(
1922
2095
  `
1923
2096
  Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
@@ -1925,24 +2098,24 @@ var doctorCommand = new Command7("doctor").description(
1925
2098
  );
1926
2099
  if (percentage < 80) {
1927
2100
  console.log(
1928
- chalk7.dim(" Run ") + chalk7.bold("kairn optimize") + chalk7.dim(" to fix issues.\n")
2101
+ chalk9.dim(" Run ") + chalk9.bold("kairn optimize") + chalk9.dim(" to fix issues.\n")
1929
2102
  );
1930
2103
  }
1931
2104
  });
1932
2105
 
1933
2106
  // src/commands/registry.ts
1934
2107
  import { Command as Command8 } from "commander";
1935
- import chalk8 from "chalk";
2108
+ import chalk10 from "chalk";
1936
2109
  import { input as input2, select as select2 } from "@inquirer/prompts";
1937
2110
  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) => {
2111
+ printCompactBanner();
1938
2112
  let all;
1939
2113
  let userTools;
1940
2114
  try {
1941
2115
  [all, userTools] = await Promise.all([loadRegistry(), loadUserRegistry()]);
1942
2116
  } catch (err) {
1943
2117
  const msg = err instanceof Error ? err.message : String(err);
1944
- console.log(chalk8.red(`
1945
- Failed to load registry: ${msg}
2118
+ console.log(ui.error(`Failed to load registry: ${msg}
1946
2119
  `));
1947
2120
  process.exit(1);
1948
2121
  }
@@ -1957,12 +2130,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1957
2130
  );
1958
2131
  }
1959
2132
  if (tools.length === 0) {
1960
- console.log(chalk8.dim("\n No tools found.\n"));
2133
+ console.log(chalk10.dim("\n No tools found.\n"));
1961
2134
  return;
1962
2135
  }
1963
2136
  const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
1964
2137
  const userCount = userIds.size;
1965
- console.log(chalk8.cyan("\n Registry Tools\n"));
2138
+ console.log(ui.section("Registry Tools"));
2139
+ console.log("");
1966
2140
  for (const tool of tools) {
1967
2141
  const isUser = userIds.has(tool.id);
1968
2142
  const meta = [
@@ -1970,13 +2144,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1970
2144
  `tier ${tool.tier}`,
1971
2145
  tool.auth
1972
2146
  ].join(", ");
1973
- console.log(chalk8.bold(` ${tool.id}`) + chalk8.dim(` (${meta})`));
1974
- console.log(chalk8.dim(` ${tool.description}`));
2147
+ console.log(` ${ui.accent(tool.id)}` + chalk10.dim(` (${meta})`));
2148
+ console.log(chalk10.dim(` ${tool.description}`));
1975
2149
  if (tool.best_for.length > 0) {
1976
- console.log(chalk8.dim(` Best for: ${tool.best_for.join(", ")}`));
2150
+ console.log(chalk10.dim(` Best for: ${tool.best_for.join(", ")}`));
1977
2151
  }
1978
2152
  if (isUser) {
1979
- console.log(chalk8.yellow(" [USER-DEFINED]"));
2153
+ console.log(chalk10.yellow(" [USER-DEFINED]"));
1980
2154
  }
1981
2155
  console.log("");
1982
2156
  }
@@ -1984,7 +2158,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1984
2158
  const shownUser = tools.filter((t) => userIds.has(t.id)).length;
1985
2159
  const shownBundled = totalShown - shownUser;
1986
2160
  console.log(
1987
- chalk8.dim(
2161
+ chalk10.dim(
1988
2162
  ` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
1989
2163
  ) + "\n"
1990
2164
  );
@@ -2082,26 +2256,24 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2082
2256
  ...env_vars.length > 0 ? { env_vars } : {},
2083
2257
  ...signup_url ? { signup_url } : {}
2084
2258
  };
2085
- let userTools;
2259
+ let userToolsList;
2086
2260
  try {
2087
- userTools = await loadUserRegistry();
2261
+ userToolsList = await loadUserRegistry();
2088
2262
  } catch {
2089
- userTools = [];
2263
+ userToolsList = [];
2090
2264
  }
2091
- const existingIdx = userTools.findIndex((t) => t.id === id);
2265
+ const existingIdx = userToolsList.findIndex((t) => t.id === id);
2092
2266
  if (existingIdx >= 0) {
2093
- userTools[existingIdx] = tool;
2267
+ userToolsList[existingIdx] = tool;
2094
2268
  } else {
2095
- userTools.push(tool);
2269
+ userToolsList.push(tool);
2096
2270
  }
2097
- await saveUserRegistry(userTools);
2098
- console.log(chalk8.green(`
2099
- \u2713 Tool ${id} added to user registry
2271
+ await saveUserRegistry(userToolsList);
2272
+ console.log(ui.success(`Tool ${id} added to user registry
2100
2273
  `));
2101
2274
  } catch (err) {
2102
2275
  const msg = err instanceof Error ? err.message : String(err);
2103
- console.log(chalk8.red(`
2104
- Failed to add tool: ${msg}
2276
+ console.log(ui.error(`Failed to add tool: ${msg}
2105
2277
  `));
2106
2278
  process.exit(1);
2107
2279
  }
@@ -2110,19 +2282,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
2110
2282
 
2111
2283
  // src/commands/templates.ts
2112
2284
  import { Command as Command9 } from "commander";
2113
- import chalk9 from "chalk";
2285
+ import chalk11 from "chalk";
2114
2286
  import fs12 from "fs/promises";
2115
2287
  import path12 from "path";
2116
2288
  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) => {
2289
+ printCompactBanner();
2117
2290
  const templatesDir = getTemplatesDir();
2118
2291
  let files;
2119
2292
  try {
2120
2293
  files = await fs12.readdir(templatesDir);
2121
2294
  } catch {
2122
2295
  console.log(
2123
- chalk9.dim(
2124
- "\n No templates found. Templates will be installed with "
2125
- ) + chalk9.bold("kairn init") + chalk9.dim(
2296
+ chalk11.dim(
2297
+ " No templates found. Templates will be installed with "
2298
+ ) + chalk11.bold("kairn init") + chalk11.dim(
2126
2299
  " or you can add .json files to ~/.kairn/templates/\n"
2127
2300
  )
2128
2301
  );
@@ -2131,9 +2304,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
2131
2304
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2132
2305
  if (jsonFiles.length === 0) {
2133
2306
  console.log(
2134
- chalk9.dim(
2135
- "\n No templates found. Templates will be installed with "
2136
- ) + chalk9.bold("kairn init") + chalk9.dim(
2307
+ chalk11.dim(
2308
+ " No templates found. Templates will be installed with "
2309
+ ) + chalk11.bold("kairn init") + chalk11.dim(
2137
2310
  " or you can add .json files to ~/.kairn/templates/\n"
2138
2311
  )
2139
2312
  );
@@ -2161,30 +2334,27 @@ var templatesCommand = new Command9("templates").description("Browse available t
2161
2334
  }
2162
2335
  if (filtered.length === 0) {
2163
2336
  console.log(
2164
- chalk9.dim(`
2165
- No templates matched category "${options.category}".
2337
+ chalk11.dim(` No templates matched category "${options.category}".
2166
2338
  `)
2167
2339
  );
2168
2340
  return;
2169
2341
  }
2170
- console.log(chalk9.cyan("\n Available Templates\n"));
2342
+ console.log(ui.section("Templates"));
2343
+ console.log("");
2171
2344
  for (const spec of filtered) {
2172
2345
  const toolCount = spec.tools?.length ?? 0;
2173
2346
  const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
2174
2347
  const ruleCount = Object.keys(spec.harness?.rules ?? {}).length;
2348
+ console.log(ui.kv("Name", chalk11.bold(spec.name)));
2349
+ console.log(ui.kv("ID", chalk11.dim(spec.id)));
2350
+ console.log(ui.kv("Description", spec.description));
2175
2351
  console.log(
2176
- chalk9.bold(` ${spec.name}`) + chalk9.dim(` (ID: ${spec.id})`)
2177
- );
2178
- console.log(chalk9.dim(` ${spec.description}`));
2179
- console.log(
2180
- chalk9.dim(
2181
- ` Tools: ${toolCount} | Commands: ${commandCount} | Rules: ${ruleCount}`
2182
- )
2352
+ ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
2183
2353
  );
2184
2354
  console.log("");
2185
2355
  }
2186
2356
  console.log(
2187
- chalk9.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2357
+ chalk11.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2188
2358
  `)
2189
2359
  );
2190
2360
  });
@@ -2193,7 +2363,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
2193
2363
  var program = new Command10();
2194
2364
  program.name("kairn").description(
2195
2365
  "Compile natural language intent into optimized Claude Code environments"
2196
- ).version("1.5.1");
2366
+ ).version("1.6.0").option("--no-color", "Disable colored output");
2197
2367
  program.addCommand(initCommand);
2198
2368
  program.addCommand(describeCommand);
2199
2369
  program.addCommand(optimizeCommand);
@@ -2203,5 +2373,8 @@ program.addCommand(updateRegistryCommand);
2203
2373
  program.addCommand(doctorCommand);
2204
2374
  program.addCommand(registryCommand);
2205
2375
  program.addCommand(templatesCommand);
2376
+ if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
2377
+ chalk12.level = 0;
2378
+ }
2206
2379
  program.parse();
2207
2380
  //# sourceMappingURL=cli.js.map