kairn-cli 1.5.1 → 1.7.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";
@@ -260,12 +362,12 @@ You must output a JSON object matching the EnvironmentSpec schema.
260
362
 
261
363
  - **Minimalism over completeness.** Fewer, well-chosen tools beat many generic ones. Each MCP server costs 500-2000 context tokens.
262
364
  - **Workflow-specific, not generic.** Every instruction, command, and rule must relate to the user's actual workflow.
263
- - **Concise CLAUDE.md.** Under 100 lines. No generic text like "be helpful." Include build/test commands, reference docs/ and skills/.
365
+ - **Concise CLAUDE.md.** Under 120 lines. No generic text like "be helpful." Include build/test commands, reference docs/ and skills/.
264
366
  - **Security by default.** Always include deny rules for destructive commands and secret file access.
265
367
 
266
368
  ## CLAUDE.md Template (mandatory structure)
267
369
 
268
- The \`claude_md\` field MUST follow this exact structure (max 100 lines):
370
+ The \`claude_md\` field MUST follow this exact structure (max 120 lines):
269
371
 
270
372
  \`\`\`
271
373
  # {Project Name}
@@ -290,6 +392,30 @@ The \`claude_md\` field MUST follow this exact structure (max 100 lines):
290
392
 
291
393
  ## Output
292
394
  {where results go, key files}
395
+
396
+ ## Verification
397
+ After implementing any change, verify it works:
398
+ - {build command} \u2014 must pass with no errors
399
+ - {test command} \u2014 all tests must pass
400
+ - {lint command} \u2014 no warnings or errors
401
+ - {type check command} \u2014 no type errors
402
+
403
+ If any verification step fails, fix the issue before moving on.
404
+ Do NOT skip verification steps.
405
+
406
+ ## Known Gotchas
407
+ <!-- After any correction, add it here: "Update CLAUDE.md so you don't make that mistake again." -->
408
+ <!-- Prune this section when it exceeds 10 items \u2014 keep only the recurring ones. -->
409
+ - (none yet \u2014 this section grows as you work)
410
+
411
+ ## Debugging
412
+ When debugging, paste raw error output. Don't summarize \u2014 Claude works better with raw data.
413
+ Use subagents for deep investigation to keep main context clean.
414
+
415
+ ## Git Workflow
416
+ - Prefer small, focused commits (one feature or fix per commit)
417
+ - Use conventional commits: feat:, fix:, docs:, refactor:, test:
418
+ - Target < 200 lines per PR when possible
293
419
  \`\`\`
294
420
 
295
421
  Do not add generic filler. Every line must be specific to the user's workflow.
@@ -308,6 +434,10 @@ Do not add generic filler. Every line must be specific to the user's workflow.
308
434
  10. A \`/project:status\` command for code projects (uses ! for live git/test output)
309
435
  11. A \`/project:fix\` command for code projects (uses $ARGUMENTS for issue number)
310
436
  12. A \`docs/SPRINT.md\` file for sprint contracts (acceptance criteria, verification steps)
437
+ 13. A "Verification" section in CLAUDE.md with concrete verify commands for the project
438
+ 14. A "Known Gotchas" section in CLAUDE.md (starts empty, grows with corrections)
439
+ 15. A "Debugging" section in CLAUDE.md (2 lines: paste raw errors, use subagents)
440
+ 16. A "Git Workflow" section in CLAUDE.md (3 rules: small commits, conventional format, <200 lines PR)
311
441
 
312
442
  ## Shell-Integrated Commands
313
443
 
@@ -428,7 +558,7 @@ Merge this into the settings hooks alongside the PreToolUse and PostToolUse hook
428
558
  ## Context Budget (STRICT)
429
559
 
430
560
  - MCP servers: maximum 6. Prefer fewer.
431
- - CLAUDE.md: maximum 100 lines.
561
+ - CLAUDE.md: maximum 120 lines.
432
562
  - Rules: maximum 5 files, each under 20 lines.
433
563
  - Skills: maximum 3. Only include directly relevant ones.
434
564
  - Agents: maximum 3. QA pipeline + one specialist.
@@ -456,6 +586,10 @@ Each MCP server costs 500-2000 tokens of context window.
456
586
  - \`@qa-orchestrator\` (sonnet) \u2014 delegates to linter and e2e-tester, compiles QA report
457
587
  - \`@linter\` (haiku) \u2014 runs formatters, linters, security scanners
458
588
  - \`@e2e-tester\` (sonnet, only when Playwright is in tools) \u2014 browser-based QA via Playwright
589
+ - \`/project:spec\` command (interview-based spec creation \u2014 asks 5-8 questions one at a time, writes structured spec to docs/SPRINT.md, does NOT start coding until confirmed)
590
+ - \`/project:prove\` command (runs tests, shows git diff vs main, rates confidence HIGH/MEDIUM/LOW with evidence)
591
+ - \`/project:grill\` command (adversarial code review \u2014 challenges each change with "why this approach?", "what if X input?", rates BLOCKER/SHOULD-FIX/NITPICK, blocks until BLOCKERs resolved)
592
+ - \`/project:reset\` command (reads DECISIONS.md and LEARNINGS.md, proposes clean restart, stashes current work, implements elegant solution)
459
593
 
460
594
  ## For Research Projects, Additionally Include
461
595
 
@@ -463,6 +597,7 @@ Each MCP server costs 500-2000 tokens of context window.
463
597
  - \`/project:summarize\` command (summarize findings)
464
598
  - A research-synthesis skill
465
599
  - A researcher agent
600
+ - Note: the Verification section in CLAUDE.md should adapt for research \u2014 e.g. "Verify all sources are cited" instead of build/test commands
466
601
 
467
602
  ## For Content/Writing Projects, Additionally Include
468
603
 
@@ -491,7 +626,7 @@ Return ONLY valid JSON matching this structure:
491
626
  { "tool_id": "id-from-registry", "reason": "why this tool fits" }
492
627
  ],
493
628
  "harness": {
494
- "claude_md": "The full CLAUDE.md content (under 100 lines)",
629
+ "claude_md": "The full CLAUDE.md content (under 120 lines)",
495
630
  "settings": {
496
631
  "permissions": {
497
632
  "allow": ["Bash(npm run *)", "Read", "Write", "Edit"],
@@ -506,7 +641,11 @@ Return ONLY valid JSON matching this structure:
506
641
  "tasks": "markdown content for /project:tasks",
507
642
  "status": "Show project status:\\n\\n!git status --short\\n\\n!git log --oneline -5\\n\\nRead TODO.md and summarize progress.",
508
643
  "fix": "Fix issue #$ARGUMENTS:\\n\\n1. Read the issue and understand the problem\\n2. Plan the fix\\n3. Implement the fix\\n4. Run tests:\\n\\n!npm test 2>&1 | tail -20\\n\\n5. Commit with: fix: resolve #$ARGUMENTS",
509
- "sprint": "Define a sprint contract for the next feature:\\n\\n1. Read docs/TODO.md for context:\\n\\n!cat docs/TODO.md 2>/dev/null\\n\\n2. Write a CONTRACT to docs/SPRINT.md with: feature name, acceptance criteria, verification steps, files to modify, scope estimate.\\n3. Do NOT start coding until contract is confirmed."
644
+ "sprint": "Define a sprint contract for the next feature:\\n\\n1. Read docs/TODO.md for context:\\n\\n!cat docs/TODO.md 2>/dev/null\\n\\n2. Write a CONTRACT to docs/SPRINT.md with: feature name, acceptance criteria, verification steps, files to modify, scope estimate.\\n3. Do NOT start coding until contract is confirmed.",
645
+ "spec": "Before building this feature, interview me to create a complete spec.\\n\\nAsk me 5-8 questions, one at a time:\\n1. What specifically should this feature do?\\n2. Who uses it and how?\\n3. What are the edge cases or error states?\\n4. How will we know it works? (acceptance criteria)\\n5. What should it explicitly NOT do? (scope boundaries)\\n6. Any dependencies, APIs, or constraints?\\n7. How does it fit with existing code?\\n8. Priority: speed, quality, or flexibility?\\n\\nAfter my answers, write a structured spec to docs/SPRINT.md:\\n- Feature name\\n- Description (from my answers, not invented)\\n- Acceptance criteria (testable)\\n- Out of scope\\n- Technical approach\\n\\nDo NOT start coding until I confirm the spec.",
646
+ "prove": "Prove the current implementation works.\\n\\n1. Run the full test suite:\\n\\n!npm test 2>&1\\n\\n2. Compare against main:\\n\\n!git diff main --stat 2>/dev/null\\n\\n3. Show evidence:\\n - Test results (pass/fail counts)\\n - Behavioral diff (main vs this branch)\\n - Edge cases tested\\n - Error handling verified\\n\\n4. Rate confidence:\\n - HIGH: All tests pass, edge cases covered, no regressions\\n - MEDIUM: Core works, some edges untested\\n - LOW: Needs more verification\\n\\nIf LOW or MEDIUM, explain what's missing and fix it.",
647
+ "grill": "Review the current changes adversarially.\\n\\n!git diff --staged 2>/dev/null || git diff HEAD 2>/dev/null\\n\\nAct as a senior engineer. For each file changed:\\n\\n1. \\"Why this approach over X?\\"\\n2. \\"What happens if Y input?\\"\\n3. \\"Performance impact of Z?\\"\\n4. \\"This could break if...\\"\\n\\nFor each concern:\\n- Severity: BLOCKER / SHOULD-FIX / NITPICK\\n- The exact scenario that could fail\\n- A suggested alternative\\n\\nDo NOT approve until all BLOCKERs are resolved.",
648
+ "reset": "Stop. Read docs/DECISIONS.md and docs/LEARNINGS.md.\\n\\nConsidering everything we've learned:\\n1. What was the original approach?\\n2. What went wrong or feels inelegant?\\n3. What would the clean solution look like?\\n\\nPropose the new approach. Do NOT implement yet.\\nIf I approve, stash current changes:\\n git stash -m \\"pre-reset: $(date +%Y%m%d-%H%M)\\"\\n\\nThen implement the elegant solution."
510
649
  },
511
650
  "rules": {
512
651
  "continuity": "markdown content for continuity rule",
@@ -531,6 +670,28 @@ Return ONLY valid JSON matching this structure:
531
670
  \`\`\`
532
671
 
533
672
  Do not include any text outside the JSON object. Do not wrap in markdown code fences.`;
