kairn-cli 1.0.0 → 1.2.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,5 +1,5 @@
1
1
  // src/cli.ts
2
- import { Command as Command6 } from "commander";
2
+ import { Command as Command7 } from "commander";
3
3
 
4
4
  // src/commands/init.ts
5
5
  import { Command } from "commander";
@@ -29,7 +29,17 @@ async function ensureDirs() {
29
29
  async function loadConfig() {
30
30
  try {
31
31
  const data = await fs.readFile(CONFIG_PATH, "utf-8");
32
- return JSON.parse(data);
32
+ const raw = JSON.parse(data);
33
+ if (raw.anthropic_api_key && !raw.provider) {
34
+ return {
35
+ provider: "anthropic",
36
+ api_key: raw.anthropic_api_key,
37
+ model: "claude-sonnet-4-6",
38
+ default_runtime: "claude-code",
39
+ created_at: raw.created_at || (/* @__PURE__ */ new Date()).toISOString()
40
+ };
41
+ }
42
+ return raw;
33
43
  } catch {
34
44
  return null;
35
45
  }
@@ -44,9 +54,9 @@ var PROVIDER_MODELS = {
44
54
  anthropic: {
45
55
  name: "Anthropic",
46
56
  models: [
47
- { name: "Claude Sonnet 4 (recommended \u2014 fast, cheap)", value: "claude-sonnet-4-20250514" },
48
- { name: "Claude Opus 4 (highest quality)", value: "claude-opus-4-20250514" },
49
- { name: "Claude Haiku 3.5 (fastest, cheapest)", value: "claude-3-5-haiku-20241022" }
57
+ { name: "Claude Sonnet 4.6 (recommended \u2014 fast, smart)", value: "claude-sonnet-4-6" },
58
+ { name: "Claude Opus 4.6 (highest quality)", value: "claude-opus-4-6" },
59
+ { name: "Claude Haiku 4.5 (fastest, cheapest)", value: "claude-haiku-4-5-20251001" }
50
60
  ]
51
61
  },
52
62
  openai: {
@@ -70,7 +80,7 @@ async function verifyKey(provider, apiKey, model) {
70
80
  if (provider === "anthropic") {
71
81
  const client = new Anthropic({ apiKey });
72
82
  await client.messages.create({
73
- model: "claude-3-5-haiku-20241022",
83
+ model: "claude-haiku-4-5-20251001",
74
84
  max_tokens: 10,
75
85
  messages: [{ role: "user", content: "ping" }]
76
86
  });
@@ -514,11 +524,23 @@ async function writeEnvironment(spec, targetDir) {
514
524
  }
515
525
  function summarizeSpec(spec, registry) {
516
526
  const pluginCommands = [];
527
+ const envSetup = [];
517
528
  for (const selected of spec.tools) {
518
529
  const tool = registry.find((t) => t.id === selected.tool_id);
519
- if (tool?.install.plugin_command) {
530
+ if (!tool) continue;
531
+ if (tool.install.plugin_command) {
520
532
  pluginCommands.push(tool.install.plugin_command);
521
533
  }
534
+ if (tool.env_vars) {
535
+ for (const ev of tool.env_vars) {
536
+ envSetup.push({
537
+ toolName: tool.name,
538
+ envVar: ev.name,
539
+ description: ev.description,
540
+ signupUrl: tool.signup_url
541
+ });
542
+ }
543
+ }
522
544
  }
523
545
  return {
524
546
  toolCount: spec.tools.length,
@@ -526,7 +548,8 @@ function summarizeSpec(spec, registry) {
526
548
  ruleCount: Object.keys(spec.harness.rules || {}).length,
527
549
  skillCount: Object.keys(spec.harness.skills || {}).length,
528
550
  agentCount: Object.keys(spec.harness.agents || {}).length,
529
- pluginCommands
551
+ pluginCommands,
552
+ envSetup
530
553
  };
531
554
  }
532
555
 
@@ -619,8 +642,22 @@ var describeCommand = new Command2("describe").description("Describe your workfl
619
642
  for (const file of written) {
620
643
  console.log(chalk2.dim(` ${file}`));
621
644
  }
645
+ if (summary.envSetup.length > 0) {
646
+ console.log(chalk2.yellow("\n API keys needed (set these environment variables):\n"));
647
+ const seen = /* @__PURE__ */ new Set();
648
+ for (const env of summary.envSetup) {
649
+ if (seen.has(env.envVar)) continue;
650
+ seen.add(env.envVar);
651
+ console.log(chalk2.bold(` export ${env.envVar}="your-key-here"`));
652
+ console.log(chalk2.dim(` ${env.description}`));
653
+ if (env.signupUrl) {
654
+ console.log(chalk2.dim(` Get one at: ${env.signupUrl}`));
655
+ }
656
+ console.log("");
657
+ }
658
+ }
622
659
  if (summary.pluginCommands.length > 0) {
623
- console.log(chalk2.yellow("\n Install plugins by running these in Claude Code:"));
660
+ console.log(chalk2.yellow(" Install plugins by running these in Claude Code:"));
624
661
  for (const cmd of summary.pluginCommands) {
625
662
  console.log(chalk2.bold(` ${cmd}`));
626
663
  }
@@ -780,13 +817,437 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
780
817
  }
781
818
  });
782
819
 
820
+ // src/commands/optimize.ts
821
+ import { Command as Command6 } from "commander";
822
+ import { confirm as confirm2 } from "@inquirer/prompts";
823
+ import chalk6 from "chalk";
824
+ import fs9 from "fs/promises";
825
+ import path9 from "path";
826
+ import { fileURLToPath as fileURLToPath4 } from "url";
827
+
828
+ // src/scanner/scan.ts
829
+ import fs8 from "fs/promises";
830
+ import path8 from "path";
831
+ async function fileExists(p) {
832
+ try {
833
+ await fs8.access(p);
834
+ return true;
835
+ } catch {
836
+ return false;
837
+ }
838
+ }
839
+ async function readJsonSafe(p) {
840
+ try {
841
+ const data = await fs8.readFile(p, "utf-8");
842
+ return JSON.parse(data);
843
+ } catch {
844
+ return null;
845
+ }
846
+ }
847
+ async function readFileSafe(p) {
848
+ try {
849
+ return await fs8.readFile(p, "utf-8");
850
+ } catch {
851
+ return null;
852
+ }
853
+ }
854
+ async function listDirSafe(p) {
855
+ try {
856
+ const entries = await fs8.readdir(p);
857
+ return entries.filter((e) => !e.startsWith("."));
858
+ } catch {
859
+ return [];
860
+ }
861
+ }
862
+ function detectFramework(deps) {
863
+ const frameworks = [
864
+ [["next"], "Next.js"],
865
+ [["nuxt"], "Nuxt"],
866
+ [["@remix-run/node", "@remix-run/react"], "Remix"],
867
+ [["svelte", "@sveltejs/kit"], "SvelteKit"],
868
+ [["express"], "Express"],
869
+ [["fastify"], "Fastify"],
870
+ [["hono"], "Hono"],
871
+ [["react", "react-dom"], "React"],
872
+ [["vue"], "Vue"],
873
+ [["angular"], "Angular"],
874
+ [["django"], "Django"],
875
+ [["flask"], "Flask"],
876
+ [["fastapi"], "FastAPI"],
877
+ [["@supabase/supabase-js"], "Supabase"],
878
+ [["prisma", "@prisma/client"], "Prisma"],
879
+ [["drizzle-orm"], "Drizzle"],
880
+ [["tailwindcss"], "Tailwind CSS"]
881
+ ];
882
+ const detected = [];
883
+ for (const [packages, name] of frameworks) {
884
+ if (packages.some((pkg) => deps.includes(pkg))) {
885
+ detected.push(name);
886
+ }
887
+ }
888
+ return detected.length > 0 ? detected.join(" + ") : null;
889
+ }
890
+ function detectLanguage(dir, keyFiles) {
891
+ if (keyFiles.some((f) => f === "tsconfig.json")) return "TypeScript";
892
+ if (keyFiles.some((f) => f === "package.json")) return "JavaScript";
893
+ if (keyFiles.some((f) => f === "pyproject.toml" || f === "setup.py" || f === "requirements.txt")) return "Python";
894
+ if (keyFiles.some((f) => f === "Cargo.toml")) return "Rust";
895
+ if (keyFiles.some((f) => f === "go.mod")) return "Go";
896
+ if (keyFiles.some((f) => f === "Gemfile")) return "Ruby";
897
+ return null;
898
+ }
899
+ function extractEnvKeys(content) {
900
+ const keys = [];
901
+ for (const line of content.split("\n")) {
902
+ const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
903
+ if (match) keys.push(match[1]);
904
+ }
905
+ return keys;
906
+ }
907
+ async function scanProject(dir) {
908
+ const pkg = await readJsonSafe(path8.join(dir, "package.json"));
909
+ const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
910
+ const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
911
+ const allDeps = [...deps, ...devDeps];
912
+ const scripts = pkg?.scripts || {};
913
+ const rootFiles = await listDirSafe(dir);
914
+ const keyFiles = rootFiles.filter(
915
+ (f) => [
916
+ "package.json",
917
+ "tsconfig.json",
918
+ "pyproject.toml",
919
+ "setup.py",
920
+ "requirements.txt",
921
+ "Cargo.toml",
922
+ "go.mod",
923
+ "Gemfile",
924
+ "docker-compose.yml",
925
+ "Dockerfile",
926
+ ".env.example",
927
+ ".env",
928
+ "README.md",
929
+ "CLAUDE.md"
930
+ ].includes(f)
931
+ );
932
+ const language = detectLanguage(dir, keyFiles);
933
+ const framework = detectFramework(allDeps);
934
+ const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
935
+ const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
936
+ const hasTests = testCommand !== null || await fileExists(path8.join(dir, "tests")) || await fileExists(path8.join(dir, "__tests__")) || await fileExists(path8.join(dir, "test"));
937
+ const buildCommand = scripts.build || null;
938
+ const lintCommand = scripts.lint || null;
939
+ const hasSrc = await fileExists(path8.join(dir, "src"));
940
+ const hasDocker = await fileExists(path8.join(dir, "docker-compose.yml")) || await fileExists(path8.join(dir, "Dockerfile"));
941
+ const hasCi = await fileExists(path8.join(dir, ".github/workflows"));
942
+ const hasEnvFile = await fileExists(path8.join(dir, ".env")) || await fileExists(path8.join(dir, ".env.example"));
943
+ let envKeys = [];
944
+ const envExample = await readFileSafe(path8.join(dir, ".env.example"));
945
+ if (envExample) {
946
+ envKeys = extractEnvKeys(envExample);
947
+ }
948
+ const claudeDir = path8.join(dir, ".claude");
949
+ const hasClaudeDir = await fileExists(claudeDir);
950
+ let existingClaudeMd = null;
951
+ let existingSettings = null;
952
+ let existingMcpConfig = null;
953
+ let existingCommands = [];
954
+ let existingRules = [];
955
+ let existingSkills = [];
956
+ let existingAgents = [];
957
+ let mcpServerCount = 0;
958
+ let claudeMdLineCount = 0;
959
+ if (hasClaudeDir) {
960
+ existingClaudeMd = await readFileSafe(path8.join(claudeDir, "CLAUDE.md"));
961
+ if (existingClaudeMd) {
962
+ claudeMdLineCount = existingClaudeMd.split("\n").length;
963
+ }
964
+ existingSettings = await readJsonSafe(path8.join(claudeDir, "settings.json"));
965
+ existingMcpConfig = await readJsonSafe(path8.join(dir, ".mcp.json"));
966
+ if (existingMcpConfig?.mcpServers) {
967
+ mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
968
+ }
969
+ existingCommands = (await listDirSafe(path8.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
970
+ existingRules = (await listDirSafe(path8.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
971
+ existingSkills = await listDirSafe(path8.join(claudeDir, "skills"));
972
+ existingAgents = (await listDirSafe(path8.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
973
+ }
974
+ const name = pkg?.name || path8.basename(dir);
975
+ const description = pkg?.description || "";
976
+ return {
977
+ name,
978
+ description,
979
+ directory: dir,
980
+ language,
981
+ framework,
982
+ typescript,
983
+ dependencies: deps,
984
+ devDependencies: devDeps,
985
+ scripts,
986
+ hasTests,
987
+ testCommand,
988
+ buildCommand,
989
+ lintCommand,
990
+ hasSrc,
991
+ hasDocker,
992
+ hasCi,
993
+ hasEnvFile,
994
+ envKeys,
995
+ hasClaudeDir,
996
+ existingClaudeMd,
997
+ existingSettings,
998
+ existingMcpConfig,
999
+ existingCommands,
1000
+ existingRules,
1001
+ existingSkills,
1002
+ existingAgents,
1003
+ mcpServerCount,
1004
+ claudeMdLineCount,
1005
+ keyFiles
1006
+ };
1007
+ }
1008
+
1009
+ // src/commands/optimize.ts
1010
+ async function loadRegistry3() {
1011
+ const __filename = fileURLToPath4(import.meta.url);
1012
+ const __dirname = path9.dirname(__filename);
1013
+ const candidates = [
1014
+ path9.resolve(__dirname, "../registry/tools.json"),
1015
+ path9.resolve(__dirname, "../src/registry/tools.json"),
1016
+ path9.resolve(__dirname, "../../src/registry/tools.json")
1017
+ ];
1018
+ for (const candidate of candidates) {
1019
+ try {
1020
+ const data = await fs9.readFile(candidate, "utf-8");
1021
+ return JSON.parse(data);
1022
+ } catch {
1023
+ continue;
1024
+ }
1025
+ }
1026
+ throw new Error("Could not find tools.json registry");
1027
+ }
1028
+ function buildProfileSummary(profile) {
1029
+ const lines = [];
1030
+ lines.push(`Project: ${profile.name}`);
1031
+ if (profile.description) lines.push(`Description: ${profile.description}`);
1032
+ if (profile.language) lines.push(`Language: ${profile.language}`);
1033
+ if (profile.framework) lines.push(`Framework: ${profile.framework}`);
1034
+ if (profile.dependencies.length > 0) {
1035
+ lines.push(`Dependencies: ${profile.dependencies.join(", ")}`);
1036
+ }
1037
+ if (profile.testCommand) lines.push(`Test command: ${profile.testCommand}`);
1038
+ if (profile.buildCommand) lines.push(`Build command: ${profile.buildCommand}`);
1039
+ if (profile.lintCommand) lines.push(`Lint command: ${profile.lintCommand}`);
1040
+ if (profile.hasDocker) lines.push("Has Docker configuration");
1041
+ if (profile.hasCi) lines.push("Has CI/CD (GitHub Actions)");
1042
+ if (profile.envKeys.length > 0) {
1043
+ lines.push(`Env keys needed: ${profile.envKeys.join(", ")}`);
1044
+ }
1045
+ return lines.join("\n");
1046
+ }
1047
+ function buildAuditSummary(profile) {
1048
+ const lines = [];
1049
+ lines.push(`
1050
+ Existing .claude/ harness found:`);
1051
+ lines.push(` CLAUDE.md: ${profile.claudeMdLineCount} lines${profile.claudeMdLineCount > 200 ? " (\u26A0 over 200 \u2014 may degrade adherence)" : ""}`);
1052
+ lines.push(` MCP servers: ${profile.mcpServerCount}`);
1053
+ lines.push(` Commands: ${profile.existingCommands.length > 0 ? profile.existingCommands.map((c) => `/project:${c}`).join(", ") : "none"}`);
1054
+ lines.push(` Rules: ${profile.existingRules.length > 0 ? profile.existingRules.join(", ") : "none"}`);
1055
+ lines.push(` Skills: ${profile.existingSkills.length > 0 ? profile.existingSkills.join(", ") : "none"}`);
1056
+ lines.push(` Agents: ${profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"}`);
1057
+ return lines.join("\n");
1058
+ }
1059
+ function buildOptimizeIntent(profile) {
1060
+ const parts = [];
1061
+ parts.push("## Project Profile (scanned from actual codebase)\n");
1062
+ parts.push(buildProfileSummary(profile));
1063
+ if (profile.hasClaudeDir) {
1064
+ parts.push(buildAuditSummary(profile));
1065
+ if (profile.existingClaudeMd) {
1066
+ parts.push(`
1067
+ ## Existing CLAUDE.md Content
1068
+
1069
+ ${profile.existingClaudeMd}`);
1070
+ }
1071
+ parts.push(`
1072
+ ## Task
1073
+ `);
1074
+ parts.push("Analyze this existing Claude Code environment and generate an OPTIMIZED version.");
1075
+ parts.push("Preserve what works. Fix what's wrong. Add what's missing. Remove what's bloat.");
1076
+ parts.push("Key optimizations to consider:");
1077
+ parts.push("- Is CLAUDE.md under 100 lines? If not, move detail to rules/ or docs/");
1078
+ parts.push("- Are the right MCP servers selected for these dependencies?");
1079
+ parts.push("- Are there missing slash commands (help, tasks, plan, test, commit)?");
1080
+ parts.push("- Are security rules present?");
1081
+ parts.push("- Is there a continuity rule for session memory?");
1082
+ parts.push("- Are there unnecessary MCP servers adding context bloat?");
1083
+ if (profile.claudeMdLineCount > 200) {
1084
+ parts.push(`- CLAUDE.md is ${profile.claudeMdLineCount} lines \u2014 needs aggressive trimming`);
1085
+ }
1086
+ if (!profile.existingCommands.includes("help")) {
1087
+ parts.push("- Missing /project:help command");
1088
+ }
1089
+ if (!profile.existingRules.includes("security")) {
1090
+ parts.push("- Missing security rules");
1091
+ }
1092
+ } else {
1093
+ parts.push(`
1094
+ ## Task
1095
+ `);
1096
+ parts.push("Generate an optimal Claude Code environment for this existing project.");
1097
+ parts.push("Use the scanned project profile \u2014 this is a real codebase, not a description.");
1098
+ parts.push("The environment should match the actual tech stack, dependencies, and workflows.");
1099
+ }
1100
+ return parts.join("\n");
1101
+ }
1102
+ 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").action(async (options) => {
1103
+ const config = await loadConfig();
1104
+ if (!config) {
1105
+ console.log(
1106
+ chalk6.red("\n No config found. Run ") + chalk6.bold("kairn init") + chalk6.red(" first.\n")
1107
+ );
1108
+ process.exit(1);
1109
+ }
1110
+ const targetDir = process.cwd();
1111
+ console.log(chalk6.dim("\n Scanning project..."));
1112
+ const profile = await scanProject(targetDir);
1113
+ console.log(chalk6.cyan("\n Project Profile\n"));
1114
+ if (profile.language) console.log(chalk6.dim(` Language: ${profile.language}`));
1115
+ if (profile.framework) console.log(chalk6.dim(` Framework: ${profile.framework}`));
1116
+ console.log(chalk6.dim(` Dependencies: ${profile.dependencies.length}`));
1117
+ if (profile.testCommand) console.log(chalk6.dim(` Tests: ${profile.testCommand}`));
1118
+ if (profile.buildCommand) console.log(chalk6.dim(` Build: ${profile.buildCommand}`));
1119
+ if (profile.hasDocker) console.log(chalk6.dim(" Docker: yes"));
1120
+ if (profile.hasCi) console.log(chalk6.dim(" CI/CD: yes"));
1121
+ if (profile.envKeys.length > 0) console.log(chalk6.dim(` Env keys: ${profile.envKeys.join(", ")}`));
1122
+ if (profile.hasClaudeDir) {
1123
+ console.log(chalk6.yellow("\n Existing .claude/ harness detected\n"));
1124
+ console.log(chalk6.dim(` CLAUDE.md: ${profile.claudeMdLineCount} lines${profile.claudeMdLineCount > 200 ? chalk6.yellow(" \u26A0 bloated") : chalk6.green(" \u2713")}`));
1125
+ console.log(chalk6.dim(` MCP servers: ${profile.mcpServerCount}`));
1126
+ console.log(chalk6.dim(` Commands: ${profile.existingCommands.length > 0 ? profile.existingCommands.map((c) => c).join(", ") : "none"}`));
1127
+ console.log(chalk6.dim(` Rules: ${profile.existingRules.length > 0 ? profile.existingRules.join(", ") : "none"}`));
1128
+ console.log(chalk6.dim(` Skills: ${profile.existingSkills.length > 0 ? profile.existingSkills.join(", ") : "none"}`));
1129
+ console.log(chalk6.dim(` Agents: ${profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"}`));
1130
+ const issues = [];
1131
+ if (profile.claudeMdLineCount > 200) issues.push("CLAUDE.md over 200 lines \u2014 move detail to rules/ or docs/");
1132
+ if (!profile.existingCommands.includes("help")) issues.push("Missing /project:help command");
1133
+ if (!profile.existingRules.includes("security")) issues.push("Missing security rules");
1134
+ if (!profile.existingRules.includes("continuity")) issues.push("Missing continuity rule for session memory");
1135
+ if (profile.mcpServerCount > 8) issues.push(`${profile.mcpServerCount} MCP servers \u2014 may cause context bloat`);
1136
+ if (profile.mcpServerCount === 0 && profile.dependencies.length > 0) issues.push("No MCP servers configured");
1137
+ if (profile.hasTests && !profile.existingCommands.includes("test")) issues.push("Has tests but no /project:test command");
1138
+ if (!profile.existingCommands.includes("tasks")) issues.push("Missing /project:tasks command");
1139
+ if (issues.length > 0) {
1140
+ console.log(chalk6.yellow("\n Issues Found:\n"));
1141
+ for (const issue of issues) {
1142
+ console.log(chalk6.yellow(` \u26A0 ${issue}`));
1143
+ }
1144
+ } else {
1145
+ console.log(chalk6.green("\n \u2713 No obvious issues found"));
1146
+ }
1147
+ if (options.auditOnly) {
1148
+ console.log(chalk6.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
1149
+ return;
1150
+ }
1151
+ if (!options.yes) {
1152
+ console.log("");
1153
+ const proceed = await confirm2({
1154
+ message: "Generate optimized environment? This will overwrite existing .claude/ files.",
1155
+ default: false
1156
+ });
1157
+ if (!proceed) {
1158
+ console.log(chalk6.dim("\n Aborted.\n"));
1159
+ return;
1160
+ }
1161
+ }
1162
+ } else {
1163
+ console.log(chalk6.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
1164
+ if (!options.yes) {
1165
+ const proceed = await confirm2({
1166
+ message: "Generate Claude Code environment for this project?",
1167
+ default: true
1168
+ });
1169
+ if (!proceed) {
1170
+ console.log(chalk6.dim("\n Aborted.\n"));
1171
+ return;
1172
+ }
1173
+ }
1174
+ }
1175
+ const intent = buildOptimizeIntent(profile);
1176
+ let spec;
1177
+ try {
1178
+ spec = await compile(intent, (msg) => {
1179
+ process.stdout.write(`\r ${chalk6.dim(msg)} `);
1180
+ });
1181
+ process.stdout.write("\r \r");
1182
+ } catch (err) {
1183
+ process.stdout.write("\r \r");
1184
+ const msg = err instanceof Error ? err.message : String(err);
1185
+ console.log(chalk6.red(`
1186
+ Optimization failed: ${msg}
1187
+ `));
1188
+ process.exit(1);
1189
+ }
1190
+ const registry = await loadRegistry3();
1191
+ const summary = summarizeSpec(spec, registry);
1192
+ console.log(chalk6.green(" \u2713 Environment compiled\n"));
1193
+ console.log(chalk6.cyan(" Name: ") + spec.name);
1194
+ console.log(chalk6.cyan(" Tools: ") + summary.toolCount);
1195
+ console.log(chalk6.cyan(" Commands: ") + summary.commandCount);
1196
+ console.log(chalk6.cyan(" Rules: ") + summary.ruleCount);
1197
+ console.log(chalk6.cyan(" Skills: ") + summary.skillCount);
1198
+ console.log(chalk6.cyan(" Agents: ") + summary.agentCount);
1199
+ if (spec.tools.length > 0) {
1200
+ console.log(chalk6.dim("\n Selected tools:"));
1201
+ for (const tool of spec.tools) {
1202
+ const regTool = registry.find((t) => t.id === tool.tool_id);
1203
+ const name = regTool?.name || tool.tool_id;
1204
+ console.log(chalk6.dim(` - ${name}: ${tool.reason}`));
1205
+ }
1206
+ }
1207
+ if (summary.pluginCommands.length > 0) {
1208
+ console.log(chalk6.yellow("\n Plugins to install manually:"));
1209
+ for (const cmd of summary.pluginCommands) {
1210
+ console.log(chalk6.yellow(` ${cmd}`));
1211
+ }
1212
+ }
1213
+ const written = await writeEnvironment(spec, targetDir);
1214
+ console.log(chalk6.green("\n \u2713 Environment written\n"));
1215
+ for (const file of written) {
1216
+ console.log(chalk6.dim(` ${file}`));
1217
+ }
1218
+ if (summary.envSetup.length > 0) {
1219
+ console.log(chalk6.yellow("\n API keys needed (set these environment variables):\n"));
1220
+ const seen = /* @__PURE__ */ new Set();
1221
+ for (const env of summary.envSetup) {
1222
+ if (seen.has(env.envVar)) continue;
1223
+ seen.add(env.envVar);
1224
+ console.log(chalk6.bold(` export ${env.envVar}="your-key-here"`));
1225
+ console.log(chalk6.dim(` ${env.description}`));
1226
+ if (env.signupUrl) {
1227
+ console.log(chalk6.dim(` Get one at: ${env.signupUrl}`));
1228
+ }
1229
+ console.log("");
1230
+ }
1231
+ }
1232
+ if (summary.pluginCommands.length > 0) {
1233
+ console.log(chalk6.yellow(" Install plugins by running these in Claude Code:"));
1234
+ for (const cmd of summary.pluginCommands) {
1235
+ console.log(chalk6.bold(` ${cmd}`));
1236
+ }
1237
+ }
1238
+ console.log(
1239
+ chalk6.cyan("\n Ready! Run ") + chalk6.bold("claude") + chalk6.cyan(" to start.\n")
1240
+ );
1241
+ });
1242
+
783
1243
  // src/cli.ts
784
- var program = new Command6();
1244
+ var program = new Command7();
785
1245
  program.name("kairn").description(
786
1246
  "Compile natural language intent into optimized Claude Code environments"
787
- ).version("0.1.0");
1247
+ ).version("1.0.0");
788
1248
  program.addCommand(initCommand);
789
1249
  program.addCommand(describeCommand);
1250
+ program.addCommand(optimizeCommand);
790
1251
  program.addCommand(listCommand);
791
1252
  program.addCommand(activateCommand);
792
1253
  program.addCommand(updateRegistryCommand);