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