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