kairn-cli 1.11.0 → 1.12.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 +153 -21
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -256,6 +256,113 @@ var ui = {
|
|
|
256
256
|
`);
|
|
257
257
|
}
|
|
258
258
|
};
|
|
259
|
+
function formatTime(seconds) {
|
|
260
|
+
if (seconds < 60) return `${seconds}s`;
|
|
261
|
+
const min = Math.floor(seconds / 60);
|
|
262
|
+
const sec = seconds % 60;
|
|
263
|
+
return sec > 0 ? `${min}m ${sec}s` : `${min} min`;
|
|
264
|
+
}
|
|
265
|
+
function estimateTime(model, intent) {
|
|
266
|
+
const wordCount = intent.split(/\s+/).length;
|
|
267
|
+
const isComplex = wordCount > 40;
|
|
268
|
+
const perPass = {
|
|
269
|
+
"haiku": 5,
|
|
270
|
+
"sonnet": 20,
|
|
271
|
+
"opus": 60,
|
|
272
|
+
"gpt-4.1-mini": 10,
|
|
273
|
+
"gpt-4.1": 25,
|
|
274
|
+
"gpt-5": 15,
|
|
275
|
+
"o4-mini": 12,
|
|
276
|
+
"gemini-2.5-flash": 8,
|
|
277
|
+
"gemini-3-flash": 8,
|
|
278
|
+
"gemini-2.5-pro": 30,
|
|
279
|
+
"gemini-3.1-pro": 30,
|
|
280
|
+
"grok-4.1-fast": 10,
|
|
281
|
+
"grok-4.20": 25,
|
|
282
|
+
"deepseek": 15,
|
|
283
|
+
"mistral-large": 20,
|
|
284
|
+
"codestral": 15,
|
|
285
|
+
"mistral-small": 10,
|
|
286
|
+
"llama": 10,
|
|
287
|
+
"qwen": 10
|
|
288
|
+
};
|
|
289
|
+
const basePerPass = Object.entries(perPass).find(([k]) => model.toLowerCase().includes(k))?.[1] ?? 20;
|
|
290
|
+
const totalBase = basePerPass * 2;
|
|
291
|
+
if (isComplex) {
|
|
292
|
+
const low = Math.floor(totalBase * 1.5);
|
|
293
|
+
const high = Math.floor(totalBase * 4);
|
|
294
|
+
return `~${formatTime(low)}-${formatTime(high)} (complex workflow)`;
|
|
295
|
+
}
|
|
296
|
+
return `~${formatTime(totalBase)}`;
|
|
297
|
+
}
|
|
298
|
+
function createProgressRenderer() {
|
|
299
|
+
const lines = [];
|
|
300
|
+
let intervalId = null;
|
|
301
|
+
let currentPhase = "";
|
|
302
|
+
let phaseStart = Date.now();
|
|
303
|
+
let lineCount = 0;
|
|
304
|
+
function render() {
|
|
305
|
+
if (lineCount > 0) {
|
|
306
|
+
process.stdout.write(`\x1B[${lineCount}A`);
|
|
307
|
+
}
|
|
308
|
+
for (const line of lines) {
|
|
309
|
+
process.stdout.write("\x1B[2K" + line + "\n");
|
|
310
|
+
}
|
|
311
|
+
lineCount = lines.length;
|
|
312
|
+
}
|
|
313
|
+
function updateElapsed() {
|
|
314
|
+
if (!currentPhase) return;
|
|
315
|
+
const elapsed = Math.floor((Date.now() - phaseStart) / 1e3);
|
|
316
|
+
const lastIdx = lines.length - 1;
|
|
317
|
+
if (lastIdx >= 0) {
|
|
318
|
+
lines[lastIdx] = lines[lastIdx].replace(/\[\d+s\]/, `[${elapsed}s]`);
|
|
319
|
+
render();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
update(progress) {
|
|
324
|
+
if (progress.status === "running") {
|
|
325
|
+
currentPhase = progress.phase;
|
|
326
|
+
phaseStart = Date.now();
|
|
327
|
+
lines.push(` ${warmStone("\u25D0")} ${progress.message} ${chalk.dim("[0s]")}`);
|
|
328
|
+
if (!intervalId) {
|
|
329
|
+
intervalId = setInterval(updateElapsed, 1e3);
|
|
330
|
+
}
|
|
331
|
+
} else if (progress.status === "success") {
|
|
332
|
+
const lastIdx = lines.length - 1;
|
|
333
|
+
const elapsed = progress.elapsed != null ? ` ${chalk.dim("\u2014")} ${chalk.dim(Math.floor(progress.elapsed) + "s")}` : "";
|
|
334
|
+
const detail = progress.detail ? ` ${chalk.dim("(" + progress.detail + ")")}` : "";
|
|
335
|
+
if (lastIdx >= 0) {
|
|
336
|
+
lines[lastIdx] = ` ${chalk.green("\u2714")} ${progress.message}${detail}${elapsed}`;
|
|
337
|
+
}
|
|
338
|
+
currentPhase = "";
|
|
339
|
+
} else if (progress.status === "warning") {
|
|
340
|
+
const lastIdx = lines.length - 1;
|
|
341
|
+
if (lastIdx >= 0) {
|
|
342
|
+
lines[lastIdx] = ` ${chalk.yellow("\u26A0")} ${progress.message}`;
|
|
343
|
+
}
|
|
344
|
+
currentPhase = progress.phase;
|
|
345
|
+
phaseStart = Date.now();
|
|
346
|
+
lines.push(` ${warmStone("\u25D0")} Retrying in concise mode... ${chalk.dim("[0s]")}`);
|
|
347
|
+
}
|
|
348
|
+
render();
|
|
349
|
+
},
|
|
350
|
+
finish() {
|
|
351
|
+
if (intervalId) clearInterval(intervalId);
|
|
352
|
+
currentPhase = "";
|
|
353
|
+
render();
|
|
354
|
+
},
|
|
355
|
+
fail(err) {
|
|
356
|
+
if (intervalId) clearInterval(intervalId);
|
|
357
|
+
currentPhase = "";
|
|
358
|
+
const lastIdx = lines.length - 1;
|
|
359
|
+
if (lastIdx >= 0) {
|
|
360
|
+
lines[lastIdx] = ` ${chalk.red("\u2716")} Compilation failed`;
|
|
361
|
+
}
|
|
362
|
+
render();
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
259
366
|
|
|
260
367
|
// src/logo.ts
|
|
261
368
|
import chalk2 from "chalk";
|
|
@@ -450,7 +557,6 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
|
|
|
450
557
|
import { Command as Command2 } from "commander";
|
|
451
558
|
import { input as input2, confirm, select as select2 } from "@inquirer/prompts";
|
|
452
559
|
import chalk5 from "chalk";
|
|
453
|
-
import ora from "ora";
|
|
454
560
|
|
|
455
561
|
// src/compiler/compile.ts
|
|
456
562
|
import fs4 from "fs/promises";
|
|
@@ -1264,7 +1370,7 @@ function buildMcpConfig(skeleton, registry) {
|
|
|
1264
1370
|
}
|
|
1265
1371
|
return config;
|
|
1266
1372
|
}
|
|
1267
|
-
function validateSpec(spec
|
|
1373
|
+
function validateSpec(spec) {
|
|
1268
1374
|
const warnings = [];
|
|
1269
1375
|
if (spec.tools.length > 8) {
|
|
1270
1376
|
warnings.push(`${spec.tools.length} MCP servers selected (recommended: \u22646)`);
|
|
@@ -1278,25 +1384,33 @@ function validateSpec(spec, onProgress) {
|
|
|
1278
1384
|
if (spec.harness.skills && Object.keys(spec.harness.skills).length > 5) {
|
|
1279
1385
|
warnings.push(`${Object.keys(spec.harness.skills).length} skills (recommended: \u22643)`);
|
|
1280
1386
|
}
|
|
1281
|
-
|
|
1282
|
-
onProgress?.(`\u26A0 ${warning}`);
|
|
1283
|
-
}
|
|
1387
|
+
return warnings;
|
|
1284
1388
|
}
|
|
1285
1389
|
async function compile(intent, onProgress) {
|
|
1390
|
+
const startTime = Date.now();
|
|
1286
1391
|
const config = await loadConfig();
|
|
1287
1392
|
if (!config) {
|
|
1288
1393
|
throw new Error("No config found. Run `kairn init` first.");
|
|
1289
1394
|
}
|
|
1290
|
-
onProgress?.("Loading tool registry...");
|
|
1395
|
+
onProgress?.({ phase: "registry", status: "running", message: "Loading tool registry..." });
|
|
1291
1396
|
const registry = await loadRegistry();
|
|
1292
|
-
onProgress?.("
|
|
1397
|
+
onProgress?.({ phase: "registry", status: "success", message: "Tool registry loaded", detail: `${registry.length} tools` });
|
|
1398
|
+
onProgress?.({ phase: "pass1", status: "running", message: "Pass 1: Analyzing workflow & selecting tools..." });
|
|
1293
1399
|
const skeletonMsg = buildSkeletonMessage(intent, registry);
|
|
1294
1400
|
const skeletonText = await callLLM(config, skeletonMsg, {
|
|
1295
1401
|
maxTokens: 2048,
|
|
1296
1402
|
systemPrompt: SKELETON_PROMPT
|
|
1297
1403
|
});
|
|
1298
1404
|
const skeleton = parseSkeletonResponse(skeletonText);
|
|
1299
|
-
|
|
1405
|
+
const toolNames = skeleton.tools.map((t) => t.tool_id).join(", ");
|
|
1406
|
+
onProgress?.({
|
|
1407
|
+
phase: "pass1",
|
|
1408
|
+
status: "success",
|
|
1409
|
+
message: `Pass 1: Selected ${skeleton.tools.length} tools`,
|
|
1410
|
+
detail: toolNames,
|
|
1411
|
+
elapsed: (Date.now() - startTime) / 1e3
|
|
1412
|
+
});
|
|
1413
|
+
onProgress?.({ phase: "pass2", status: "running", message: "Pass 2: Generating CLAUDE.md, commands, agents..." });
|
|
1300
1414
|
const harnessMsg = buildHarnessMessage(intent, skeleton);
|
|
1301
1415
|
let harness;
|
|
1302
1416
|
try {
|
|
@@ -1306,7 +1420,7 @@ async function compile(intent, onProgress) {
|
|
|
1306
1420
|
});
|
|
1307
1421
|
harness = parseHarnessResponse(harnessText);
|
|
1308
1422
|
} catch {
|
|
1309
|
-
onProgress?.("
|
|
1423
|
+
onProgress?.({ phase: "pass2-retry", status: "warning", message: "Pass 2: Response too large, retrying in concise mode..." });
|
|
1310
1424
|
const retryMsg = buildHarnessMessage(intent, skeleton, true);
|
|
1311
1425
|
const retryText = await callLLM(config, retryMsg, {
|
|
1312
1426
|
maxTokens: 8192,
|
|
@@ -1314,9 +1428,19 @@ async function compile(intent, onProgress) {
|
|
|
1314
1428
|
});
|
|
1315
1429
|
harness = parseHarnessResponse(retryText);
|
|
1316
1430
|
}
|
|
1317
|
-
|
|
1431
|
+
const cmdCount = Object.keys(harness.commands).length;
|
|
1432
|
+
const agentCount = Object.keys(harness.agents ?? {}).length;
|
|
1433
|
+
const ruleCount = Object.keys(harness.rules).length;
|
|
1434
|
+
onProgress?.({
|
|
1435
|
+
phase: "pass2",
|
|
1436
|
+
status: "success",
|
|
1437
|
+
message: `Pass 2: Generated ${cmdCount} commands, ${agentCount} agents, ${ruleCount} rules`,
|
|
1438
|
+
elapsed: (Date.now() - startTime) / 1e3
|
|
1439
|
+
});
|
|
1440
|
+
onProgress?.({ phase: "pass3", status: "running", message: "Pass 3: Configuring MCP servers & settings..." });
|
|
1318
1441
|
const settings = buildSettings(skeleton, registry);
|
|
1319
1442
|
const mcpConfig = buildMcpConfig(skeleton, registry);
|
|
1443
|
+
onProgress?.({ phase: "pass3", status: "success", message: "Pass 3: Configured MCP servers & settings" });
|
|
1320
1444
|
const spec = {
|
|
1321
1445
|
id: `env_${crypto.randomUUID()}`,
|
|
1322
1446
|
intent,
|
|
@@ -1336,7 +1460,12 @@ async function compile(intent, onProgress) {
|
|
|
1336
1460
|
docs: harness.docs
|
|
1337
1461
|
}
|
|
1338
1462
|
};
|
|
1339
|
-
validateSpec(spec
|
|
1463
|
+
const warnings = validateSpec(spec);
|
|
1464
|
+
for (const w of warnings) {
|
|
1465
|
+
onProgress?.({ phase: "done", status: "warning", message: `\u26A0 ${w}` });
|
|
1466
|
+
}
|
|
1467
|
+
const totalElapsed = ((Date.now() - startTime) / 1e3).toFixed(0);
|
|
1468
|
+
onProgress?.({ phase: "done", status: "success", message: `Environment compiled in ${totalElapsed}s`, elapsed: (Date.now() - startTime) / 1e3 });
|
|
1340
1469
|
await ensureDirs();
|
|
1341
1470
|
const envPath = path4.join(getEnvsDir(), `${spec.id}.json`);
|
|
1342
1471
|
await fs4.writeFile(envPath, JSON.stringify(spec, null, 2), "utf-8");
|
|
@@ -2112,16 +2241,19 @@ ${clarificationLines}`;
|
|
|
2112
2241
|
Autonomy level: ${autonomyLevel} (${autonomyLabel(autonomyLevel)})`;
|
|
2113
2242
|
}
|
|
2114
2243
|
console.log(ui.section("Compilation"));
|
|
2115
|
-
const
|
|
2244
|
+
const estimate = estimateTime(config.model, finalIntent);
|
|
2245
|
+
console.log(chalk5.dim(` Estimated time: ${estimate} (${config.model})`));
|
|
2246
|
+
console.log("");
|
|
2247
|
+
const renderer = createProgressRenderer();
|
|
2116
2248
|
let spec;
|
|
2117
2249
|
try {
|
|
2118
|
-
spec = await compile(finalIntent, (
|
|
2119
|
-
|
|
2250
|
+
spec = await compile(finalIntent, (progress) => {
|
|
2251
|
+
renderer.update(progress);
|
|
2120
2252
|
});
|
|
2121
2253
|
spec.autonomy_level = autonomyLevel;
|
|
2122
|
-
|
|
2254
|
+
renderer.finish();
|
|
2123
2255
|
} catch (err) {
|
|
2124
|
-
|
|
2256
|
+
renderer.fail(err);
|
|
2125
2257
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2126
2258
|
console.log(chalk5.red(`
|
|
2127
2259
|
${msg}
|
|
@@ -2366,7 +2498,7 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
|
|
|
2366
2498
|
import { Command as Command6 } from "commander";
|
|
2367
2499
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
2368
2500
|
import chalk9 from "chalk";
|
|
2369
|
-
import
|
|
2501
|
+
import ora from "ora";
|
|
2370
2502
|
import fs12 from "fs/promises";
|
|
2371
2503
|
import path12 from "path";
|
|
2372
2504
|
|
|
@@ -2691,7 +2823,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
2691
2823
|
}
|
|
2692
2824
|
const targetDir = process.cwd();
|
|
2693
2825
|
console.log(ui.section("Project Scan"));
|
|
2694
|
-
const scanSpinner =
|
|
2826
|
+
const scanSpinner = ora({ text: "Scanning project...", indent: 2 }).start();
|
|
2695
2827
|
const profile = await scanProject(targetDir);
|
|
2696
2828
|
scanSpinner.stop();
|
|
2697
2829
|
if (profile.language) console.log(ui.kv("Language:", profile.language));
|
|
@@ -2760,10 +2892,10 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
2760
2892
|
}
|
|
2761
2893
|
const intent = buildOptimizeIntent(profile);
|
|
2762
2894
|
let spec;
|
|
2763
|
-
const spinner =
|
|
2895
|
+
const spinner = ora({ text: "Compiling optimized environment...", indent: 2 }).start();
|
|
2764
2896
|
try {
|
|
2765
|
-
spec = await compile(intent, (
|
|
2766
|
-
spinner.text =
|
|
2897
|
+
spec = await compile(intent, (progress) => {
|
|
2898
|
+
spinner.text = progress.message;
|
|
2767
2899
|
});
|
|
2768
2900
|
spinner.succeed("Environment compiled");
|
|
2769
2901
|
} catch (err) {
|