673
+ var CLARIFICATION_PROMPT = `You are helping a user define their project for environment compilation.
674
+
675
+ Given their initial description, generate 3-5 clarifying questions to understand:
676
+ 1. Language and framework
677
+ 2. What the project specifically does (be precise)
678
+ 3. Primary workflow (build, research, write, analyze?)
679
+ 4. Key dependencies or integrations
680
+ 5. Target audience
681
+
682
+ For each question, provide a reasonable suggestion based on the description.
683
+
684
+ Output ONLY a JSON array:
685
+ [
686
+ { "question": "Language/framework?", "suggestion": "TypeScript + Node.js" },
687
+ ...
688
+ ]
689
+
690
+ Rules:
691
+ - Suggestions should be reasonable guesses, clearly marked as suggestions
692
+ - Keep questions short (under 10 words)
693
+ - Maximum 5 questions
694
+ - If the description is already very detailed, ask fewer questions`;
534
695
 
535
696
  // src/registry/loader.ts
536
697
  import fs3 from "fs/promises";
@@ -731,10 +892,49 @@ async function compile(intent, onProgress) {
731
892
  await fs4.writeFile(envPath, JSON.stringify(spec, null, 2), "utf-8");
732
893
  return spec;
733
894
  }
895
+ async function generateClarifications(intent, onProgress) {
896
+ const config = await loadConfig();
897
+ if (!config) {
898
+ throw new Error("No config found. Run `kairn init` first.");
899
+ }
900
+ onProgress?.("Analyzing your request...");
901
+ const clarificationConfig = { ...config };
902
+ if (config.provider === "anthropic") {
903
+ clarificationConfig.model = "claude-haiku-4-5-20251001";
904
+ }
905
+ const response = await callLLM(clarificationConfig, CLARIFICATION_PROMPT + "\n\nUser description: " + intent);
906
+ try {
907
+ let cleaned = response.trim();
908
+ if (cleaned.startsWith("```")) {
909
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
910
+ }
911
+ const jsonMatch = cleaned.match(/\[[\s\S]*\]/);
912
+ if (!jsonMatch) return [];
913
+ return JSON.parse(jsonMatch[0]);
914
+ } catch {
915
+ return [];
916
+ }
917
+ }
734
918
 
735
919
  // src/adapter/claude-code.ts
736
920
  import fs5 from "fs/promises";
737
921
  import path5 from "path";
