kairn-cli 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +443 -151
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
- package/src/registry/templates/api-service.json +9 -2
- package/src/registry/templates/content-writing.json +1 -1
- package/src/registry/templates/nextjs-fullstack.json +9 -2
- package/src/registry/templates/research-project.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import { Command as
|
|
3
|
-
import
|
|
2
|
+
import { Command as Command11 } from "commander";
|
|
3
|
+
import chalk14 from "chalk";
|
|
4
4
|
|
|
5
5
|
// src/commands/init.ts
|
|
6
6
|
import { Command } from "commander";
|
|
@@ -90,7 +90,7 @@ var ui = {
|
|
|
90
90
|
// Key-value pairs
|
|
91
91
|
kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
|
|
92
92
|
// File list
|
|
93
|
-
file: (
|
|
93
|
+
file: (path15) => chalk.dim(` ${path15}`),
|
|
94
94
|
// Tool display
|
|
95
95
|
tool: (name, reason) => ` ${warm("\u25CF")} ${chalk.bold(name)}
|
|
96
96
|
${chalk.dim(reason)}`,
|
|
@@ -339,7 +339,7 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
|
|
|
339
339
|
// src/commands/describe.ts
|
|
340
340
|
import { Command as Command2 } from "commander";
|
|
341
341
|
import { input, confirm } from "@inquirer/prompts";
|
|
342
|
-
import
|
|
342
|
+
import chalk5 from "chalk";
|
|
343
343
|
import ora from "ora";
|
|
344
344
|
|
|
345
345
|
// src/compiler/compile.ts
|
|
@@ -362,12 +362,12 @@ You must output a JSON object matching the EnvironmentSpec schema.
|
|
|
362
362
|
|
|
363
363
|
- **Minimalism over completeness.** Fewer, well-chosen tools beat many generic ones. Each MCP server costs 500-2000 context tokens.
|
|
364
364
|
- **Workflow-specific, not generic.** Every instruction, command, and rule must relate to the user's actual workflow.
|
|
365
|
-
- **Concise CLAUDE.md.** Under
|
|
365
|
+
- **Concise CLAUDE.md.** Under 120 lines. No generic text like "be helpful." Include build/test commands, reference docs/ and skills/.
|
|
366
366
|
- **Security by default.** Always include deny rules for destructive commands and secret file access.
|
|
367
367
|
|
|
368
368
|
## CLAUDE.md Template (mandatory structure)
|
|
369
369
|
|
|
370
|
-
The \`claude_md\` field MUST follow this exact structure (max
|
|
370
|
+
The \`claude_md\` field MUST follow this exact structure (max 120 lines):
|
|
371
371
|
|
|
372
372
|
\`\`\`
|
|
373
373
|
# {Project Name}
|
|
@@ -392,6 +392,30 @@ The \`claude_md\` field MUST follow this exact structure (max 100 lines):
|
|
|
392
392
|
|
|
393
393
|
## Output
|
|
394
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
|
|
395
419
|
\`\`\`
|
|
396
420
|
|
|
397
421
|
Do not add generic filler. Every line must be specific to the user's workflow.
|
|
@@ -410,6 +434,10 @@ Do not add generic filler. Every line must be specific to the user's workflow.
|
|
|
410
434
|
10. A \`/project:status\` command for code projects (uses ! for live git/test output)
|
|
411
435
|
11. A \`/project:fix\` command for code projects (uses $ARGUMENTS for issue number)
|
|
412
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)
|
|
413
441
|
|
|
414
442
|
## Shell-Integrated Commands
|
|
415
443
|
|
|
@@ -530,7 +558,7 @@ Merge this into the settings hooks alongside the PreToolUse and PostToolUse hook
|
|
|
530
558
|
## Context Budget (STRICT)
|
|
531
559
|
|
|
532
560
|
- MCP servers: maximum 6. Prefer fewer.
|
|
533
|
-
- CLAUDE.md: maximum
|
|
561
|
+
- CLAUDE.md: maximum 120 lines.
|
|
534
562
|
- Rules: maximum 5 files, each under 20 lines.
|
|
535
563
|
- Skills: maximum 3. Only include directly relevant ones.
|
|
536
564
|
- Agents: maximum 3. QA pipeline + one specialist.
|
|
@@ -558,6 +586,10 @@ Each MCP server costs 500-2000 tokens of context window.
|
|
|
558
586
|
- \`@qa-orchestrator\` (sonnet) \u2014 delegates to linter and e2e-tester, compiles QA report
|
|
559
587
|
- \`@linter\` (haiku) \u2014 runs formatters, linters, security scanners
|
|
560
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)
|
|
561
593
|
|
|
562
594
|
## For Research Projects, Additionally Include
|
|
563
595
|
|
|
@@ -565,6 +597,7 @@ Each MCP server costs 500-2000 tokens of context window.
|
|
|
565
597
|
- \`/project:summarize\` command (summarize findings)
|
|
566
598
|
- A research-synthesis skill
|
|
567
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
|
|
568
601
|
|
|
569
602
|
## For Content/Writing Projects, Additionally Include
|
|
570
603
|
|
|
@@ -593,7 +626,7 @@ Return ONLY valid JSON matching this structure:
|
|
|
593
626
|
{ "tool_id": "id-from-registry", "reason": "why this tool fits" }
|
|
594
627
|
],
|
|
595
628
|
"harness": {
|
|
596
|
-
"claude_md": "The full CLAUDE.md content (under
|
|
629
|
+
"claude_md": "The full CLAUDE.md content (under 120 lines)",
|
|
597
630
|
"settings": {
|
|
598
631
|
"permissions": {
|
|
599
632
|
"allow": ["Bash(npm run *)", "Read", "Write", "Edit"],
|
|
@@ -608,7 +641,11 @@ Return ONLY valid JSON matching this structure:
|
|
|
608
641
|
"tasks": "markdown content for /project:tasks",
|
|
609
642
|
"status": "Show project status:\\n\\n!git status --short\\n\\n!git log --oneline -5\\n\\nRead TODO.md and summarize progress.",
|
|
610
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",
|
|
611
|
-
"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."
|
|
612
649
|
},
|
|
613
650
|
"rules": {
|
|
614
651
|
"continuity": "markdown content for continuity rule",
|
|
@@ -882,20 +919,48 @@ async function generateClarifications(intent, onProgress) {
|
|
|
882
919
|
// src/adapter/claude-code.ts
|
|
883
920
|
import fs5 from "fs/promises";
|
|
884
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
|
+
var ENV_LOADER_HOOK = {
|
|
930
|
+
matcher: "",
|
|
931
|
+
hooks: [{
|
|
932
|
+
type: "command",
|
|
933
|
+
command: 'if [ -f .env ] && [ -n "$CLAUDE_ENV_FILE" ]; then grep -v "^#" .env | grep -v "^$" | grep "=" >> "$CLAUDE_ENV_FILE"; fi'
|
|
934
|
+
}]
|
|
935
|
+
};
|
|
936
|
+
function resolveSettings(spec, options) {
|
|
937
|
+
const settings = spec.harness.settings;
|
|
938
|
+
const base = settings && Object.keys(settings).length > 0 ? { ...settings } : {};
|
|
939
|
+
if (!("statusLine" in base) && isCodeProject(spec)) {
|
|
940
|
+
base.statusLine = STATUS_LINE;
|
|
941
|
+
}
|
|
942
|
+
if (options?.hasEnvVars) {
|
|
943
|
+
const hooks = base.hooks ?? {};
|
|
944
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
945
|
+
sessionStart.push(ENV_LOADER_HOOK);
|
|
946
|
+
hooks.SessionStart = sessionStart;
|
|
947
|
+
base.hooks = hooks;
|
|
948
|
+
}
|
|
949
|
+
if (Object.keys(base).length === 0) return null;
|
|
950
|
+
return base;
|
|
951
|
+
}
|
|
885
952
|
async function writeFile(filePath, content) {
|
|
886
953
|
await fs5.mkdir(path5.dirname(filePath), { recursive: true });
|
|
887
954
|
await fs5.writeFile(filePath, content, "utf-8");
|
|
888
955
|
}
|
|
889
|
-
function buildFileMap(spec) {
|
|
956
|
+
function buildFileMap(spec, options) {
|
|
890
957
|
const files = /* @__PURE__ */ new Map();
|
|
891
958
|
if (spec.harness.claude_md) {
|
|
892
959
|
files.set(".claude/CLAUDE.md", spec.harness.claude_md);
|
|
893
960
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
JSON.stringify(spec.harness.settings, null, 2)
|
|
898
|
-
);
|
|
961
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
962
|
+
if (resolvedSettings) {
|
|
963
|
+
files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
|
|
899
964
|
}
|
|
900
965
|
if (spec.harness.mcp_config && Object.keys(spec.harness.mcp_config).length > 0) {
|
|
901
966
|
files.set(
|
|
@@ -930,7 +995,7 @@ function buildFileMap(spec) {
|
|
|
930
995
|
}
|
|
931
996
|
return files;
|
|
932
997
|
}
|
|
933
|
-
async function writeEnvironment(spec, targetDir) {
|
|
998
|
+
async function writeEnvironment(spec, targetDir, options) {
|
|
934
999
|
const claudeDir = path5.join(targetDir, ".claude");
|
|
935
1000
|
const written = [];
|
|
936
1001
|
if (spec.harness.claude_md) {
|
|
@@ -938,9 +1003,10 @@ async function writeEnvironment(spec, targetDir) {
|
|
|
938
1003
|
await writeFile(p, spec.harness.claude_md);
|
|
939
1004
|
written.push(".claude/CLAUDE.md");
|
|
940
1005
|
}
|
|
941
|
-
|
|
1006
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
1007
|
+
if (resolvedSettings) {
|
|
942
1008
|
const p = path5.join(claudeDir, "settings.json");
|
|
943
|
-
await writeFile(p, JSON.stringify(
|
|
1009
|
+
await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
|
|
944
1010
|
written.push(".claude/settings.json");
|
|
945
1011
|
}
|
|
946
1012
|
if (spec.harness.mcp_config && Object.keys(spec.harness.mcp_config).length > 0) {
|
|
@@ -1140,6 +1206,114 @@ async function writeHermesEnvironment(spec, registry) {
|
|
|
1140
1206
|
return written;
|
|
1141
1207
|
}
|
|
1142
1208
|
|
|
1209
|
+
// src/secrets.ts
|
|
1210
|
+
import { password as password2 } from "@inquirer/prompts";
|
|
1211
|
+
import chalk4 from "chalk";
|
|
1212
|
+
import fs7 from "fs/promises";
|
|
1213
|
+
import path7 from "path";
|
|
1214
|
+
async function collectAndWriteKeys(envSetup, targetDir) {
|
|
1215
|
+
console.log(ui.section("API Keys"));
|
|
1216
|
+
console.log(
|
|
1217
|
+
chalk4.dim(" Some tools need API keys. Enter them now or press Enter to skip.\n")
|
|
1218
|
+
);
|
|
1219
|
+
const envEntries = [
|
|
1220
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1221
|
+
"# Do NOT commit this file to git",
|
|
1222
|
+
""
|
|
1223
|
+
];
|
|
1224
|
+
let keysEntered = 0;
|
|
1225
|
+
let keysSkipped = 0;
|
|
1226
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1227
|
+
for (const env of envSetup) {
|
|
1228
|
+
if (seen.has(env.envVar)) continue;
|
|
1229
|
+
seen.add(env.envVar);
|
|
1230
|
+
console.log(chalk4.bold(` ${env.envVar}`) + chalk4.dim(` (${env.toolName})`));
|
|
1231
|
+
if (env.signupUrl) {
|
|
1232
|
+
console.log(chalk4.dim(` Get one at: ${env.signupUrl}`));
|
|
1233
|
+
}
|
|
1234
|
+
const value = await password2({
|
|
1235
|
+
message: env.envVar,
|
|
1236
|
+
mask: "\u2022"
|
|
1237
|
+
});
|
|
1238
|
+
if (value && value.trim()) {
|
|
1239
|
+
envEntries.push(`${env.envVar}=${value.trim()}`);
|
|
1240
|
+
console.log(chalk4.green(" \u2713 saved\n"));
|
|
1241
|
+
keysEntered++;
|
|
1242
|
+
} else {
|
|
1243
|
+
envEntries.push(`${env.envVar}=`);
|
|
1244
|
+
console.log(chalk4.dim(" (skipped)\n"));
|
|
1245
|
+
keysSkipped++;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1249
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1250
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1251
|
+
console.log(chalk4.green(` \u2713 ${keysEntered} key(s) saved to .env (gitignored)`));
|
|
1252
|
+
if (keysSkipped > 0) {
|
|
1253
|
+
console.log(chalk4.dim(" Skipped keys can be added later: kairn keys"));
|
|
1254
|
+
}
|
|
1255
|
+
return { keysEntered, keysSkipped, envPath };
|
|
1256
|
+
}
|
|
1257
|
+
async function writeEmptyEnvFile(envSetup, targetDir) {
|
|
1258
|
+
const envEntries = [
|
|
1259
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1260
|
+
"# Do NOT commit this file to git",
|
|
1261
|
+
""
|
|
1262
|
+
];
|
|
1263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1264
|
+
for (const env of envSetup) {
|
|
1265
|
+
if (seen.has(env.envVar)) continue;
|
|
1266
|
+
seen.add(env.envVar);
|
|
1267
|
+
envEntries.push(`${env.envVar}=`);
|
|
1268
|
+
}
|
|
1269
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1270
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1271
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1272
|
+
}
|
|
1273
|
+
async function ensureGitignoreEntry(targetDir, entry) {
|
|
1274
|
+
const gitignorePath = path7.join(targetDir, ".gitignore");
|
|
1275
|
+
let gitignore = "";
|
|
1276
|
+
try {
|
|
1277
|
+
gitignore = await fs7.readFile(gitignorePath, "utf-8");
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
if (!gitignore.split("\n").some((line) => line.trim() === entry)) {
|
|
1281
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
1282
|
+
await fs7.writeFile(gitignorePath, gitignore + separator + entry + "\n", "utf-8");
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
async function readEnvFile(targetDir) {
|
|
1286
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1287
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1288
|
+
try {
|
|
1289
|
+
const content = await fs7.readFile(envPath, "utf-8");
|
|
1290
|
+
for (const line of content.split("\n")) {
|
|
1291
|
+
const trimmed = line.trim();
|
|
1292
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1293
|
+
const eqIndex = trimmed.indexOf("=");
|
|
1294
|
+
if (eqIndex === -1) continue;
|
|
1295
|
+
const key = trimmed.slice(0, eqIndex);
|
|
1296
|
+
const value = trimmed.slice(eqIndex + 1);
|
|
1297
|
+
entries.set(key, value);
|
|
1298
|
+
}
|
|
1299
|
+
} catch {
|
|
1300
|
+
}
|
|
1301
|
+
return entries;
|
|
1302
|
+
}
|
|
1303
|
+
async function detectRequiredEnvVars(targetDir) {
|
|
1304
|
+
const mcpPath = path7.join(targetDir, ".mcp.json");
|
|
1305
|
+
const envVars = /* @__PURE__ */ new Set();
|
|
1306
|
+
try {
|
|
1307
|
+
const content = await fs7.readFile(mcpPath, "utf-8");
|
|
1308
|
+
const matches = content.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
|
|
1309
|
+
for (const match of matches) {
|
|
1310
|
+
envVars.add(match[1]);
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1313
|
+
}
|
|
1314
|
+
return [...envVars];
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1143
1317
|
// src/commands/describe.ts
|
|
1144
1318
|
var describeCommand = new Command2("describe").description("Describe your workflow and generate a Claude Code environment").argument("[intent]", "What you want your agent to do").option("-y, --yes", "Skip confirmation prompt").option("-q, --quick", "Skip clarification questions").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (intentArg, options) => {
|
|
1145
1319
|
printFullBanner("The Agent Environment Compiler");
|
|
@@ -1148,7 +1322,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1148
1322
|
console.log(
|
|
1149
1323
|
ui.errorBox(
|
|
1150
1324
|
"No configuration found",
|
|
1151
|
-
`Run ${
|
|
1325
|
+
`Run ${chalk5.bold("kairn init")} to set up your API key.`
|
|
1152
1326
|
)
|
|
1153
1327
|
);
|
|
1154
1328
|
process.exit(1);
|
|
@@ -1157,14 +1331,14 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1157
1331
|
message: "What do you want your agent to do?"
|
|
1158
1332
|
});
|
|
1159
1333
|
if (!intentRaw.trim()) {
|
|
1160
|
-
console.log(
|
|
1334
|
+
console.log(chalk5.red("\n No description provided. Aborting.\n"));
|
|
1161
1335
|
process.exit(1);
|
|
1162
1336
|
}
|
|
1163
1337
|
let finalIntent = intentRaw;
|
|
1164
1338
|
if (!options.quick) {
|
|
1165
1339
|
console.log(ui.section("Clarification"));
|
|
1166
|
-
console.log(
|
|
1167
|
-
console.log(
|
|
1340
|
+
console.log(chalk5.dim(" Let me understand your project better."));
|
|
1341
|
+
console.log(chalk5.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
|
|
1168
1342
|
let clarifications = [];
|
|
1169
1343
|
try {
|
|
1170
1344
|
clarifications = await generateClarifications(intentRaw);
|
|
@@ -1197,7 +1371,7 @@ ${clarificationLines}`;
|
|
|
1197
1371
|
} catch (err) {
|
|
1198
1372
|
spinner.fail("Compilation failed");
|
|
1199
1373
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1200
|
-
console.log(
|
|
1374
|
+
console.log(chalk5.red(`
|
|
1201
1375
|
${msg}
|
|
1202
1376
|
`));
|
|
1203
1377
|
process.exit(1);
|
|
@@ -1227,7 +1401,7 @@ ${clarificationLines}`;
|
|
|
1227
1401
|
default: true
|
|
1228
1402
|
});
|
|
1229
1403
|
if (!proceed) {
|
|
1230
|
-
console.log(
|
|
1404
|
+
console.log(chalk5.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
|
|
1231
1405
|
return;
|
|
1232
1406
|
}
|
|
1233
1407
|
const targetDir = process.cwd();
|
|
@@ -1236,25 +1410,24 @@ ${clarificationLines}`;
|
|
|
1236
1410
|
await writeHermesEnvironment(spec, registry);
|
|
1237
1411
|
console.log("\n" + ui.success("Environment written for Hermes"));
|
|
1238
1412
|
console.log(
|
|
1239
|
-
|
|
1413
|
+
chalk5.cyan("\n Ready! Run ") + chalk5.bold("hermes") + chalk5.cyan(" to start.\n")
|
|
1240
1414
|
);
|
|
1241
1415
|
} else {
|
|
1242
|
-
const
|
|
1416
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1417
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1243
1418
|
console.log(ui.section("Files Written"));
|
|
1244
1419
|
console.log("");
|
|
1245
1420
|
for (const file of written) {
|
|
1246
1421
|
console.log(ui.file(file));
|
|
1247
1422
|
}
|
|
1248
|
-
if (
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
seen.add(env.envVar);
|
|
1255
|
-
console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
|
|
1256
|
-
console.log("");
|
|
1423
|
+
if (hasEnvVars) {
|
|
1424
|
+
if (options.quick) {
|
|
1425
|
+
await writeEmptyEnvFile(summary.envSetup, targetDir);
|
|
1426
|
+
console.log(ui.success("Empty .env written (gitignored) \u2014 fill in keys later: kairn keys"));
|
|
1427
|
+
} else {
|
|
1428
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
1257
1429
|
}
|
|
1430
|
+
console.log("");
|
|
1258
1431
|
}
|
|
1259
1432
|
if (summary.pluginCommands.length > 0) {
|
|
1260
1433
|
console.log(ui.section("Plugins"));
|
|
@@ -1272,28 +1445,28 @@ ${clarificationLines}`;
|
|
|
1272
1445
|
|
|
1273
1446
|
// src/commands/list.ts
|
|
1274
1447
|
import { Command as Command3 } from "commander";
|
|
1275
|
-
import
|
|
1276
|
-
import
|
|
1277
|
-
import
|
|
1448
|
+
import chalk6 from "chalk";
|
|
1449
|
+
import fs8 from "fs/promises";
|
|
1450
|
+
import path8 from "path";
|
|
1278
1451
|
var listCommand = new Command3("list").description("Show saved environments").action(async () => {
|
|
1279
1452
|
printCompactBanner();
|
|
1280
1453
|
const envsDir = getEnvsDir();
|
|
1281
1454
|
let files;
|
|
1282
1455
|
try {
|
|
1283
|
-
files = await
|
|
1456
|
+
files = await fs8.readdir(envsDir);
|
|
1284
1457
|
} catch {
|
|
1285
|
-
console.log(
|
|
1458
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1286
1459
|
return;
|
|
1287
1460
|
}
|
|
1288
1461
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1289
1462
|
if (jsonFiles.length === 0) {
|
|
1290
|
-
console.log(
|
|
1463
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1291
1464
|
return;
|
|
1292
1465
|
}
|
|
1293
1466
|
let first = true;
|
|
1294
1467
|
for (const file of jsonFiles) {
|
|
1295
1468
|
try {
|
|
1296
|
-
const data = await
|
|
1469
|
+
const data = await fs8.readFile(path8.join(envsDir, file), "utf-8");
|
|
1297
1470
|
const spec = JSON.parse(data);
|
|
1298
1471
|
const date = new Date(spec.created_at).toLocaleDateString();
|
|
1299
1472
|
const toolCount = spec.tools?.length ?? 0;
|
|
@@ -1301,10 +1474,10 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1301
1474
|
console.log(ui.divider());
|
|
1302
1475
|
}
|
|
1303
1476
|
first = false;
|
|
1304
|
-
console.log(ui.kv("Name",
|
|
1477
|
+
console.log(ui.kv("Name", chalk6.bold(spec.name)));
|
|
1305
1478
|
console.log(ui.kv("Description", spec.description));
|
|
1306
1479
|
console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
|
|
1307
|
-
console.log(ui.kv("ID",
|
|
1480
|
+
console.log(ui.kv("ID", chalk6.dim(spec.id)));
|
|
1308
1481
|
console.log("");
|
|
1309
1482
|
} catch {
|
|
1310
1483
|
}
|
|
@@ -1313,9 +1486,9 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1313
1486
|
|
|
1314
1487
|
// src/commands/activate.ts
|
|
1315
1488
|
import { Command as Command4 } from "commander";
|
|
1316
|
-
import
|
|
1317
|
-
import
|
|
1318
|
-
import
|
|
1489
|
+
import chalk7 from "chalk";
|
|
1490
|
+
import fs9 from "fs/promises";
|
|
1491
|
+
import path9 from "path";
|
|
1319
1492
|
var activateCommand = new Command4("activate").description("Re-deploy a saved environment to the current directory").argument("<env_id>", "Environment ID (from kairn list)").action(async (envId) => {
|
|
1320
1493
|
printCompactBanner();
|
|
1321
1494
|
const envsDir = getEnvsDir();
|
|
@@ -1325,7 +1498,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1325
1498
|
let fromTemplate = false;
|
|
1326
1499
|
let envFiles = [];
|
|
1327
1500
|
try {
|
|
1328
|
-
envFiles = await
|
|
1501
|
+
envFiles = await fs9.readdir(envsDir);
|
|
1329
1502
|
} catch {
|
|
1330
1503
|
}
|
|
1331
1504
|
match = envFiles.find(
|
|
@@ -1336,7 +1509,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1336
1509
|
} else {
|
|
1337
1510
|
let templateFiles = [];
|
|
1338
1511
|
try {
|
|
1339
|
-
templateFiles = await
|
|
1512
|
+
templateFiles = await fs9.readdir(templatesDir);
|
|
1340
1513
|
} catch {
|
|
1341
1514
|
}
|
|
1342
1515
|
match = templateFiles.find(
|
|
@@ -1347,16 +1520,16 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1347
1520
|
fromTemplate = true;
|
|
1348
1521
|
} else {
|
|
1349
1522
|
console.log(ui.error(`Environment "${envId}" not found.`));
|
|
1350
|
-
console.log(
|
|
1351
|
-
console.log(
|
|
1523
|
+
console.log(chalk7.dim(" Run kairn list to see saved environments."));
|
|
1524
|
+
console.log(chalk7.dim(" Run kairn templates to see available templates.\n"));
|
|
1352
1525
|
process.exit(1);
|
|
1353
1526
|
}
|
|
1354
1527
|
}
|
|
1355
|
-
const data = await
|
|
1528
|
+
const data = await fs9.readFile(path9.join(sourceDir, match), "utf-8");
|
|
1356
1529
|
const spec = JSON.parse(data);
|
|
1357
|
-
const label = fromTemplate ?
|
|
1358
|
-
console.log(
|
|
1359
|
-
console.log(
|
|
1530
|
+
const label = fromTemplate ? chalk7.dim(" (template)") : "";
|
|
1531
|
+
console.log(chalk7.cyan(` Activating: ${spec.name}`) + label);
|
|
1532
|
+
console.log(chalk7.dim(` ${spec.description}
|
|
1360
1533
|
`));
|
|
1361
1534
|
const targetDir = process.cwd();
|
|
1362
1535
|
const written = await writeEnvironment(spec, targetDir);
|
|
@@ -1369,22 +1542,22 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1369
1542
|
|
|
1370
1543
|
// src/commands/update-registry.ts
|
|
1371
1544
|
import { Command as Command5 } from "commander";
|
|
1372
|
-
import
|
|
1373
|
-
import
|
|
1374
|
-
import
|
|
1545
|
+
import chalk8 from "chalk";
|
|
1546
|
+
import fs10 from "fs/promises";
|
|
1547
|
+
import path10 from "path";
|
|
1375
1548
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1376
1549
|
var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
|
|
1377
1550
|
async function getLocalRegistryPath() {
|
|
1378
1551
|
const __filename3 = fileURLToPath3(import.meta.url);
|
|
1379
|
-
const __dirname3 =
|
|
1552
|
+
const __dirname3 = path10.dirname(__filename3);
|
|
1380
1553
|
const candidates = [
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1554
|
+
path10.resolve(__dirname3, "../registry/tools.json"),
|
|
1555
|
+
path10.resolve(__dirname3, "../src/registry/tools.json"),
|
|
1556
|
+
path10.resolve(__dirname3, "../../src/registry/tools.json")
|
|
1384
1557
|
];
|
|
1385
1558
|
for (const candidate of candidates) {
|
|
1386
1559
|
try {
|
|
1387
|
-
await
|
|
1560
|
+
await fs10.access(candidate);
|
|
1388
1561
|
return candidate;
|
|
1389
1562
|
} catch {
|
|
1390
1563
|
continue;
|
|
@@ -1395,15 +1568,15 @@ async function getLocalRegistryPath() {
|
|
|
1395
1568
|
var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
|
|
1396
1569
|
printCompactBanner();
|
|
1397
1570
|
const url = options.url || REGISTRY_URL;
|
|
1398
|
-
console.log(
|
|
1571
|
+
console.log(chalk8.dim(` Fetching registry from ${url}...`));
|
|
1399
1572
|
try {
|
|
1400
1573
|
const response = await fetch(url);
|
|
1401
1574
|
if (!response.ok) {
|
|
1402
1575
|
console.log(
|
|
1403
1576
|
ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
|
|
1404
1577
|
);
|
|
1405
|
-
console.log(
|
|
1406
|
-
console.log(
|
|
1578
|
+
console.log(chalk8.dim(" The remote registry may not be available yet."));
|
|
1579
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1407
1580
|
return;
|
|
1408
1581
|
}
|
|
1409
1582
|
const text = await response.text();
|
|
@@ -1421,35 +1594,35 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
|
|
|
1421
1594
|
const registryPath = await getLocalRegistryPath();
|
|
1422
1595
|
const backupPath = registryPath + ".bak";
|
|
1423
1596
|
try {
|
|
1424
|
-
await
|
|
1597
|
+
await fs10.copyFile(registryPath, backupPath);
|
|
1425
1598
|
} catch {
|
|
1426
1599
|
}
|
|
1427
|
-
await
|
|
1600
|
+
await fs10.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
|
|
1428
1601
|
console.log(ui.success(`Registry updated: ${tools.length} tools`));
|
|
1429
|
-
console.log(
|
|
1430
|
-
console.log(
|
|
1602
|
+
console.log(chalk8.dim(` Saved to: ${registryPath}`));
|
|
1603
|
+
console.log(chalk8.dim(` Backup: ${backupPath}
|
|
1431
1604
|
`));
|
|
1432
1605
|
} catch (err) {
|
|
1433
1606
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1434
1607
|
console.log(ui.error(`Network error: ${msg}`));
|
|
1435
|
-
console.log(
|
|
1608
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1436
1609
|
}
|
|
1437
1610
|
});
|
|
1438
1611
|
|
|
1439
1612
|
// src/commands/optimize.ts
|
|
1440
1613
|
import { Command as Command6 } from "commander";
|
|
1441
1614
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1442
|
-
import
|
|
1615
|
+
import chalk9 from "chalk";
|
|
1443
1616
|
import ora2 from "ora";
|
|
1444
|
-
import
|
|
1445
|
-
import
|
|
1617
|
+
import fs12 from "fs/promises";
|
|
1618
|
+
import path12 from "path";
|
|
1446
1619
|
|
|
1447
1620
|
// src/scanner/scan.ts
|
|
1448
|
-
import
|
|
1449
|
-
import
|
|
1621
|
+
import fs11 from "fs/promises";
|
|
1622
|
+
import path11 from "path";
|
|
1450
1623
|
async function fileExists(p) {
|
|
1451
1624
|
try {
|
|
1452
|
-
await
|
|
1625
|
+
await fs11.access(p);
|
|
1453
1626
|
return true;
|
|
1454
1627
|
} catch {
|
|
1455
1628
|
return false;
|
|
@@ -1457,7 +1630,7 @@ async function fileExists(p) {
|
|
|
1457
1630
|
}
|
|
1458
1631
|
async function readJsonSafe(p) {
|
|
1459
1632
|
try {
|
|
1460
|
-
const data = await
|
|
1633
|
+
const data = await fs11.readFile(p, "utf-8");
|
|
1461
1634
|
return JSON.parse(data);
|
|
1462
1635
|
} catch {
|
|
1463
1636
|
return null;
|
|
@@ -1465,14 +1638,14 @@ async function readJsonSafe(p) {
|
|
|
1465
1638
|
}
|
|
1466
1639
|
async function readFileSafe(p) {
|
|
1467
1640
|
try {
|
|
1468
|
-
return await
|
|
1641
|
+
return await fs11.readFile(p, "utf-8");
|
|
1469
1642
|
} catch {
|
|
1470
1643
|
return null;
|
|
1471
1644
|
}
|
|
1472
1645
|
}
|
|
1473
1646
|
async function listDirSafe(p) {
|
|
1474
1647
|
try {
|
|
1475
|
-
const entries = await
|
|
1648
|
+
const entries = await fs11.readdir(p);
|
|
1476
1649
|
return entries.filter((e) => !e.startsWith("."));
|
|
1477
1650
|
} catch {
|
|
1478
1651
|
return [];
|
|
@@ -1524,7 +1697,7 @@ function extractEnvKeys(content) {
|
|
|
1524
1697
|
return keys;
|
|
1525
1698
|
}
|
|
1526
1699
|
async function scanProject(dir) {
|
|
1527
|
-
const pkg = await readJsonSafe(
|
|
1700
|
+
const pkg = await readJsonSafe(path11.join(dir, "package.json"));
|
|
1528
1701
|
const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
|
|
1529
1702
|
const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
|
|
1530
1703
|
const allDeps = [...deps, ...devDeps];
|
|
@@ -1552,19 +1725,19 @@ async function scanProject(dir) {
|
|
|
1552
1725
|
const framework = detectFramework(allDeps);
|
|
1553
1726
|
const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
|
|
1554
1727
|
const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
|
|
1555
|
-
const hasTests = testCommand !== null || await fileExists(
|
|
1728
|
+
const hasTests = testCommand !== null || await fileExists(path11.join(dir, "tests")) || await fileExists(path11.join(dir, "__tests__")) || await fileExists(path11.join(dir, "test"));
|
|
1556
1729
|
const buildCommand = scripts.build || null;
|
|
1557
1730
|
const lintCommand = scripts.lint || null;
|
|
1558
|
-
const hasSrc = await fileExists(
|
|
1559
|
-
const hasDocker = await fileExists(
|
|
1560
|
-
const hasCi = await fileExists(
|
|
1561
|
-
const hasEnvFile = await fileExists(
|
|
1731
|
+
const hasSrc = await fileExists(path11.join(dir, "src"));
|
|
1732
|
+
const hasDocker = await fileExists(path11.join(dir, "docker-compose.yml")) || await fileExists(path11.join(dir, "Dockerfile"));
|
|
1733
|
+
const hasCi = await fileExists(path11.join(dir, ".github/workflows"));
|
|
1734
|
+
const hasEnvFile = await fileExists(path11.join(dir, ".env")) || await fileExists(path11.join(dir, ".env.example"));
|
|
1562
1735
|
let envKeys = [];
|
|
1563
|
-
const envExample = await readFileSafe(
|
|
1736
|
+
const envExample = await readFileSafe(path11.join(dir, ".env.example"));
|
|
1564
1737
|
if (envExample) {
|
|
1565
1738
|
envKeys = extractEnvKeys(envExample);
|
|
1566
1739
|
}
|
|
1567
|
-
const claudeDir =
|
|
1740
|
+
const claudeDir = path11.join(dir, ".claude");
|
|
1568
1741
|
const hasClaudeDir = await fileExists(claudeDir);
|
|
1569
1742
|
let existingClaudeMd = null;
|
|
1570
1743
|
let existingSettings = null;
|
|
@@ -1576,21 +1749,21 @@ async function scanProject(dir) {
|
|
|
1576
1749
|
let mcpServerCount = 0;
|
|
1577
1750
|
let claudeMdLineCount = 0;
|
|
1578
1751
|
if (hasClaudeDir) {
|
|
1579
|
-
existingClaudeMd = await readFileSafe(
|
|
1752
|
+
existingClaudeMd = await readFileSafe(path11.join(claudeDir, "CLAUDE.md"));
|
|
1580
1753
|
if (existingClaudeMd) {
|
|
1581
1754
|
claudeMdLineCount = existingClaudeMd.split("\n").length;
|
|
1582
1755
|
}
|
|
1583
|
-
existingSettings = await readJsonSafe(
|
|
1584
|
-
existingMcpConfig = await readJsonSafe(
|
|
1756
|
+
existingSettings = await readJsonSafe(path11.join(claudeDir, "settings.json"));
|
|
1757
|
+
existingMcpConfig = await readJsonSafe(path11.join(dir, ".mcp.json"));
|
|
1585
1758
|
if (existingMcpConfig?.mcpServers) {
|
|
1586
1759
|
mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
|
|
1587
1760
|
}
|
|
1588
|
-
existingCommands = (await listDirSafe(
|
|
1589
|
-
existingRules = (await listDirSafe(
|
|
1590
|
-
existingSkills = await listDirSafe(
|
|
1591
|
-
existingAgents = (await listDirSafe(
|
|
1761
|
+
existingCommands = (await listDirSafe(path11.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1762
|
+
existingRules = (await listDirSafe(path11.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1763
|
+
existingSkills = await listDirSafe(path11.join(claudeDir, "skills"));
|
|
1764
|
+
existingAgents = (await listDirSafe(path11.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1592
1765
|
}
|
|
1593
|
-
const name = pkg?.name ||
|
|
1766
|
+
const name = pkg?.name || path11.basename(dir);
|
|
1594
1767
|
const description = pkg?.description || "";
|
|
1595
1768
|
return {
|
|
1596
1769
|
name,
|
|
@@ -1635,31 +1808,31 @@ function simpleDiff(oldContent, newContent) {
|
|
|
1635
1808
|
const oldLine = oldLines[i];
|
|
1636
1809
|
const newLine = newLines[i];
|
|
1637
1810
|
if (oldLine === void 0) {
|
|
1638
|
-
output.push(
|
|
1811
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1639
1812
|
} else if (newLine === void 0) {
|
|
1640
|
-
output.push(
|
|
1813
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
1641
1814
|
} else if (oldLine !== newLine) {
|
|
1642
|
-
output.push(
|
|
1643
|
-
output.push(
|
|
1815
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
1816
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1644
1817
|
}
|
|
1645
1818
|
}
|
|
1646
1819
|
return output;
|
|
1647
1820
|
}
|
|
1648
|
-
async function generateDiff(spec, targetDir) {
|
|
1649
|
-
const fileMap = buildFileMap(spec);
|
|
1821
|
+
async function generateDiff(spec, targetDir, options) {
|
|
1822
|
+
const fileMap = buildFileMap(spec, options);
|
|
1650
1823
|
const results = [];
|
|
1651
1824
|
for (const [relativePath, newContent] of fileMap) {
|
|
1652
|
-
const absolutePath =
|
|
1825
|
+
const absolutePath = path12.join(targetDir, relativePath);
|
|
1653
1826
|
let oldContent = null;
|
|
1654
1827
|
try {
|
|
1655
|
-
oldContent = await
|
|
1828
|
+
oldContent = await fs12.readFile(absolutePath, "utf-8");
|
|
1656
1829
|
} catch {
|
|
1657
1830
|
}
|
|
1658
1831
|
if (oldContent === null) {
|
|
1659
1832
|
results.push({
|
|
1660
1833
|
path: relativePath,
|
|
1661
1834
|
status: "new",
|
|
1662
|
-
diff:
|
|
1835
|
+
diff: chalk9.green("+ NEW FILE")
|
|
1663
1836
|
});
|
|
1664
1837
|
} else if (oldContent === newContent) {
|
|
1665
1838
|
results.push({
|
|
@@ -1805,7 +1978,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1805
1978
|
console.log(ui.success("No obvious issues found"));
|
|
1806
1979
|
}
|
|
1807
1980
|
if (options.auditOnly) {
|
|
1808
|
-
console.log(
|
|
1981
|
+
console.log(chalk9.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
|
|
1809
1982
|
return;
|
|
1810
1983
|
}
|
|
1811
1984
|
if (!options.yes) {
|
|
@@ -1815,19 +1988,19 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1815
1988
|
default: false
|
|
1816
1989
|
});
|
|
1817
1990
|
if (!proceed) {
|
|
1818
|
-
console.log(
|
|
1991
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1819
1992
|
return;
|
|
1820
1993
|
}
|
|
1821
1994
|
}
|
|
1822
1995
|
} else {
|
|
1823
|
-
console.log(
|
|
1996
|
+
console.log(chalk9.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
|
|
1824
1997
|
if (!options.yes) {
|
|
1825
1998
|
const proceed = await confirm2({
|
|
1826
1999
|
message: "Generate Claude Code environment for this project?",
|
|
1827
2000
|
default: true
|
|
1828
2001
|
});
|
|
1829
2002
|
if (!proceed) {
|
|
1830
|
-
console.log(
|
|
2003
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1831
2004
|
return;
|
|
1832
2005
|
}
|
|
1833
2006
|
}
|
|
@@ -1863,8 +2036,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1863
2036
|
console.log(ui.tool(name, tool.reason));
|
|
1864
2037
|
}
|
|
1865
2038
|
}
|
|
2039
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1866
2040
|
if (options.diff) {
|
|
1867
|
-
const diffs = await generateDiff(spec, targetDir);
|
|
2041
|
+
const diffs = await generateDiff(spec, targetDir, { hasEnvVars });
|
|
1868
2042
|
const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
|
|
1869
2043
|
if (changedDiffs.length === 0) {
|
|
1870
2044
|
console.log(ui.success("No changes needed \u2014 environment is already up to date."));
|
|
@@ -1873,7 +2047,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1873
2047
|
}
|
|
1874
2048
|
console.log(ui.section("Changes Preview"));
|
|
1875
2049
|
for (const d of changedDiffs) {
|
|
1876
|
-
console.log(
|
|
2050
|
+
console.log(chalk9.cyan(`
|
|
1877
2051
|
--- ${d.path}`));
|
|
1878
2052
|
if (d.status === "new") {
|
|
1879
2053
|
console.log(` ${d.diff}`);
|
|
@@ -1889,7 +2063,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1889
2063
|
default: true
|
|
1890
2064
|
});
|
|
1891
2065
|
if (!apply) {
|
|
1892
|
-
console.log(
|
|
2066
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1893
2067
|
return;
|
|
1894
2068
|
}
|
|
1895
2069
|
}
|
|
@@ -1900,20 +2074,14 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1900
2074
|
console.log(ui.success(`Ready! Run: $ hermes`));
|
|
1901
2075
|
console.log("");
|
|
1902
2076
|
} else {
|
|
1903
|
-
const written = await writeEnvironment(spec, targetDir);
|
|
2077
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1904
2078
|
console.log(ui.section("Files Written"));
|
|
1905
2079
|
for (const file of written) {
|
|
1906
2080
|
console.log(ui.file(file));
|
|
1907
2081
|
}
|
|
1908
|
-
if (
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
for (const env of summary.envSetup) {
|
|
1912
|
-
if (seen.has(env.envVar)) continue;
|
|
1913
|
-
seen.add(env.envVar);
|
|
1914
|
-
console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
|
|
1915
|
-
console.log("");
|
|
1916
|
-
}
|
|
2082
|
+
if (hasEnvVars) {
|
|
2083
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
2084
|
+
console.log("");
|
|
1917
2085
|
}
|
|
1918
2086
|
if (summary.pluginCommands.length > 0) {
|
|
1919
2087
|
console.log(ui.section("Plugins"));
|
|
@@ -1930,7 +2098,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1930
2098
|
|
|
1931
2099
|
// src/commands/doctor.ts
|
|
1932
2100
|
import { Command as Command7 } from "commander";
|
|
1933
|
-
import
|
|
2101
|
+
import chalk10 from "chalk";
|
|
1934
2102
|
function runChecks(profile) {
|
|
1935
2103
|
const checks = [];
|
|
1936
2104
|
if (!profile.existingClaudeMd) {
|
|
@@ -2062,12 +2230,12 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2062
2230
|
).action(async () => {
|
|
2063
2231
|
printFullBanner("Doctor");
|
|
2064
2232
|
const targetDir = process.cwd();
|
|
2065
|
-
console.log(
|
|
2233
|
+
console.log(chalk10.dim(" Checking .claude/ environment...\n"));
|
|
2066
2234
|
const profile = await scanProject(targetDir);
|
|
2067
2235
|
if (!profile.hasClaudeDir) {
|
|
2068
2236
|
console.log(ui.error("No .claude/ directory found.\n"));
|
|
2069
2237
|
console.log(
|
|
2070
|
-
|
|
2238
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn describe") + chalk10.dim(" or ") + chalk10.bold("kairn optimize") + chalk10.dim(" to generate one.\n")
|
|
2071
2239
|
);
|
|
2072
2240
|
process.exit(1);
|
|
2073
2241
|
}
|
|
@@ -2090,7 +2258,7 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2090
2258
|
return sum;
|
|
2091
2259
|
}, 0);
|
|
2092
2260
|
const percentage = Math.round(score / maxScore * 100);
|
|
2093
|
-
const scoreColor = percentage >= 80 ?
|
|
2261
|
+
const scoreColor = percentage >= 80 ? chalk10.green : percentage >= 50 ? chalk10.yellow : chalk10.red;
|
|
2094
2262
|
console.log(
|
|
2095
2263
|
`
|
|
2096
2264
|
Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
|
|
@@ -2098,14 +2266,14 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2098
2266
|
);
|
|
2099
2267
|
if (percentage < 80) {
|
|
2100
2268
|
console.log(
|
|
2101
|
-
|
|
2269
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn optimize") + chalk10.dim(" to fix issues.\n")
|
|
2102
2270
|
);
|
|
2103
2271
|
}
|
|
2104
2272
|
});
|
|
2105
2273
|
|
|
2106
2274
|
// src/commands/registry.ts
|
|
2107
2275
|
import { Command as Command8 } from "commander";
|
|
2108
|
-
import
|
|
2276
|
+
import chalk11 from "chalk";
|
|
2109
2277
|
import { input as input2, select as select2 } from "@inquirer/prompts";
|
|
2110
2278
|
var listCommand2 = new Command8("list").description("List tools in the registry").option("--category <cat>", "Filter by category").option("--user-only", "Show only user-defined tools").action(async (options) => {
|
|
2111
2279
|
printCompactBanner();
|
|
@@ -2130,7 +2298,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2130
2298
|
);
|
|
2131
2299
|
}
|
|
2132
2300
|
if (tools.length === 0) {
|
|
2133
|
-
console.log(
|
|
2301
|
+
console.log(chalk11.dim("\n No tools found.\n"));
|
|
2134
2302
|
return;
|
|
2135
2303
|
}
|
|
2136
2304
|
const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
|
|
@@ -2144,13 +2312,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2144
2312
|
`tier ${tool.tier}`,
|
|
2145
2313
|
tool.auth
|
|
2146
2314
|
].join(", ");
|
|
2147
|
-
console.log(` ${ui.accent(tool.id)}` +
|
|
2148
|
-
console.log(
|
|
2315
|
+
console.log(` ${ui.accent(tool.id)}` + chalk11.dim(` (${meta})`));
|
|
2316
|
+
console.log(chalk11.dim(` ${tool.description}`));
|
|
2149
2317
|
if (tool.best_for.length > 0) {
|
|
2150
|
-
console.log(
|
|
2318
|
+
console.log(chalk11.dim(` Best for: ${tool.best_for.join(", ")}`));
|
|
2151
2319
|
}
|
|
2152
2320
|
if (isUser) {
|
|
2153
|
-
console.log(
|
|
2321
|
+
console.log(chalk11.yellow(" [USER-DEFINED]"));
|
|
2154
2322
|
}
|
|
2155
2323
|
console.log("");
|
|
2156
2324
|
}
|
|
@@ -2158,7 +2326,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2158
2326
|
const shownUser = tools.filter((t) => userIds.has(t.id)).length;
|
|
2159
2327
|
const shownBundled = totalShown - shownUser;
|
|
2160
2328
|
console.log(
|
|
2161
|
-
|
|
2329
|
+
chalk11.dim(
|
|
2162
2330
|
` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
|
|
2163
2331
|
) + "\n"
|
|
2164
2332
|
);
|
|
@@ -2282,20 +2450,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
|
|
|
2282
2450
|
|
|
2283
2451
|
// src/commands/templates.ts
|
|
2284
2452
|
import { Command as Command9 } from "commander";
|
|
2285
|
-
import
|
|
2286
|
-
import
|
|
2287
|
-
import
|
|
2453
|
+
import chalk12 from "chalk";
|
|
2454
|
+
import fs13 from "fs/promises";
|
|
2455
|
+
import path13 from "path";
|
|
2288
2456
|
var templatesCommand = new Command9("templates").description("Browse available templates").option("--category <cat>", "filter templates by category keyword").option("--json", "output raw JSON array").action(async (options) => {
|
|
2289
2457
|
printCompactBanner();
|
|
2290
2458
|
const templatesDir = getTemplatesDir();
|
|
2291
2459
|
let files;
|
|
2292
2460
|
try {
|
|
2293
|
-
files = await
|
|
2461
|
+
files = await fs13.readdir(templatesDir);
|
|
2294
2462
|
} catch {
|
|
2295
2463
|
console.log(
|
|
2296
|
-
|
|
2464
|
+
chalk12.dim(
|
|
2297
2465
|
" No templates found. Templates will be installed with "
|
|
2298
|
-
) +
|
|
2466
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2299
2467
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2300
2468
|
)
|
|
2301
2469
|
);
|
|
@@ -2304,9 +2472,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2304
2472
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
2305
2473
|
if (jsonFiles.length === 0) {
|
|
2306
2474
|
console.log(
|
|
2307
|
-
|
|
2475
|
+
chalk12.dim(
|
|
2308
2476
|
" No templates found. Templates will be installed with "
|
|
2309
|
-
) +
|
|
2477
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2310
2478
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2311
2479
|
)
|
|
2312
2480
|
);
|
|
@@ -2315,8 +2483,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2315
2483
|
const templates = [];
|
|
2316
2484
|
for (const file of jsonFiles) {
|
|
2317
2485
|
try {
|
|
2318
|
-
const data = await
|
|
2319
|
-
|
|
2486
|
+
const data = await fs13.readFile(
|
|
2487
|
+
path13.join(templatesDir, file),
|
|
2320
2488
|
"utf-8"
|
|
2321
2489
|
);
|
|
2322
2490
|
const spec = JSON.parse(data);
|
|
@@ -2334,7 +2502,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2334
2502
|
}
|
|
2335
2503
|
if (filtered.length === 0) {
|
|
2336
2504
|
console.log(
|
|
2337
|
-
|
|
2505
|
+
chalk12.dim(` No templates matched category "${options.category}".
|
|
2338
2506
|
`)
|
|
2339
2507
|
);
|
|
2340
2508
|
return;
|
|
@@ -2345,8 +2513,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2345
2513
|
const toolCount = spec.tools?.length ?? 0;
|
|
2346
2514
|
const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
|
|
2347
2515
|
const ruleCount = Object.keys(spec.harness?.rules ?? {}).length;
|
|
2348
|
-
console.log(ui.kv("Name",
|
|
2349
|
-
console.log(ui.kv("ID",
|
|
2516
|
+
console.log(ui.kv("Name", chalk12.bold(spec.name)));
|
|
2517
|
+
console.log(ui.kv("ID", chalk12.dim(spec.id)));
|
|
2350
2518
|
console.log(ui.kv("Description", spec.description));
|
|
2351
2519
|
console.log(
|
|
2352
2520
|
ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
|
|
@@ -2354,16 +2522,139 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2354
2522
|
console.log("");
|
|
2355
2523
|
}
|
|
2356
2524
|
console.log(
|
|
2357
|
-
|
|
2525
|
+
chalk12.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
|
|
2358
2526
|
`)
|
|
2359
2527
|
);
|
|
2360
2528
|
});
|
|
2361
2529
|
|
|
2530
|
+
// src/commands/keys.ts
|
|
2531
|
+
import { Command as Command10 } from "commander";
|
|
2532
|
+
import { password as password3 } from "@inquirer/prompts";
|
|
2533
|
+
import chalk13 from "chalk";
|
|
2534
|
+
import fs14 from "fs/promises";
|
|
2535
|
+
import path14 from "path";
|
|
2536
|
+
var keysCommand = new Command10("keys").description("Add or update API keys for the current environment").option("--show", "Show which keys are set vs missing").action(async (options) => {
|
|
2537
|
+
printCompactBanner();
|
|
2538
|
+
const targetDir = process.cwd();
|
|
2539
|
+
const requiredVars = await detectRequiredEnvVars(targetDir);
|
|
2540
|
+
if (requiredVars.length === 0) {
|
|
2541
|
+
console.log(
|
|
2542
|
+
ui.info("No MCP servers found in .mcp.json \u2014 no API keys needed.")
|
|
2543
|
+
);
|
|
2544
|
+
console.log("");
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const existing = await readEnvFile(targetDir);
|
|
2548
|
+
const registry = await loadRegistry();
|
|
2549
|
+
const envSetupMap = /* @__PURE__ */ new Map();
|
|
2550
|
+
for (const tool of registry) {
|
|
2551
|
+
if (!tool.env_vars) continue;
|
|
2552
|
+
for (const ev of tool.env_vars) {
|
|
2553
|
+
if (requiredVars.includes(ev.name)) {
|
|
2554
|
+
envSetupMap.set(ev.name, {
|
|
2555
|
+
toolName: tool.name,
|
|
2556
|
+
envVar: ev.name,
|
|
2557
|
+
description: ev.description,
|
|
2558
|
+
signupUrl: tool.signup_url
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
for (const varName of requiredVars) {
|
|
2564
|
+
if (!envSetupMap.has(varName)) {
|
|
2565
|
+
envSetupMap.set(varName, {
|
|
2566
|
+
toolName: "unknown",
|
|
2567
|
+
envVar: varName,
|
|
2568
|
+
description: "Required by MCP server"
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (options.show) {
|
|
2573
|
+
console.log(ui.section("API Key Status"));
|
|
2574
|
+
console.log("");
|
|
2575
|
+
for (const varName of requiredVars) {
|
|
2576
|
+
const value = existing.get(varName);
|
|
2577
|
+
const info = envSetupMap.get(varName);
|
|
2578
|
+
const toolLabel = info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "";
|
|
2579
|
+
if (value && value.length > 0) {
|
|
2580
|
+
const masked = value.slice(0, 4) + "\u2022".repeat(Math.max(0, value.length - 4));
|
|
2581
|
+
console.log(chalk13.green(` \u2713 ${varName}`) + toolLabel + chalk13.dim(` = ${masked}`));
|
|
2582
|
+
} else {
|
|
2583
|
+
console.log(chalk13.yellow(` \u2717 ${varName}`) + toolLabel + chalk13.dim(" = (not set)"));
|
|
2584
|
+
if (info?.signupUrl) {
|
|
2585
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
const setCount = requiredVars.filter((v) => {
|
|
2590
|
+
const val = existing.get(v);
|
|
2591
|
+
return val && val.length > 0;
|
|
2592
|
+
}).length;
|
|
2593
|
+
const missingCount = requiredVars.length - setCount;
|
|
2594
|
+
console.log("");
|
|
2595
|
+
if (missingCount === 0) {
|
|
2596
|
+
console.log(ui.success(`All ${setCount} key(s) configured`));
|
|
2597
|
+
} else {
|
|
2598
|
+
console.log(
|
|
2599
|
+
ui.warn(`${missingCount} key(s) missing \u2014 run ${chalk13.bold("kairn keys")} to add them`)
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
console.log("");
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const missing = requiredVars.filter((v) => {
|
|
2606
|
+
const val = existing.get(v);
|
|
2607
|
+
return !val || val.length === 0;
|
|
2608
|
+
});
|
|
2609
|
+
if (missing.length === 0) {
|
|
2610
|
+
console.log(ui.success("All API keys are already configured."));
|
|
2611
|
+
console.log(chalk13.dim(" Use --show to see current keys.\n"));
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
console.log(ui.section("API Keys"));
|
|
2615
|
+
console.log(chalk13.dim(` ${missing.length} key(s) need to be set. Press Enter to skip.
|
|
2616
|
+
`));
|
|
2617
|
+
const envEntries = new Map(existing);
|
|
2618
|
+
let keysEntered = 0;
|
|
2619
|
+
for (const varName of missing) {
|
|
2620
|
+
const info = envSetupMap.get(varName);
|
|
2621
|
+
console.log(
|
|
2622
|
+
chalk13.bold(` ${varName}`) + (info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "")
|
|
2623
|
+
);
|
|
2624
|
+
if (info?.signupUrl) {
|
|
2625
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2626
|
+
}
|
|
2627
|
+
const value = await password3({
|
|
2628
|
+
message: varName,
|
|
2629
|
+
mask: "\u2022"
|
|
2630
|
+
});
|
|
2631
|
+
if (value && value.trim()) {
|
|
2632
|
+
envEntries.set(varName, value.trim());
|
|
2633
|
+
console.log(chalk13.green(" \u2713 saved\n"));
|
|
2634
|
+
keysEntered++;
|
|
2635
|
+
} else {
|
|
2636
|
+
console.log(chalk13.dim(" (skipped)\n"));
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
const envLines = [
|
|
2640
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
2641
|
+
"# Do NOT commit this file to git",
|
|
2642
|
+
""
|
|
2643
|
+
];
|
|
2644
|
+
for (const [key, value] of envEntries) {
|
|
2645
|
+
envLines.push(`${key}=${value}`);
|
|
2646
|
+
}
|
|
2647
|
+
const envPath = path14.join(targetDir, ".env");
|
|
2648
|
+
await fs14.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
|
|
2649
|
+
console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
|
|
2650
|
+
console.log("");
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2362
2653
|
// src/cli.ts
|
|
2363
|
-
var program = new
|
|
2654
|
+
var program = new Command11();
|
|
2364
2655
|
program.name("kairn").description(
|
|
2365
2656
|
"Compile natural language intent into optimized Claude Code environments"
|
|
2366
|
-
).version("1.
|
|
2657
|
+
).version("1.8.0").option("--no-color", "Disable colored output");
|
|
2367
2658
|
program.addCommand(initCommand);
|
|
2368
2659
|
program.addCommand(describeCommand);
|
|
2369
2660
|
program.addCommand(optimizeCommand);
|
|
@@ -2373,8 +2664,9 @@ program.addCommand(updateRegistryCommand);
|
|
|
2373
2664
|
program.addCommand(doctorCommand);
|
|
2374
2665
|
program.addCommand(registryCommand);
|
|
2375
2666
|
program.addCommand(templatesCommand);
|
|
2667
|
+
program.addCommand(keysCommand);
|
|
2376
2668
|
if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
|
|
2377
|
-
|
|
2669
|
+
chalk14.level = 0;
|
|
2378
2670
|
}
|
|
2379
2671
|
program.parse();
|
|
2380
2672
|
//# sourceMappingURL=cli.js.map
|