922
+ var STATUS_LINE = {
923
+ 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
+ };
925
+ function isCodeProject(spec) {
926
+ const commands = spec.harness.commands ?? {};
927
+ return "status" in commands || "test" in commands;
928
+ }
929
+ function resolveSettings(spec) {
930
+ 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;
937
+ }
738
938
  async function writeFile(filePath, content) {
739
939
  await fs5.mkdir(path5.dirname(filePath), { recursive: true });
740
940
  await fs5.writeFile(filePath, content, "utf-8");
@@ -744,11 +944,9 @@ function buildFileMap(spec) {
744
944
  if (spec.harness.claude_md) {
745
945
  files.set(".claude/CLAUDE.md", spec.harness.claude_md);
746
946
  }
747
- if (spec.harness.settings && Object.keys(spec.harness.settings).length > 0) {
748
- files.set(
749
- ".claude/settings.json",
750
- JSON.stringify(spec.harness.settings, null, 2)
751
- );
947
+ const resolvedSettings = resolveSettings(spec);
948
+ if (resolvedSettings) {
949
+ files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
752
950
  }
753
951
  if (spec.harness.mcp_config && Object.keys(spec.harness.mcp_config).length > 0) {
754
952
  files.set(
@@ -791,9 +989,10 @@ async function writeEnvironment(spec, targetDir) {
791
989
  await writeFile(p, spec.harness.claude_md);
792
990
  written.push(".claude/CLAUDE.md");
793
991
  }
794
- if (spec.harness.settings && Object.keys(spec.harness.settings).length > 0) {
992
+ const resolvedSettings = resolveSettings(spec);
993
+ if (resolvedSettings) {
795
994
  const p = path5.join(claudeDir, "settings.json");
796
- await writeFile(p, JSON.stringify(spec.harness.settings, null, 2));
995
+ await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
797
996
  written.push(".claude/settings.json");
798
997
  }
799
998
  if (spec.harness.mcp_config && Object.keys(spec.harness.mcp_config).length > 0) {
@@ -994,138 +1193,170 @@ async function writeHermesEnvironment(spec, registry) {
994
1193
  }
995
1194
 
996
1195
  // 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) => {
1196
+ 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
+ printFullBanner("The Agent Environment Compiler");
998
1198
  const config = await loadConfig();
999
1199
  if (!config) {
1000
1200
  console.log(
1001
- chalk2.red("\n No config found. Run ") + chalk2.bold("kairn init") + chalk2.red(" first.\n")
1201
+ ui.errorBox(
1202
+ "No configuration found",
1203
+ `Run ${chalk4.bold("kairn init")} to set up your API key.`
1204
+ )
1002
1205
  );
1003
1206
  process.exit(1);
1004
1207
  }
1005
- const intent = intentArg || await input({
1208
+ const intentRaw = intentArg || await input({
1006
1209
  message: "What do you want your agent to do?"
1007
1210
  });
1008
- if (!intent.trim()) {
1009
- console.log(chalk2.red("\n No description provided. Aborting.\n"));
1211
+ if (!intentRaw.trim()) {
1212
+ console.log(chalk4.red("\n No description provided. Aborting.\n"));
1010
1213
  process.exit(1);
1011
1214
  }
1012
- console.log("");
1215
+ let finalIntent = intentRaw;
1216
+ if (!options.quick) {
1217
+ 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"));
1220
+ let clarifications = [];
1221
+ try {
1222
+ clarifications = await generateClarifications(intentRaw);
1223
+ } catch {
1224
+ }
1225
+ if (clarifications.length > 0) {
1226
+ const answers = [];
1227
+ for (const c of clarifications) {
1228
+ const answer = await input({
1229
+ message: c.question,
1230
+ default: c.suggestion
1231
+ });
1232
+ answers.push({ question: c.question, answer });
1233
+ }
1234
+ const clarificationLines = answers.map((a) => `- ${a.question}: ${a.answer}`).join("\n");
1235
+ finalIntent = `User intent: "${intentRaw}"
1236
+
1237
+ Clarifications:
1238
+ ${clarificationLines}`;
1239
+ }
1240
+ }
1241
+ console.log(ui.section("Compilation"));
1242
+ const spinner = ora({ text: "Loading tool registry...", indent: 2 }).start();
1013
1243
  let spec;
1014
1244
  try {
1015
- spec = await compile(intent, (msg) => {
1016
- process.stdout.write(`\r ${chalk2.dim(msg)} `);
1245
+ spec = await compile(finalIntent, (msg) => {
1246
+ spinner.text = msg;
1017
1247
  });
1018
- process.stdout.write("\r \r");
1248
+ spinner.succeed("Environment compiled");
1019
1249
  } catch (err) {
1020
- process.stdout.write("\r \r");
1250
+ spinner.fail("Compilation failed");
1021
1251
  const msg = err instanceof Error ? err.message : String(err);
1022
- console.log(chalk2.red(`
1023
- Compilation failed: ${msg}
1252
+ console.log(chalk4.red(`
1253
+ ${msg}
1024
1254
  `));
1025
1255
  process.exit(1);
1026
1256
  }
1027
1257
  const registry = await loadRegistry();
1028
1258
  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);
1259
+ console.log("");
1260
+ console.log(ui.kv("Name:", spec.name));
1261
+ console.log(ui.kv("Description:", spec.description));
1262
+ console.log(ui.kv("Tools:", String(summary.toolCount)));
1263
+ console.log(ui.kv("Commands:", String(summary.commandCount)));
1264
+ console.log(ui.kv("Rules:", String(summary.ruleCount)));
1265
+ console.log(ui.kv("Skills:", String(summary.skillCount)));
1266
+ console.log(ui.kv("Agents:", String(summary.agentCount)));
1037
1267
  if (spec.tools.length > 0) {
1038
- console.log(chalk2.dim("\n Selected tools:"));
1268
+ console.log(ui.section("Selected Tools"));
1269
+ console.log("");
1039
1270
  for (const tool of spec.tools) {
1040
1271
  const regTool = registry.find((t) => t.id === tool.tool_id);
1041
1272
  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}`));
1273
+ console.log(ui.tool(name, tool.reason));
1274
+ console.log("");
1049
1275
  }
1050
1276
  }
1051
- console.log("");
1052
1277
  const proceed = options.yes || await confirm({
1053
1278
  message: "Generate environment in current directory?",
1054
1279
  default: true
1055
1280
  });
1056
1281
  if (!proceed) {
1057
- console.log(chalk2.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1282
+ console.log(chalk4.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
1058
1283
  return;
1059
1284
  }
1060
1285
  const targetDir = process.cwd();
1061
1286
  const runtime = options.runtime ?? "claude-code";
1062
1287
  if (runtime === "hermes") {
1063
1288
  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"));
1289
+ console.log("\n" + ui.success("Environment written for Hermes"));
1290
+ console.log(
1291
+ chalk4.cyan("\n Ready! Run ") + chalk4.bold("hermes") + chalk4.cyan(" to start.\n")
1292
+ );
1066
1293
  } else {
1067
1294
  const written = await writeEnvironment(spec, targetDir);
1068
- console.log(chalk2.green("\n \u2713 Environment written\n"));
1295
+ console.log(ui.section("Files Written"));
1296
+ console.log("");
1069
1297
  for (const file of written) {
1070
- console.log(chalk2.dim(` ${file}`));
1298
+ console.log(ui.file(file));
1071
1299
  }
1072
1300
  if (summary.envSetup.length > 0) {
1073
- console.log(chalk2.yellow("\n API keys needed (set these environment variables):\n"));
1301
+ console.log(ui.section("Setup Required"));
1302
+ console.log("");
1074
1303
  const seen = /* @__PURE__ */ new Set();
1075
1304
  for (const env of summary.envSetup) {
1076
1305
  if (seen.has(env.envVar)) continue;
1077
1306
  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
- }
1307
+ console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1083
1308
  console.log("");
1084
1309
  }
1085
1310
  }
1086
1311
  if (summary.pluginCommands.length > 0) {
1087
- console.log(chalk2.yellow(" Install plugins by running these in Claude Code:"));
1312
+ console.log(ui.section("Plugins"));
1313
+ console.log("");
1088
1314
  for (const cmd of summary.pluginCommands) {
1089
- console.log(chalk2.bold(` ${cmd}`));
1315
+ console.log(ui.cmd(cmd));
1090
1316
  }
1317
+ console.log("");
1091
1318
  }
1092
- console.log(
1093
- chalk2.cyan("\n Ready! Run ") + chalk2.bold("claude") + chalk2.cyan(" to start.\n")
1094
- );
1319
+ console.log(ui.divider());
1320
+ console.log(ui.success("Ready! Run: $ claude"));
1321
+ console.log("");
1095
1322
  }
1096
1323
  });
1097
1324
 
1098
1325
  // src/commands/list.ts
1099
1326
  import { Command as Command3 } from "commander";
1100
- import chalk3 from "chalk";
1327
+ import chalk5 from "chalk";
1101
1328
  import fs7 from "fs/promises";
1102
1329
  import path7 from "path";
1103
1330
  var listCommand = new Command3("list").description("Show saved environments").action(async () => {
1331
+ printCompactBanner();
1104
1332
  const envsDir = getEnvsDir();
1105
1333
  let files;
1106
1334
  try {
1107
1335
  files = await fs7.readdir(envsDir);
1108
1336
  } catch {
1109
- console.log(chalk3.dim("\n No environments yet. Run ") + chalk3.bold("kairn describe") + chalk3.dim(" to create one.\n"));
1337
+ console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1110
1338
  return;
1111
1339
  }
1112
1340
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
1113
1341
  if (jsonFiles.length === 0) {
1114
- console.log(chalk3.dim("\n No environments yet. Run ") + chalk3.bold("kairn describe") + chalk3.dim(" to create one.\n"));
1342
+ console.log(chalk5.dim(" No environments yet. Run ") + chalk5.bold("kairn describe") + chalk5.dim(" to create one.\n"));
1115
1343
  return;
1116
1344
  }
1117
- console.log(chalk3.cyan("\n Saved Environments\n"));
1345
+ let first = true;
1118
1346
  for (const file of jsonFiles) {
1119
1347
  try {
1120
1348
  const data = await fs7.readFile(path7.join(envsDir, file), "utf-8");
1121
1349
  const spec = JSON.parse(data);
1122
1350
  const date = new Date(spec.created_at).toLocaleDateString();
1123
1351
  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
- );
1352
+ if (!first) {
1353
+ console.log(ui.divider());
1354
+ }
1355
+ first = false;
1356
+ console.log(ui.kv("Name", chalk5.bold(spec.name)));
1357
+ console.log(ui.kv("Description", spec.description));
1358
+ console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
1359
+ console.log(ui.kv("ID", chalk5.dim(spec.id)));
1129
1360
  console.log("");
1130
1361
  } catch {
1131
1362
  }
@@ -1134,10 +1365,11 @@ var listCommand = new Command3("list").description("Show saved environments").ac
1134
1365
 
1135
1366
  // src/commands/activate.ts
1136
1367
  import { Command as Command4 } from "commander";
1137
- import chalk4 from "chalk";
1368
+ import chalk6 from "chalk";
1138
1369
  import fs8 from "fs/promises";
1139
1370
  import path8 from "path";
1140
1371
  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
+ printCompactBanner();
1141
1373
  const envsDir = getEnvsDir();
1142
1374
  const templatesDir = getTemplatesDir();
1143
1375
  let sourceDir;
@@ -1166,34 +1398,30 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
1166
1398
  sourceDir = templatesDir;
1167
1399
  fromTemplate = true;
1168
1400
  } 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"));
1401
+ 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"));
1173
1404
  process.exit(1);
1174
1405
  }
1175
1406
  }
1176
1407
  const data = await fs8.readFile(path8.join(sourceDir, match), "utf-8");
1177
1408
  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}
1409
+ const label = fromTemplate ? chalk6.dim(" (template)") : "";
1410
+ console.log(chalk6.cyan(` Activating: ${spec.name}`) + label);
1411
+ console.log(chalk6.dim(` ${spec.description}
1182
1412
  `));
1183
1413
  const targetDir = process.cwd();
1184
1414
  const written = await writeEnvironment(spec, targetDir);
1185
- console.log(chalk4.green(" \u2713 Environment written\n"));
1415
+ console.log(ui.success("Environment written\n"));
1186
1416
  for (const file of written) {
1187
- console.log(chalk4.dim(` ${file}`));
1417
+ console.log(ui.file(file));
1188
1418
  }
1189
- console.log(
1190
- chalk4.cyan("\n Ready! Run ") + chalk4.bold("claude") + chalk4.cyan(" to start.\n")
1191
- );
1419
+ console.log("\n" + ui.success(`Ready! Run: $ claude`) + "\n");
1192
1420
  });
1193
1421
 
1194
1422
  // src/commands/update-registry.ts
1195
1423
  import { Command as Command5 } from "commander";
1196
- import chalk5 from "chalk";
1424
+ import chalk7 from "chalk";
1197
1425
  import fs9 from "fs/promises";
1198
1426
  import path9 from "path";
1199
1427
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -1217,21 +1445,17 @@ async function getLocalRegistryPath() {
1217
1445
  throw new Error("Could not find local tools.json registry");
1218
1446
  }
1219
1447
  var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
1448
+ printCompactBanner();
1220
1449
  const url = options.url || REGISTRY_URL;
1221
- console.log(chalk5.dim(`
1222
- Fetching registry from ${url}...`));
1450
+ console.log(chalk7.dim(` Fetching registry from ${url}...`));
1223
1451
  try {
1224
1452
  const response = await fetch(url);
1225
1453
  if (!response.ok) {
1226
1454
  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")
1455
+ ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
1234
1456
  );
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"));
1235
1459
  return;
1236
1460
  }
1237
1461
  const text = await response.text();
@@ -1242,7 +1466,7 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1242
1466
  if (tools.length === 0) throw new Error("Empty registry");
1243
1467
  } catch (err) {
1244
1468
  const msg = err instanceof Error ? err.message : String(err);
1245
- console.log(chalk5.red(` Invalid registry format: ${msg}
1469
+ console.log(ui.error(`Invalid registry format: ${msg}
1246
1470
  `));
1247
1471
  return;
1248
1472
  }
@@ -1253,21 +1477,22 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
1253
1477
  } catch {
1254
1478
  }
1255
1479
  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}
1480
+ console.log(ui.success(`Registry updated: ${tools.length} tools`));
1481
+ console.log(chalk7.dim(` Saved to: ${registryPath}`));
1482
+ console.log(chalk7.dim(` Backup: ${backupPath}
1259
1483
  `));
1260
1484
  } catch (err) {
1261
1485
  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"));
1486
+ console.log(ui.error(`Network error: ${msg}`));
1487
+ console.log(chalk7.dim(" Your local registry is still active.\n"));
1264
1488
  }
1265
1489
  });
1266
1490
 
1267
1491
  // src/commands/optimize.ts
1268
1492
  import { Command as Command6 } from "commander";
1269
1493
  import { confirm as confirm2 } from "@inquirer/prompts";
1270
- import chalk6 from "chalk";
1494
+ import chalk8 from "chalk";
1495
+ import ora2 from "ora";
1271
1496
  import fs11 from "fs/promises";
1272
1497
  import path11 from "path";
1273
1498
 
@@ -1462,12 +1687,12 @@ function simpleDiff(oldContent, newContent) {
1462
1687
  const oldLine = oldLines[i];
1463
1688
  const newLine = newLines[i];
1464
1689
  if (oldLine === void 0) {
1465
- output.push(chalk6.green(`+ ${newLine}`));
1690
+ output.push(chalk8.green(`+ ${newLine}`));
1466
1691
  } else if (newLine === void 0) {
1467
- output.push(chalk6.red(`- ${oldLine}`));
1692
+ output.push(chalk8.red(`- ${oldLine}`));
1468
1693
  } else if (oldLine !== newLine) {
1469
- output.push(chalk6.red(`- ${oldLine}`));
1470
- output.push(chalk6.green(`+ ${newLine}`));
1694
+ output.push(chalk8.red(`- ${oldLine}`));
1695
+ output.push(chalk8.green(`+ ${newLine}`));
1471
1696
  }
1472
1697
  }
1473
1698
  return output;
@@ -1486,7 +1711,7 @@ async function generateDiff(spec, targetDir) {
1486
1711
  results.push({
1487
1712
  path: relativePath,
1488
1713
  status: "new",
1489
- diff: chalk6.green("+ NEW FILE")
1714
+ diff: chalk8.green("+ NEW FILE")
1490
1715
  });
1491
1716
  } else if (oldContent === newContent) {
1492
1717
  results.push({
@@ -1584,33 +1809,33 @@ ${profile.existingClaudeMd}`);
1584
1809
  return parts.join("\n");
1585
1810
  }
1586
1811
  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) => {
1812
+ printCompactBanner();
1587
1813
  const config = await loadConfig();
1588
1814
  if (!config) {
1589
- console.log(
1590
- chalk6.red("\n No config found. Run ") + chalk6.bold("kairn init") + chalk6.red(" first.\n")
1591
- );
1815
+ console.log(ui.errorBox("KAIRN \u2014 Error", "No config found. Run kairn init first."));
1592
1816
  process.exit(1);
1593
1817
  }
1594
1818
  const targetDir = process.cwd();
1595
- console.log(chalk6.dim("\n Scanning project..."));
1819
+ console.log(ui.section("Project Scan"));
1820
+ const scanSpinner = ora2({ text: "Scanning project...", indent: 2 }).start();
1596
1821
  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(", ")}`));
1822
+ scanSpinner.stop();
1823
+ if (profile.language) console.log(ui.kv("Language:", profile.language));
1824
+ if (profile.framework) console.log(ui.kv("Framework:", profile.framework));
1825
+ console.log(ui.kv("Dependencies:", String(profile.dependencies.length)));
1826
+ if (profile.testCommand) console.log(ui.kv("Tests:", profile.testCommand));
1827
+ if (profile.buildCommand) console.log(ui.kv("Build:", profile.buildCommand));
1828
+ if (profile.hasDocker) console.log(ui.kv("Docker:", "yes"));
1829
+ if (profile.hasCi) console.log(ui.kv("CI/CD:", "yes"));
1830
+ if (profile.envKeys.length > 0) console.log(ui.kv("Env keys:", profile.envKeys.join(", ")));
1606
1831
  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"}`));
1832
+ console.log(ui.section("Harness Audit"));
1833
+ console.log(ui.kv("CLAUDE.md:", `${profile.claudeMdLineCount} lines${profile.claudeMdLineCount > 200 ? " \u26A0 bloated" : " \u2713"}`));
1834
+ console.log(ui.kv("MCP servers:", String(profile.mcpServerCount)));
1835
+ console.log(ui.kv("Commands:", profile.existingCommands.length > 0 ? profile.existingCommands.join(", ") : "none"));
1836
+ console.log(ui.kv("Rules:", profile.existingRules.length > 0 ? profile.existingRules.join(", ") : "none"));
1837
+ console.log(ui.kv("Skills:", profile.existingSkills.length > 0 ? profile.existingSkills.join(", ") : "none"));
1838
+ console.log(ui.kv("Agents:", profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"));
1614
1839
  const issues = [];
1615
1840
  if (profile.claudeMdLineCount > 200) issues.push("CLAUDE.md over 200 lines \u2014 move detail to rules/ or docs/");
1616
1841
  if (!profile.existingCommands.includes("help")) issues.push("Missing /project:help command");
@@ -1624,15 +1849,15 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1624
1849
  const scopedRules = profile.existingRules.filter((r) => r !== "security" && r !== "continuity");
1625
1850
  if (profile.hasSrc && scopedRules.length === 0) issues.push("No path-scoped rules \u2014 consider adding api.md, testing.md, or frontend.md rules");
1626
1851
  if (issues.length > 0) {
1627
- console.log(chalk6.yellow("\n Issues Found:\n"));
1852
+ console.log("");
1628
1853
  for (const issue of issues) {
1629
- console.log(chalk6.yellow(` \u26A0 ${issue}`));
1854
+ console.log(ui.warn(issue));
1630
1855
  }
1631
1856
  } else {
1632
- console.log(chalk6.green("\n \u2713 No obvious issues found"));
1857
+ console.log(ui.success("No obvious issues found"));
1633
1858
  }
1634
1859
  if (options.auditOnly) {
1635
- console.log(chalk6.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1860
+ console.log(chalk8.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1636
1861
  return;
1637
1862
  }
1638
1863
  if (!options.yes) {
@@ -1642,71 +1867,66 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1642
1867
  default: false
1643
1868
  });
1644
1869
  if (!proceed) {
1645
- console.log(chalk6.dim("\n Aborted.\n"));
1870
+ console.log(chalk8.dim("\n Aborted.\n"));
1646
1871
  return;
1647
1872
  }
1648
1873
  }
1649
1874
  } else {
1650
- console.log(chalk6.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1875
+ console.log(chalk8.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1651
1876
  if (!options.yes) {
1652
1877
  const proceed = await confirm2({
1653
1878
  message: "Generate Claude Code environment for this project?",
1654
1879
  default: true
1655
1880
  });
1656
1881
  if (!proceed) {
1657
- console.log(chalk6.dim("\n Aborted.\n"));
1882
+ console.log(chalk8.dim("\n Aborted.\n"));
1658
1883
  return;
1659
1884
  }
1660
1885
  }
1661
1886
  }
1662
1887
  const intent = buildOptimizeIntent(profile);
1663
1888
  let spec;
1889
+ const spinner = ora2({ text: "Compiling optimized environment...", indent: 2 }).start();
1664
1890
  try {
1665
1891
  spec = await compile(intent, (msg) => {
1666
- process.stdout.write(`\r ${chalk6.dim(msg)} `);
1892
+ spinner.text = msg;
1667
1893
  });
1668
- process.stdout.write("\r \r");
1894
+ spinner.succeed("Environment compiled");
1669
1895
  } catch (err) {
1670
- process.stdout.write("\r \r");
1896
+ spinner.fail("Compilation failed");
1671
1897
  const msg = err instanceof Error ? err.message : String(err);
1672
- console.log(chalk6.red(`
1673
- Optimization failed: ${msg}
1674
- `));
1898
+ console.log(ui.errorBox("KAIRN \u2014 Error", `Optimization failed: ${msg}`));
1675
1899
  process.exit(1);
1676
1900
  }
1677
1901
  const registry = await loadRegistry();
1678
1902
  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);
1903
+ console.log("");
1904
+ console.log(ui.kv("Name:", spec.name));
1905
+ console.log(ui.kv("Tools:", String(summary.toolCount)));
1906
+ console.log(ui.kv("Commands:", String(summary.commandCount)));
1907
+ console.log(ui.kv("Rules:", String(summary.ruleCount)));
1908
+ console.log(ui.kv("Skills:", String(summary.skillCount)));
1909
+ console.log(ui.kv("Agents:", String(summary.agentCount)));
1686
1910
  if (spec.tools.length > 0) {
1687
- console.log(chalk6.dim("\n Selected tools:"));
1911
+ console.log(ui.section("Selected Tools"));
1688
1912
  for (const tool of spec.tools) {
1689
1913
  const regTool = registry.find((t) => t.id === tool.tool_id);
1690
1914
  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}`));
1915
+ console.log(ui.tool(name, tool.reason));
1698
1916
  }
1699
1917
  }
1700
1918
  if (options.diff) {
1701
1919
  const diffs = await generateDiff(spec, targetDir);
1702
1920
  const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
1703
1921
  if (changedDiffs.length === 0) {
1704
- console.log(chalk6.green("\n \u2713 No changes needed \u2014 environment is already up to date.\n"));
1922
+ console.log(ui.success("No changes needed \u2014 environment is already up to date."));
1923
+ console.log("");
1705
1924
  return;
1706
1925
  }
1707
- console.log(chalk6.cyan("\n Changes preview:\n"));
1926
+ console.log(ui.section("Changes Preview"));
1708
1927
  for (const d of changedDiffs) {
1709
- console.log(chalk6.cyan(` --- ${d.path}`));
1928
+ console.log(chalk8.cyan(`
1929
+ --- ${d.path}`));
1710
1930
  if (d.status === "new") {
1711
1931
  console.log(` ${d.diff}`);
1712
1932
  } else {
@@ -1714,57 +1934,55 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
1714
1934
  console.log(` ${line}`);
1715
1935
  }
1716
1936
  }
1717
- console.log("");
1718
1937
  }
1938
+ console.log("");
1719
1939
  const apply = await confirm2({
1720
1940
  message: "Apply these changes?",
1721
1941
  default: true
1722
1942
  });
1723
1943
  if (!apply) {
1724
- console.log(chalk6.dim("\n Aborted.\n"));
1944
+ console.log(chalk8.dim("\n Aborted.\n"));
1725
1945
  return;
1726
1946
  }
1727
1947
  }
1728
1948
  const runtime = options.runtime ?? "claude-code";
1729
1949
  if (runtime === "hermes") {
1730
1950
  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"));
1951
+ console.log(ui.divider());
1952
+ console.log(ui.success(`Ready! Run: $ hermes`));
1953
+ console.log("");
1733
1954
  } else {
1734
1955
  const written = await writeEnvironment(spec, targetDir);
1735
- console.log(chalk6.green("\n \u2713 Environment written\n"));
1956
+ console.log(ui.section("Files Written"));
1736
1957
  for (const file of written) {
1737
- console.log(chalk6.dim(` ${file}`));
1958
+ console.log(ui.file(file));
1738
1959
  }
1739
1960
  if (summary.envSetup.length > 0) {
1740
- console.log(chalk6.yellow("\n API keys needed (set these environment variables):\n"));
1961
+ console.log(ui.section("Setup Required"));
1741
1962
  const seen = /* @__PURE__ */ new Set();
1742
1963
  for (const env of summary.envSetup) {
1743
1964
  if (seen.has(env.envVar)) continue;
1744
1965
  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
- }
1966
+ console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
1750
1967
  console.log("");
1751
1968
  }
1752
1969
  }
1753
1970
  if (summary.pluginCommands.length > 0) {
1754
- console.log(chalk6.yellow(" Install plugins by running these in Claude Code:"));
1971
+ console.log(ui.section("Plugins"));
1755
1972
  for (const cmd of summary.pluginCommands) {
1756
- console.log(chalk6.bold(` ${cmd}`));
1973
+ console.log(ui.cmd(cmd));
1757
1974
  }
1975
+ console.log("");
1758
1976
  }
1759
- console.log(
1760
- chalk6.cyan("\n Ready! Run ") + chalk6.bold("claude") + chalk6.cyan(" to start.\n")
1761
- );
1977
+ console.log(ui.divider());
1978
+ console.log(ui.success("Ready! Run: $ claude"));
1979
+ console.log("");
1762
1980
  }
1763
1981
  });
1764
1982
 
1765
1983
  // src/commands/doctor.ts
1766
1984
  import { Command as Command7 } from "commander";
1767
- import chalk7 from "chalk";
1985
+ import chalk9 from "chalk";
1768
1986
  function runChecks(profile) {
1769
1987
  const checks = [];
1770
1988
  if (!profile.existingClaudeMd) {
@@ -1894,21 +2112,28 @@ function runChecks(profile) {
1894
2112
  var doctorCommand = new Command7("doctor").description(
1895
2113
  "Validate the current Claude Code environment against best practices"
1896
2114
  ).action(async () => {
2115
+ printFullBanner("Doctor");
1897
2116
  const targetDir = process.cwd();
1898
- console.log(chalk7.dim("\n Checking .claude/ environment...\n"));
2117
+ console.log(chalk9.dim(" Checking .claude/ environment...\n"));
1899
2118
  const profile = await scanProject(targetDir);
1900
2119
  if (!profile.hasClaudeDir) {
1901
- console.log(chalk7.red(" \u274C No .claude/ directory found.\n"));
2120
+ console.log(ui.error("No .claude/ directory found.\n"));
1902
2121
  console.log(
1903
- chalk7.dim(" Run ") + chalk7.bold("kairn describe") + chalk7.dim(" or ") + chalk7.bold("kairn optimize") + chalk7.dim(" to generate one.\n")
2122
+ chalk9.dim(" Run ") + chalk9.bold("kairn describe") + chalk9.dim(" or ") + chalk9.bold("kairn optimize") + chalk9.dim(" to generate one.\n")
1904
2123
  );
1905
2124
  process.exit(1);
1906
2125
  }
1907
2126
  const checks = runChecks(profile);
2127
+ console.log(ui.section("Health Check"));
2128
+ console.log("");
1908
2129
  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}`);
2130
+ if (check.status === "pass") {
2131
+ console.log(ui.success(`${check.name}: ${check.message}`));
2132
+ } else if (check.status === "warn") {
2133
+ console.log(ui.warn(`${check.name}: ${check.message}`));
2134
+ } else {
2135
+ console.log(ui.error(`${check.name}: ${check.message}`));
2136
+ }
1912
2137
  }
1913
2138
  const maxScore = checks.reduce((sum, c) => sum + c.weight, 0);
1914
2139
  const score = checks.reduce((sum, c) => {
@@ -1917,7 +2142,7 @@ var doctorCommand = new Command7("doctor").description(
1917
2142
  return sum;
1918
2143
  }, 0);
1919
2144
  const percentage = Math.round(score / maxScore * 100);
1920
- const scoreColor = percentage >= 80 ? chalk7.green : percentage >= 50 ? chalk7.yellow : chalk7.red;
2145
+ const scoreColor = percentage >= 80 ? chalk9.green : percentage >= 50 ? chalk9.yellow : chalk9.red;
1921
2146
  console.log(
1922
2147
  `
1923
2148
  Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
@@ -1925,24 +2150,24 @@ var doctorCommand = new Command7("doctor").description(
1925
2150
  );
1926
2151
  if (percentage < 80) {
1927
2152
  console.log(
1928
- chalk7.dim(" Run ") + chalk7.bold("kairn optimize") + chalk7.dim(" to fix issues.\n")
2153
+ chalk9.dim(" Run ") + chalk9.bold("kairn optimize") + chalk9.dim(" to fix issues.\n")
1929
2154
  );
1930
2155
  }
1931
2156
  });
1932
2157
 
1933
2158
  // src/commands/registry.ts
1934
2159
  import { Command as Command8 } from "commander";
1935
- import chalk8 from "chalk";
2160
+ import chalk10 from "chalk";
1936
2161
  import { input as input2, select as select2 } from "@inquirer/prompts";
1937
2162
  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
+ printCompactBanner();
1938
2164
  let all;
1939
2165
  let userTools;
1940
2166
  try {
1941
2167
  [all, userTools] = await Promise.all([loadRegistry(), loadUserRegistry()]);
1942
2168
  } catch (err) {
1943
2169
  const msg = err instanceof Error ? err.message : String(err);
1944
- console.log(chalk8.red(`
1945
- Failed to load registry: ${msg}
2170
+ console.log(ui.error(`Failed to load registry: ${msg}
1946
2171
  `));
1947
2172
  process.exit(1);
1948
2173
  }
@@ -1957,12 +2182,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1957
2182
  );
1958
2183
  }
1959
2184
  if (tools.length === 0) {
1960
- console.log(chalk8.dim("\n No tools found.\n"));
2185
+ console.log(chalk10.dim("\n No tools found.\n"));
1961
2186
  return;
1962
2187
  }
1963
2188
  const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
1964
2189
  const userCount = userIds.size;
1965
- console.log(chalk8.cyan("\n Registry Tools\n"));
2190
+ console.log(ui.section("Registry Tools"));
2191
+ console.log("");
1966
2192
  for (const tool of tools) {
1967
2193
  const isUser = userIds.has(tool.id);
1968
2194
  const meta = [
@@ -1970,13 +2196,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1970
2196
  `tier ${tool.tier}`,
1971
2197
  tool.auth
1972
2198
  ].join(", ");
1973
- console.log(chalk8.bold(` ${tool.id}`) + chalk8.dim(` (${meta})`));
1974
- console.log(chalk8.dim(` ${tool.description}`));
2199
+ console.log(` ${ui.accent(tool.id)}` + chalk10.dim(` (${meta})`));
2200
+ console.log(chalk10.dim(` ${tool.description}`));
1975
2201
  if (tool.best_for.length > 0) {
1976
- console.log(chalk8.dim(` Best for: ${tool.best_for.join(", ")}`));
2202
+ console.log(chalk10.dim(` Best for: ${tool.best_for.join(", ")}`));
1977
2203
  }
1978
2204
  if (isUser) {
1979
- console.log(chalk8.yellow(" [USER-DEFINED]"));
2205
+ console.log(chalk10.yellow(" [USER-DEFINED]"));
1980
2206
  }
1981
2207
  console.log("");
1982
2208
  }
@@ -1984,7 +2210,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
1984
2210
  const shownUser = tools.filter((t) => userIds.has(t.id)).length;
1985
2211
  const shownBundled = totalShown - shownUser;
1986
2212
  console.log(
1987
- chalk8.dim(
2213
+ chalk10.dim(
1988
2214
  ` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
1989
2215
  ) + "\n"
1990
2216
  );
@@ -2082,26 +2308,24 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2082
2308
  ...env_vars.length > 0 ? { env_vars } : {},
2083
2309
  ...signup_url ? { signup_url } : {}
2084
2310
  };
2085
- let userTools;
2311
+ let userToolsList;
2086
2312
  try {
2087
- userTools = await loadUserRegistry();
2313
+ userToolsList = await loadUserRegistry();
2088
2314
  } catch {
2089
- userTools = [];
2315
+ userToolsList = [];
2090
2316
  }
2091
- const existingIdx = userTools.findIndex((t) => t.id === id);
2317
+ const existingIdx = userToolsList.findIndex((t) => t.id === id);
2092
2318
  if (existingIdx >= 0) {
2093
- userTools[existingIdx] = tool;
2319
+ userToolsList[existingIdx] = tool;
2094
2320
  } else {
2095
- userTools.push(tool);
2321
+ userToolsList.push(tool);
2096
2322
  }
2097
- await saveUserRegistry(userTools);
2098
- console.log(chalk8.green(`
2099
- \u2713 Tool ${id} added to user registry
2323
+ await saveUserRegistry(userToolsList);
2324
+ console.log(ui.success(`Tool ${id} added to user registry
2100
2325
  `));
2101
2326
  } catch (err) {
2102
2327
  const msg = err instanceof Error ? err.message : String(err);
2103
- console.log(chalk8.red(`
2104
- Failed to add tool: ${msg}
2328
+ console.log(ui.error(`Failed to add tool: ${msg}
2105
2329
  `));
2106
2330
  process.exit(1);
2107
2331
  }
@@ -2110,19 +2334,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
2110
2334
 
2111
2335
  // src/commands/templates.ts
2112
2336
  import { Command as Command9 } from "commander";
2113
- import chalk9 from "chalk";
2337
+ import chalk11 from "chalk";
2114
2338
  import fs12 from "fs/promises";
2115
2339
  import path12 from "path";
2116
2340
  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
+ printCompactBanner();
2117
2342
  const templatesDir = getTemplatesDir();
2118
2343
  let files;
2119
2344
  try {
2120
2345
  files = await fs12.readdir(templatesDir);
2121
2346
  } catch {
2122
2347
  console.log(
2123
- chalk9.dim(
2124
- "\n No templates found. Templates will be installed with "
2125
- ) + chalk9.bold("kairn init") + chalk9.dim(
2348
+ chalk11.dim(
2349
+ " No templates found. Templates will be installed with "
2350
+ ) + chalk11.bold("kairn init") + chalk11.dim(
2126
2351
  " or you can add .json files to ~/.kairn/templates/\n"
2127
2352
  )
2128
2353
  );
@@ -2131,9 +2356,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
2131
2356
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2132
2357
  if (jsonFiles.length === 0) {
2133
2358
  console.log(
2134
- chalk9.dim(
2135
- "\n No templates found. Templates will be installed with "
2136
- ) + chalk9.bold("kairn init") + chalk9.dim(
2359
+ chalk11.dim(
2360
+ " No templates found. Templates will be installed with "
2361
+ ) + chalk11.bold("kairn init") + chalk11.dim(
2137
2362
  " or you can add .json files to ~/.kairn/templates/\n"
2138
2363
  )
2139
2364
  );
@@ -2161,30 +2386,27 @@ var templatesCommand = new Command9("templates").description("Browse available t
2161
2386
  }
2162
2387
  if (filtered.length === 0) {
2163
2388
  console.log(
2164
- chalk9.dim(`
2165
- No templates matched category "${options.category}".
2389
+ chalk11.dim(` No templates matched category "${options.category}".
2166
2390
  `)
2167
2391
  );
2168
2392
  return;
2169
2393
  }
2170
- console.log(chalk9.cyan("\n Available Templates\n"));
2394
+ console.log(ui.section("Templates"));
2395
+ console.log("");
2171
2396
  for (const spec of filtered) {
2172
2397
  const toolCount = spec.tools?.length ?? 0;
2173
2398
  const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
2174
2399
  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)));
2402
+ console.log(ui.kv("Description", spec.description));
2175
2403
  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
- )
2404
+ ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
2183
2405
  );
2184
2406
  console.log("");
2185
2407
  }
2186
2408
  console.log(
2187
- chalk9.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2409
+ chalk11.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
2188
2410
  `)
2189
2411
  );
2190
2412
  });
@@ -2193,7 +2415,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
2193
2415
  var program = new Command10();
2194
2416
  program.name("kairn").description(
2195
2417
  "Compile natural language intent into optimized Claude Code environments"
2196
- ).version("1.5.1");
2418
+ ).version("1.7.0").option("--no-color", "Disable colored output");
2197
2419
  program.addCommand(initCommand);
2198
2420
  program.addCommand(describeCommand);
2199
2421
  program.addCommand(optimizeCommand);
@@ -2203,5 +2425,8 @@ program.addCommand(updateRegistryCommand);
2203
2425
  program.addCommand(doctorCommand);
2204
2426
  program.addCommand(registryCommand);
2205
2427
  program.addCommand(templatesCommand);
2428
+ if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
2429
+ chalk12.level = 0;
2430
+ }
2206
2431
  program.parse();
2207
2432
  //# sourceMappingURL=cli.js.map