kairn-cli 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +388 -148
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import { Command as
|
|
3
|
-
import
|
|
2
|
+
import { Command as Command11 } from "commander";
|
|
3
|
+
import chalk14 from "chalk";
|
|
4
4
|
|
|
5
5
|
// src/commands/init.ts
|
|
6
6
|
import { Command } from "commander";
|
|
@@ -90,7 +90,7 @@ var ui = {
|
|
|
90
90
|
// Key-value pairs
|
|
91
91
|
kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
|
|
92
92
|
// File list
|
|
93
|
-
file: (
|
|
93
|
+
file: (path15) => chalk.dim(` ${path15}`),
|
|
94
94
|
// Tool display
|
|
95
95
|
tool: (name, reason) => ` ${warm("\u25CF")} ${chalk.bold(name)}
|
|
96
96
|
${chalk.dim(reason)}`,
|
|
@@ -339,7 +339,7 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
|
|
|
339
339
|
// src/commands/describe.ts
|
|
340
340
|
import { Command as Command2 } from "commander";
|
|
341
341
|
import { input, confirm } from "@inquirer/prompts";
|
|
342
|
-
import
|
|
342
|
+
import chalk5 from "chalk";
|
|
343
343
|
import ora from "ora";
|
|
344
344
|
|
|
345
345
|
// src/compiler/compile.ts
|
|
@@ -926,25 +926,39 @@ function isCodeProject(spec) {
|
|
|
926
926
|
const commands = spec.harness.commands ?? {};
|
|
927
927
|
return "status" in commands || "test" in commands;
|
|
928
928
|
}
|
|
929
|
-
|
|
929
|
+
var ENV_LOADER_HOOK = {
|
|
930
|
+
matcher: "",
|
|
931
|
+
hooks: [{
|
|
932
|
+
type: "command",
|
|
933
|
+
command: 'if [ -f .env ] && [ -n "$CLAUDE_ENV_FILE" ]; then grep -v "^#" .env | grep -v "^$" | grep "=" >> "$CLAUDE_ENV_FILE"; fi'
|
|
934
|
+
}]
|
|
935
|
+
};
|
|
936
|
+
function resolveSettings(spec, options) {
|
|
930
937
|
const settings = spec.harness.settings;
|
|
931
|
-
|
|
932
|
-
if ("statusLine" in
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
938
|
+
const base = settings && Object.keys(settings).length > 0 ? { ...settings } : {};
|
|
939
|
+
if (!("statusLine" in base) && isCodeProject(spec)) {
|
|
940
|
+
base.statusLine = STATUS_LINE;
|
|
941
|
+
}
|
|
942
|
+
if (options?.hasEnvVars) {
|
|
943
|
+
const hooks = base.hooks ?? {};
|
|
944
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
945
|
+
sessionStart.push(ENV_LOADER_HOOK);
|
|
946
|
+
hooks.SessionStart = sessionStart;
|
|
947
|
+
base.hooks = hooks;
|
|
948
|
+
}
|
|
949
|
+
if (Object.keys(base).length === 0) return null;
|
|
950
|
+
return base;
|
|
937
951
|
}
|
|
938
952
|
async function writeFile(filePath, content) {
|
|
939
953
|
await fs5.mkdir(path5.dirname(filePath), { recursive: true });
|
|
940
954
|
await fs5.writeFile(filePath, content, "utf-8");
|
|
941
955
|
}
|
|
942
|
-
function buildFileMap(spec) {
|
|
956
|
+
function buildFileMap(spec, options) {
|
|
943
957
|
const files = /* @__PURE__ */ new Map();
|
|
944
958
|
if (spec.harness.claude_md) {
|
|
945
959
|
files.set(".claude/CLAUDE.md", spec.harness.claude_md);
|
|
946
960
|
}
|
|
947
|
-
const resolvedSettings = resolveSettings(spec);
|
|
961
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
948
962
|
if (resolvedSettings) {
|
|
949
963
|
files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
|
|
950
964
|
}
|
|
@@ -981,7 +995,7 @@ function buildFileMap(spec) {
|
|
|
981
995
|
}
|
|
982
996
|
return files;
|
|
983
997
|
}
|
|
984
|
-
async function writeEnvironment(spec, targetDir) {
|
|
998
|
+
async function writeEnvironment(spec, targetDir, options) {
|
|
985
999
|
const claudeDir = path5.join(targetDir, ".claude");
|
|
986
1000
|
const written = [];
|
|
987
1001
|
if (spec.harness.claude_md) {
|
|
@@ -989,7 +1003,7 @@ async function writeEnvironment(spec, targetDir) {
|
|
|
989
1003
|
await writeFile(p, spec.harness.claude_md);
|
|
990
1004
|
written.push(".claude/CLAUDE.md");
|
|
991
1005
|
}
|
|
992
|
-
const resolvedSettings = resolveSettings(spec);
|
|
1006
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
993
1007
|
if (resolvedSettings) {
|
|
994
1008
|
const p = path5.join(claudeDir, "settings.json");
|
|
995
1009
|
await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
|
|
@@ -1192,6 +1206,114 @@ async function writeHermesEnvironment(spec, registry) {
|
|
|
1192
1206
|
return written;
|
|
1193
1207
|
}
|
|
1194
1208
|
|
|
1209
|
+
// src/secrets.ts
|
|
1210
|
+
import { password as password2 } from "@inquirer/prompts";
|
|
1211
|
+
import chalk4 from "chalk";
|
|
1212
|
+
import fs7 from "fs/promises";
|
|
1213
|
+
import path7 from "path";
|
|
1214
|
+
async function collectAndWriteKeys(envSetup, targetDir) {
|
|
1215
|
+
console.log(ui.section("API Keys"));
|
|
1216
|
+
console.log(
|
|
1217
|
+
chalk4.dim(" Some tools need API keys. Enter them now or press Enter to skip.\n")
|
|
1218
|
+
);
|
|
1219
|
+
const envEntries = [
|
|
1220
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1221
|
+
"# Do NOT commit this file to git",
|
|
1222
|
+
""
|
|
1223
|
+
];
|
|
1224
|
+
let keysEntered = 0;
|
|
1225
|
+
let keysSkipped = 0;
|
|
1226
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1227
|
+
for (const env of envSetup) {
|
|
1228
|
+
if (seen.has(env.envVar)) continue;
|
|
1229
|
+
seen.add(env.envVar);
|
|
1230
|
+
console.log(chalk4.bold(` ${env.envVar}`) + chalk4.dim(` (${env.toolName})`));
|
|
1231
|
+
if (env.signupUrl) {
|
|
1232
|
+
console.log(chalk4.dim(` Get one at: ${env.signupUrl}`));
|
|
1233
|
+
}
|
|
1234
|
+
const value = await password2({
|
|
1235
|
+
message: env.envVar,
|
|
1236
|
+
mask: "\u2022"
|
|
1237
|
+
});
|
|
1238
|
+
if (value && value.trim()) {
|
|
1239
|
+
envEntries.push(`${env.envVar}=${value.trim()}`);
|
|
1240
|
+
console.log(chalk4.green(" \u2713 saved\n"));
|
|
1241
|
+
keysEntered++;
|
|
1242
|
+
} else {
|
|
1243
|
+
envEntries.push(`${env.envVar}=`);
|
|
1244
|
+
console.log(chalk4.dim(" (skipped)\n"));
|
|
1245
|
+
keysSkipped++;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1249
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1250
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1251
|
+
console.log(chalk4.green(` \u2713 ${keysEntered} key(s) saved to .env (gitignored)`));
|
|
1252
|
+
if (keysSkipped > 0) {
|
|
1253
|
+
console.log(chalk4.dim(" Skipped keys can be added later: kairn keys"));
|
|
1254
|
+
}
|
|
1255
|
+
return { keysEntered, keysSkipped, envPath };
|
|
1256
|
+
}
|
|
1257
|
+
async function writeEmptyEnvFile(envSetup, targetDir) {
|
|
1258
|
+
const envEntries = [
|
|
1259
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1260
|
+
"# Do NOT commit this file to git",
|
|
1261
|
+
""
|
|
1262
|
+
];
|
|
1263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1264
|
+
for (const env of envSetup) {
|
|
1265
|
+
if (seen.has(env.envVar)) continue;
|
|
1266
|
+
seen.add(env.envVar);
|
|
1267
|
+
envEntries.push(`${env.envVar}=`);
|
|
1268
|
+
}
|
|
1269
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1270
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1271
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1272
|
+
}
|
|
1273
|
+
async function ensureGitignoreEntry(targetDir, entry) {
|
|
1274
|
+
const gitignorePath = path7.join(targetDir, ".gitignore");
|
|
1275
|
+
let gitignore = "";
|
|
1276
|
+
try {
|
|
1277
|
+
gitignore = await fs7.readFile(gitignorePath, "utf-8");
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
if (!gitignore.split("\n").some((line) => line.trim() === entry)) {
|
|
1281
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
1282
|
+
await fs7.writeFile(gitignorePath, gitignore + separator + entry + "\n", "utf-8");
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
async function readEnvFile(targetDir) {
|
|
1286
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1287
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1288
|
+
try {
|
|
1289
|
+
const content = await fs7.readFile(envPath, "utf-8");
|
|
1290
|
+
for (const line of content.split("\n")) {
|
|
1291
|
+
const trimmed = line.trim();
|
|
1292
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1293
|
+
const eqIndex = trimmed.indexOf("=");
|
|
1294
|
+
if (eqIndex === -1) continue;
|
|
1295
|
+
const key = trimmed.slice(0, eqIndex);
|
|
1296
|
+
const value = trimmed.slice(eqIndex + 1);
|
|
1297
|
+
entries.set(key, value);
|
|
1298
|
+
}
|
|
1299
|
+
} catch {
|
|
1300
|
+
}
|
|
1301
|
+
return entries;
|
|
1302
|
+
}
|
|
1303
|
+
async function detectRequiredEnvVars(targetDir) {
|
|
1304
|
+
const mcpPath = path7.join(targetDir, ".mcp.json");
|
|
1305
|
+
const envVars = /* @__PURE__ */ new Set();
|
|
1306
|
+
try {
|
|
1307
|
+
const content = await fs7.readFile(mcpPath, "utf-8");
|
|
1308
|
+
const matches = content.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
|
|
1309
|
+
for (const match of matches) {
|
|
1310
|
+
envVars.add(match[1]);
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1313
|
+
}
|
|
1314
|
+
return [...envVars];
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1195
1317
|
// src/commands/describe.ts
|
|
1196
1318
|
var describeCommand = new Command2("describe").description("Describe your workflow and generate a Claude Code environment").argument("[intent]", "What you want your agent to do").option("-y, --yes", "Skip confirmation prompt").option("-q, --quick", "Skip clarification questions").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (intentArg, options) => {
|
|
1197
1319
|
printFullBanner("The Agent Environment Compiler");
|
|
@@ -1200,7 +1322,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1200
1322
|
console.log(
|
|
1201
1323
|
ui.errorBox(
|
|
1202
1324
|
"No configuration found",
|
|
1203
|
-
`Run ${
|
|
1325
|
+
`Run ${chalk5.bold("kairn init")} to set up your API key.`
|
|
1204
1326
|
)
|
|
1205
1327
|
);
|
|
1206
1328
|
process.exit(1);
|
|
@@ -1209,14 +1331,14 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1209
1331
|
message: "What do you want your agent to do?"
|
|
1210
1332
|
});
|
|
1211
1333
|
if (!intentRaw.trim()) {
|
|
1212
|
-
console.log(
|
|
1334
|
+
console.log(chalk5.red("\n No description provided. Aborting.\n"));
|
|
1213
1335
|
process.exit(1);
|
|
1214
1336
|
}
|
|
1215
1337
|
let finalIntent = intentRaw;
|
|
1216
1338
|
if (!options.quick) {
|
|
1217
1339
|
console.log(ui.section("Clarification"));
|
|
1218
|
-
console.log(
|
|
1219
|
-
console.log(
|
|
1340
|
+
console.log(chalk5.dim(" Let me understand your project better."));
|
|
1341
|
+
console.log(chalk5.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
|
|
1220
1342
|
let clarifications = [];
|
|
1221
1343
|
try {
|
|
1222
1344
|
clarifications = await generateClarifications(intentRaw);
|
|
@@ -1249,7 +1371,7 @@ ${clarificationLines}`;
|
|
|
1249
1371
|
} catch (err) {
|
|
1250
1372
|
spinner.fail("Compilation failed");
|
|
1251
1373
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1252
|
-
console.log(
|
|
1374
|
+
console.log(chalk5.red(`
|
|
1253
1375
|
${msg}
|
|
1254
1376
|
`));
|
|
1255
1377
|
process.exit(1);
|
|
@@ -1279,7 +1401,7 @@ ${clarificationLines}`;
|
|
|
1279
1401
|
default: true
|
|
1280
1402
|
});
|
|
1281
1403
|
if (!proceed) {
|
|
1282
|
-
console.log(
|
|
1404
|
+
console.log(chalk5.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
|
|
1283
1405
|
return;
|
|
1284
1406
|
}
|
|
1285
1407
|
const targetDir = process.cwd();
|
|
@@ -1288,25 +1410,24 @@ ${clarificationLines}`;
|
|
|
1288
1410
|
await writeHermesEnvironment(spec, registry);
|
|
1289
1411
|
console.log("\n" + ui.success("Environment written for Hermes"));
|
|
1290
1412
|
console.log(
|
|
1291
|
-
|
|
1413
|
+
chalk5.cyan("\n Ready! Run ") + chalk5.bold("hermes") + chalk5.cyan(" to start.\n")
|
|
1292
1414
|
);
|
|
1293
1415
|
} else {
|
|
1294
|
-
const
|
|
1416
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1417
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1295
1418
|
console.log(ui.section("Files Written"));
|
|
1296
1419
|
console.log("");
|
|
1297
1420
|
for (const file of written) {
|
|
1298
1421
|
console.log(ui.file(file));
|
|
1299
1422
|
}
|
|
1300
|
-
if (
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
seen.add(env.envVar);
|
|
1307
|
-
console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
|
|
1308
|
-
console.log("");
|
|
1423
|
+
if (hasEnvVars) {
|
|
1424
|
+
if (options.quick) {
|
|
1425
|
+
await writeEmptyEnvFile(summary.envSetup, targetDir);
|
|
1426
|
+
console.log(ui.success("Empty .env written (gitignored) \u2014 fill in keys later: kairn keys"));
|
|
1427
|
+
} else {
|
|
1428
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
1309
1429
|
}
|
|
1430
|
+
console.log("");
|
|
1310
1431
|
}
|
|
1311
1432
|
if (summary.pluginCommands.length > 0) {
|
|
1312
1433
|
console.log(ui.section("Plugins"));
|
|
@@ -1324,28 +1445,28 @@ ${clarificationLines}`;
|
|
|
1324
1445
|
|
|
1325
1446
|
// src/commands/list.ts
|
|
1326
1447
|
import { Command as Command3 } from "commander";
|
|
1327
|
-
import
|
|
1328
|
-
import
|
|
1329
|
-
import
|
|
1448
|
+
import chalk6 from "chalk";
|
|
1449
|
+
import fs8 from "fs/promises";
|
|
1450
|
+
import path8 from "path";
|
|
1330
1451
|
var listCommand = new Command3("list").description("Show saved environments").action(async () => {
|
|
1331
1452
|
printCompactBanner();
|
|
1332
1453
|
const envsDir = getEnvsDir();
|
|
1333
1454
|
let files;
|
|
1334
1455
|
try {
|
|
1335
|
-
files = await
|
|
1456
|
+
files = await fs8.readdir(envsDir);
|
|
1336
1457
|
} catch {
|
|
1337
|
-
console.log(
|
|
1458
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1338
1459
|
return;
|
|
1339
1460
|
}
|
|
1340
1461
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1341
1462
|
if (jsonFiles.length === 0) {
|
|
1342
|
-
console.log(
|
|
1463
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1343
1464
|
return;
|
|
1344
1465
|
}
|
|
1345
1466
|
let first = true;
|
|
1346
1467
|
for (const file of jsonFiles) {
|
|
1347
1468
|
try {
|
|
1348
|
-
const data = await
|
|
1469
|
+
const data = await fs8.readFile(path8.join(envsDir, file), "utf-8");
|
|
1349
1470
|
const spec = JSON.parse(data);
|
|
1350
1471
|
const date = new Date(spec.created_at).toLocaleDateString();
|
|
1351
1472
|
const toolCount = spec.tools?.length ?? 0;
|
|
@@ -1353,10 +1474,10 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1353
1474
|
console.log(ui.divider());
|
|
1354
1475
|
}
|
|
1355
1476
|
first = false;
|
|
1356
|
-
console.log(ui.kv("Name",
|
|
1477
|
+
console.log(ui.kv("Name", chalk6.bold(spec.name)));
|
|
1357
1478
|
console.log(ui.kv("Description", spec.description));
|
|
1358
1479
|
console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
|
|
1359
|
-
console.log(ui.kv("ID",
|
|
1480
|
+
console.log(ui.kv("ID", chalk6.dim(spec.id)));
|
|
1360
1481
|
console.log("");
|
|
1361
1482
|
} catch {
|
|
1362
1483
|
}
|
|
@@ -1365,9 +1486,9 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1365
1486
|
|
|
1366
1487
|
// src/commands/activate.ts
|
|
1367
1488
|
import { Command as Command4 } from "commander";
|
|
1368
|
-
import
|
|
1369
|
-
import
|
|
1370
|
-
import
|
|
1489
|
+
import chalk7 from "chalk";
|
|
1490
|
+
import fs9 from "fs/promises";
|
|
1491
|
+
import path9 from "path";
|
|
1371
1492
|
var activateCommand = new Command4("activate").description("Re-deploy a saved environment to the current directory").argument("<env_id>", "Environment ID (from kairn list)").action(async (envId) => {
|
|
1372
1493
|
printCompactBanner();
|
|
1373
1494
|
const envsDir = getEnvsDir();
|
|
@@ -1377,7 +1498,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1377
1498
|
let fromTemplate = false;
|
|
1378
1499
|
let envFiles = [];
|
|
1379
1500
|
try {
|
|
1380
|
-
envFiles = await
|
|
1501
|
+
envFiles = await fs9.readdir(envsDir);
|
|
1381
1502
|
} catch {
|
|
1382
1503
|
}
|
|
1383
1504
|
match = envFiles.find(
|
|
@@ -1388,7 +1509,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1388
1509
|
} else {
|
|
1389
1510
|
let templateFiles = [];
|
|
1390
1511
|
try {
|
|
1391
|
-
templateFiles = await
|
|
1512
|
+
templateFiles = await fs9.readdir(templatesDir);
|
|
1392
1513
|
} catch {
|
|
1393
1514
|
}
|
|
1394
1515
|
match = templateFiles.find(
|
|
@@ -1399,16 +1520,16 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1399
1520
|
fromTemplate = true;
|
|
1400
1521
|
} else {
|
|
1401
1522
|
console.log(ui.error(`Environment "${envId}" not found.`));
|
|
1402
|
-
console.log(
|
|
1403
|
-
console.log(
|
|
1523
|
+
console.log(chalk7.dim(" Run kairn list to see saved environments."));
|
|
1524
|
+
console.log(chalk7.dim(" Run kairn templates to see available templates.\n"));
|
|
1404
1525
|
process.exit(1);
|
|
1405
1526
|
}
|
|
1406
1527
|
}
|
|
1407
|
-
const data = await
|
|
1528
|
+
const data = await fs9.readFile(path9.join(sourceDir, match), "utf-8");
|
|
1408
1529
|
const spec = JSON.parse(data);
|
|
1409
|
-
const label = fromTemplate ?
|
|
1410
|
-
console.log(
|
|
1411
|
-
console.log(
|
|
1530
|
+
const label = fromTemplate ? chalk7.dim(" (template)") : "";
|
|
1531
|
+
console.log(chalk7.cyan(` Activating: ${spec.name}`) + label);
|
|
1532
|
+
console.log(chalk7.dim(` ${spec.description}
|
|
1412
1533
|
`));
|
|
1413
1534
|
const targetDir = process.cwd();
|
|
1414
1535
|
const written = await writeEnvironment(spec, targetDir);
|
|
@@ -1421,22 +1542,22 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1421
1542
|
|
|
1422
1543
|
// src/commands/update-registry.ts
|
|
1423
1544
|
import { Command as Command5 } from "commander";
|
|
1424
|
-
import
|
|
1425
|
-
import
|
|
1426
|
-
import
|
|
1545
|
+
import chalk8 from "chalk";
|
|
1546
|
+
import fs10 from "fs/promises";
|
|
1547
|
+
import path10 from "path";
|
|
1427
1548
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1428
1549
|
var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
|
|
1429
1550
|
async function getLocalRegistryPath() {
|
|
1430
1551
|
const __filename3 = fileURLToPath3(import.meta.url);
|
|
1431
|
-
const __dirname3 =
|
|
1552
|
+
const __dirname3 = path10.dirname(__filename3);
|
|
1432
1553
|
const candidates = [
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1554
|
+
path10.resolve(__dirname3, "../registry/tools.json"),
|
|
1555
|
+
path10.resolve(__dirname3, "../src/registry/tools.json"),
|
|
1556
|
+
path10.resolve(__dirname3, "../../src/registry/tools.json")
|
|
1436
1557
|
];
|
|
1437
1558
|
for (const candidate of candidates) {
|
|
1438
1559
|
try {
|
|
1439
|
-
await
|
|
1560
|
+
await fs10.access(candidate);
|
|
1440
1561
|
return candidate;
|
|
1441
1562
|
} catch {
|
|
1442
1563
|
continue;
|
|
@@ -1447,15 +1568,15 @@ async function getLocalRegistryPath() {
|
|
|
1447
1568
|
var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
|
|
1448
1569
|
printCompactBanner();
|
|
1449
1570
|
const url = options.url || REGISTRY_URL;
|
|
1450
|
-
console.log(
|
|
1571
|
+
console.log(chalk8.dim(` Fetching registry from ${url}...`));
|
|
1451
1572
|
try {
|
|
1452
1573
|
const response = await fetch(url);
|
|
1453
1574
|
if (!response.ok) {
|
|
1454
1575
|
console.log(
|
|
1455
1576
|
ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
|
|
1456
1577
|
);
|
|
1457
|
-
console.log(
|
|
1458
|
-
console.log(
|
|
1578
|
+
console.log(chalk8.dim(" The remote registry may not be available yet."));
|
|
1579
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1459
1580
|
return;
|
|
1460
1581
|
}
|
|
1461
1582
|
const text = await response.text();
|
|
@@ -1473,35 +1594,35 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
|
|
|
1473
1594
|
const registryPath = await getLocalRegistryPath();
|
|
1474
1595
|
const backupPath = registryPath + ".bak";
|
|
1475
1596
|
try {
|
|
1476
|
-
await
|
|
1597
|
+
await fs10.copyFile(registryPath, backupPath);
|
|
1477
1598
|
} catch {
|
|
1478
1599
|
}
|
|
1479
|
-
await
|
|
1600
|
+
await fs10.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
|
|
1480
1601
|
console.log(ui.success(`Registry updated: ${tools.length} tools`));
|
|
1481
|
-
console.log(
|
|
1482
|
-
console.log(
|
|
1602
|
+
console.log(chalk8.dim(` Saved to: ${registryPath}`));
|
|
1603
|
+
console.log(chalk8.dim(` Backup: ${backupPath}
|
|
1483
1604
|
`));
|
|
1484
1605
|
} catch (err) {
|
|
1485
1606
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1486
1607
|
console.log(ui.error(`Network error: ${msg}`));
|
|
1487
|
-
console.log(
|
|
1608
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1488
1609
|
}
|
|
1489
1610
|
});
|
|
1490
1611
|
|
|
1491
1612
|
// src/commands/optimize.ts
|
|
1492
1613
|
import { Command as Command6 } from "commander";
|
|
1493
1614
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1494
|
-
import
|
|
1615
|
+
import chalk9 from "chalk";
|
|
1495
1616
|
import ora2 from "ora";
|
|
1496
|
-
import
|
|
1497
|
-
import
|
|
1617
|
+
import fs12 from "fs/promises";
|
|
1618
|
+
import path12 from "path";
|
|
1498
1619
|
|
|
1499
1620
|
// src/scanner/scan.ts
|
|
1500
|
-
import
|
|
1501
|
-
import
|
|
1621
|
+
import fs11 from "fs/promises";
|
|
1622
|
+
import path11 from "path";
|
|
1502
1623
|
async function fileExists(p) {
|
|
1503
1624
|
try {
|
|
1504
|
-
await
|
|
1625
|
+
await fs11.access(p);
|
|
1505
1626
|
return true;
|
|
1506
1627
|
} catch {
|
|
1507
1628
|
return false;
|
|
@@ -1509,7 +1630,7 @@ async function fileExists(p) {
|
|
|
1509
1630
|
}
|
|
1510
1631
|
async function readJsonSafe(p) {
|
|
1511
1632
|
try {
|
|
1512
|
-
const data = await
|
|
1633
|
+
const data = await fs11.readFile(p, "utf-8");
|
|
1513
1634
|
return JSON.parse(data);
|
|
1514
1635
|
} catch {
|
|
1515
1636
|
return null;
|
|
@@ -1517,14 +1638,14 @@ async function readJsonSafe(p) {
|
|
|
1517
1638
|
}
|
|
1518
1639
|
async function readFileSafe(p) {
|
|
1519
1640
|
try {
|
|
1520
|
-
return await
|
|
1641
|
+
return await fs11.readFile(p, "utf-8");
|
|
1521
1642
|
} catch {
|
|
1522
1643
|
return null;
|
|
1523
1644
|
}
|
|
1524
1645
|
}
|
|
1525
1646
|
async function listDirSafe(p) {
|
|
1526
1647
|
try {
|
|
1527
|
-
const entries = await
|
|
1648
|
+
const entries = await fs11.readdir(p);
|
|
1528
1649
|
return entries.filter((e) => !e.startsWith("."));
|
|
1529
1650
|
} catch {
|
|
1530
1651
|
return [];
|
|
@@ -1576,7 +1697,7 @@ function extractEnvKeys(content) {
|
|
|
1576
1697
|
return keys;
|
|
1577
1698
|
}
|
|
1578
1699
|
async function scanProject(dir) {
|
|
1579
|
-
const pkg = await readJsonSafe(
|
|
1700
|
+
const pkg = await readJsonSafe(path11.join(dir, "package.json"));
|
|
1580
1701
|
const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
|
|
1581
1702
|
const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
|
|
1582
1703
|
const allDeps = [...deps, ...devDeps];
|
|
@@ -1604,19 +1725,19 @@ async function scanProject(dir) {
|
|
|
1604
1725
|
const framework = detectFramework(allDeps);
|
|
1605
1726
|
const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
|
|
1606
1727
|
const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
|
|
1607
|
-
const hasTests = testCommand !== null || await fileExists(
|
|
1728
|
+
const hasTests = testCommand !== null || await fileExists(path11.join(dir, "tests")) || await fileExists(path11.join(dir, "__tests__")) || await fileExists(path11.join(dir, "test"));
|
|
1608
1729
|
const buildCommand = scripts.build || null;
|
|
1609
1730
|
const lintCommand = scripts.lint || null;
|
|
1610
|
-
const hasSrc = await fileExists(
|
|
1611
|
-
const hasDocker = await fileExists(
|
|
1612
|
-
const hasCi = await fileExists(
|
|
1613
|
-
const hasEnvFile = await fileExists(
|
|
1731
|
+
const hasSrc = await fileExists(path11.join(dir, "src"));
|
|
1732
|
+
const hasDocker = await fileExists(path11.join(dir, "docker-compose.yml")) || await fileExists(path11.join(dir, "Dockerfile"));
|
|
1733
|
+
const hasCi = await fileExists(path11.join(dir, ".github/workflows"));
|
|
1734
|
+
const hasEnvFile = await fileExists(path11.join(dir, ".env")) || await fileExists(path11.join(dir, ".env.example"));
|
|
1614
1735
|
let envKeys = [];
|
|
1615
|
-
const envExample = await readFileSafe(
|
|
1736
|
+
const envExample = await readFileSafe(path11.join(dir, ".env.example"));
|
|
1616
1737
|
if (envExample) {
|
|
1617
1738
|
envKeys = extractEnvKeys(envExample);
|
|
1618
1739
|
}
|
|
1619
|
-
const claudeDir =
|
|
1740
|
+
const claudeDir = path11.join(dir, ".claude");
|
|
1620
1741
|
const hasClaudeDir = await fileExists(claudeDir);
|
|
1621
1742
|
let existingClaudeMd = null;
|
|
1622
1743
|
let existingSettings = null;
|
|
@@ -1628,21 +1749,21 @@ async function scanProject(dir) {
|
|
|
1628
1749
|
let mcpServerCount = 0;
|
|
1629
1750
|
let claudeMdLineCount = 0;
|
|
1630
1751
|
if (hasClaudeDir) {
|
|
1631
|
-
existingClaudeMd = await readFileSafe(
|
|
1752
|
+
existingClaudeMd = await readFileSafe(path11.join(claudeDir, "CLAUDE.md"));
|
|
1632
1753
|
if (existingClaudeMd) {
|
|
1633
1754
|
claudeMdLineCount = existingClaudeMd.split("\n").length;
|
|
1634
1755
|
}
|
|
1635
|
-
existingSettings = await readJsonSafe(
|
|
1636
|
-
existingMcpConfig = await readJsonSafe(
|
|
1756
|
+
existingSettings = await readJsonSafe(path11.join(claudeDir, "settings.json"));
|
|
1757
|
+
existingMcpConfig = await readJsonSafe(path11.join(dir, ".mcp.json"));
|
|
1637
1758
|
if (existingMcpConfig?.mcpServers) {
|
|
1638
1759
|
mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
|
|
1639
1760
|
}
|
|
1640
|
-
existingCommands = (await listDirSafe(
|
|
1641
|
-
existingRules = (await listDirSafe(
|
|
1642
|
-
existingSkills = await listDirSafe(
|
|
1643
|
-
existingAgents = (await listDirSafe(
|
|
1761
|
+
existingCommands = (await listDirSafe(path11.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1762
|
+
existingRules = (await listDirSafe(path11.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1763
|
+
existingSkills = await listDirSafe(path11.join(claudeDir, "skills"));
|
|
1764
|
+
existingAgents = (await listDirSafe(path11.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1644
1765
|
}
|
|
1645
|
-
const name = pkg?.name ||
|
|
1766
|
+
const name = pkg?.name || path11.basename(dir);
|
|
1646
1767
|
const description = pkg?.description || "";
|
|
1647
1768
|
return {
|
|
1648
1769
|
name,
|
|
@@ -1687,31 +1808,31 @@ function simpleDiff(oldContent, newContent) {
|
|
|
1687
1808
|
const oldLine = oldLines[i];
|
|
1688
1809
|
const newLine = newLines[i];
|
|
1689
1810
|
if (oldLine === void 0) {
|
|
1690
|
-
output.push(
|
|
1811
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1691
1812
|
} else if (newLine === void 0) {
|
|
1692
|
-
output.push(
|
|
1813
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
1693
1814
|
} else if (oldLine !== newLine) {
|
|
1694
|
-
output.push(
|
|
1695
|
-
output.push(
|
|
1815
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
1816
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1696
1817
|
}
|
|
1697
1818
|
}
|
|
1698
1819
|
return output;
|
|
1699
1820
|
}
|
|
1700
|
-
async function generateDiff(spec, targetDir) {
|
|
1701
|
-
const fileMap = buildFileMap(spec);
|
|
1821
|
+
async function generateDiff(spec, targetDir, options) {
|
|
1822
|
+
const fileMap = buildFileMap(spec, options);
|
|
1702
1823
|
const results = [];
|
|
1703
1824
|
for (const [relativePath, newContent] of fileMap) {
|
|
1704
|
-
const absolutePath =
|
|
1825
|
+
const absolutePath = path12.join(targetDir, relativePath);
|
|
1705
1826
|
let oldContent = null;
|
|
1706
1827
|
try {
|
|
1707
|
-
oldContent = await
|
|
1828
|
+
oldContent = await fs12.readFile(absolutePath, "utf-8");
|
|
1708
1829
|
} catch {
|
|
1709
1830
|
}
|
|
1710
1831
|
if (oldContent === null) {
|
|
1711
1832
|
results.push({
|
|
1712
1833
|
path: relativePath,
|
|
1713
1834
|
status: "new",
|
|
1714
|
-
diff:
|
|
1835
|
+
diff: chalk9.green("+ NEW FILE")
|
|
1715
1836
|
});
|
|
1716
1837
|
} else if (oldContent === newContent) {
|
|
1717
1838
|
results.push({
|
|
@@ -1857,7 +1978,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1857
1978
|
console.log(ui.success("No obvious issues found"));
|
|
1858
1979
|
}
|
|
1859
1980
|
if (options.auditOnly) {
|
|
1860
|
-
console.log(
|
|
1981
|
+
console.log(chalk9.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
|
|
1861
1982
|
return;
|
|
1862
1983
|
}
|
|
1863
1984
|
if (!options.yes) {
|
|
@@ -1867,19 +1988,19 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1867
1988
|
default: false
|
|
1868
1989
|
});
|
|
1869
1990
|
if (!proceed) {
|
|
1870
|
-
console.log(
|
|
1991
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1871
1992
|
return;
|
|
1872
1993
|
}
|
|
1873
1994
|
}
|
|
1874
1995
|
} else {
|
|
1875
|
-
console.log(
|
|
1996
|
+
console.log(chalk9.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
|
|
1876
1997
|
if (!options.yes) {
|
|
1877
1998
|
const proceed = await confirm2({
|
|
1878
1999
|
message: "Generate Claude Code environment for this project?",
|
|
1879
2000
|
default: true
|
|
1880
2001
|
});
|
|
1881
2002
|
if (!proceed) {
|
|
1882
|
-
console.log(
|
|
2003
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1883
2004
|
return;
|
|
1884
2005
|
}
|
|
1885
2006
|
}
|
|
@@ -1915,8 +2036,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1915
2036
|
console.log(ui.tool(name, tool.reason));
|
|
1916
2037
|
}
|
|
1917
2038
|
}
|
|
2039
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1918
2040
|
if (options.diff) {
|
|
1919
|
-
const diffs = await generateDiff(spec, targetDir);
|
|
2041
|
+
const diffs = await generateDiff(spec, targetDir, { hasEnvVars });
|
|
1920
2042
|
const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
|
|
1921
2043
|
if (changedDiffs.length === 0) {
|
|
1922
2044
|
console.log(ui.success("No changes needed \u2014 environment is already up to date."));
|
|
@@ -1925,7 +2047,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1925
2047
|
}
|
|
1926
2048
|
console.log(ui.section("Changes Preview"));
|
|
1927
2049
|
for (const d of changedDiffs) {
|
|
1928
|
-
console.log(
|
|
2050
|
+
console.log(chalk9.cyan(`
|
|
1929
2051
|
--- ${d.path}`));
|
|
1930
2052
|
if (d.status === "new") {
|
|
1931
2053
|
console.log(` ${d.diff}`);
|
|
@@ -1941,7 +2063,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1941
2063
|
default: true
|
|
1942
2064
|
});
|
|
1943
2065
|
if (!apply) {
|
|
1944
|
-
console.log(
|
|
2066
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1945
2067
|
return;
|
|
1946
2068
|
}
|
|
1947
2069
|
}
|
|
@@ -1952,20 +2074,14 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1952
2074
|
console.log(ui.success(`Ready! Run: $ hermes`));
|
|
1953
2075
|
console.log("");
|
|
1954
2076
|
} else {
|
|
1955
|
-
const written = await writeEnvironment(spec, targetDir);
|
|
2077
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1956
2078
|
console.log(ui.section("Files Written"));
|
|
1957
2079
|
for (const file of written) {
|
|
1958
2080
|
console.log(ui.file(file));
|
|
1959
2081
|
}
|
|
1960
|
-
if (
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
for (const env of summary.envSetup) {
|
|
1964
|
-
if (seen.has(env.envVar)) continue;
|
|
1965
|
-
seen.add(env.envVar);
|
|
1966
|
-
console.log(ui.envVar(env.envVar, env.description, env.signupUrl));
|
|
1967
|
-
console.log("");
|
|
1968
|
-
}
|
|
2082
|
+
if (hasEnvVars) {
|
|
2083
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
2084
|
+
console.log("");
|
|
1969
2085
|
}
|
|
1970
2086
|
if (summary.pluginCommands.length > 0) {
|
|
1971
2087
|
console.log(ui.section("Plugins"));
|
|
@@ -1982,7 +2098,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1982
2098
|
|
|
1983
2099
|
// src/commands/doctor.ts
|
|
1984
2100
|
import { Command as Command7 } from "commander";
|
|
1985
|
-
import
|
|
2101
|
+
import chalk10 from "chalk";
|
|
1986
2102
|
function runChecks(profile) {
|
|
1987
2103
|
const checks = [];
|
|
1988
2104
|
if (!profile.existingClaudeMd) {
|
|
@@ -2114,12 +2230,12 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2114
2230
|
).action(async () => {
|
|
2115
2231
|
printFullBanner("Doctor");
|
|
2116
2232
|
const targetDir = process.cwd();
|
|
2117
|
-
console.log(
|
|
2233
|
+
console.log(chalk10.dim(" Checking .claude/ environment...\n"));
|
|
2118
2234
|
const profile = await scanProject(targetDir);
|
|
2119
2235
|
if (!profile.hasClaudeDir) {
|
|
2120
2236
|
console.log(ui.error("No .claude/ directory found.\n"));
|
|
2121
2237
|
console.log(
|
|
2122
|
-
|
|
2238
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn describe") + chalk10.dim(" or ") + chalk10.bold("kairn optimize") + chalk10.dim(" to generate one.\n")
|
|
2123
2239
|
);
|
|
2124
2240
|
process.exit(1);
|
|
2125
2241
|
}
|
|
@@ -2142,7 +2258,7 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2142
2258
|
return sum;
|
|
2143
2259
|
}, 0);
|
|
2144
2260
|
const percentage = Math.round(score / maxScore * 100);
|
|
2145
|
-
const scoreColor = percentage >= 80 ?
|
|
2261
|
+
const scoreColor = percentage >= 80 ? chalk10.green : percentage >= 50 ? chalk10.yellow : chalk10.red;
|
|
2146
2262
|
console.log(
|
|
2147
2263
|
`
|
|
2148
2264
|
Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
|
|
@@ -2150,14 +2266,14 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2150
2266
|
);
|
|
2151
2267
|
if (percentage < 80) {
|
|
2152
2268
|
console.log(
|
|
2153
|
-
|
|
2269
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn optimize") + chalk10.dim(" to fix issues.\n")
|
|
2154
2270
|
);
|
|
2155
2271
|
}
|
|
2156
2272
|
});
|
|
2157
2273
|
|
|
2158
2274
|
// src/commands/registry.ts
|
|
2159
2275
|
import { Command as Command8 } from "commander";
|
|
2160
|
-
import
|
|
2276
|
+
import chalk11 from "chalk";
|
|
2161
2277
|
import { input as input2, select as select2 } from "@inquirer/prompts";
|
|
2162
2278
|
var listCommand2 = new Command8("list").description("List tools in the registry").option("--category <cat>", "Filter by category").option("--user-only", "Show only user-defined tools").action(async (options) => {
|
|
2163
2279
|
printCompactBanner();
|
|
@@ -2182,7 +2298,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2182
2298
|
);
|
|
2183
2299
|
}
|
|
2184
2300
|
if (tools.length === 0) {
|
|
2185
|
-
console.log(
|
|
2301
|
+
console.log(chalk11.dim("\n No tools found.\n"));
|
|
2186
2302
|
return;
|
|
2187
2303
|
}
|
|
2188
2304
|
const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
|
|
@@ -2196,13 +2312,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2196
2312
|
`tier ${tool.tier}`,
|
|
2197
2313
|
tool.auth
|
|
2198
2314
|
].join(", ");
|
|
2199
|
-
console.log(` ${ui.accent(tool.id)}` +
|
|
2200
|
-
console.log(
|
|
2315
|
+
console.log(` ${ui.accent(tool.id)}` + chalk11.dim(` (${meta})`));
|
|
2316
|
+
console.log(chalk11.dim(` ${tool.description}`));
|
|
2201
2317
|
if (tool.best_for.length > 0) {
|
|
2202
|
-
console.log(
|
|
2318
|
+
console.log(chalk11.dim(` Best for: ${tool.best_for.join(", ")}`));
|
|
2203
2319
|
}
|
|
2204
2320
|
if (isUser) {
|
|
2205
|
-
console.log(
|
|
2321
|
+
console.log(chalk11.yellow(" [USER-DEFINED]"));
|
|
2206
2322
|
}
|
|
2207
2323
|
console.log("");
|
|
2208
2324
|
}
|
|
@@ -2210,7 +2326,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2210
2326
|
const shownUser = tools.filter((t) => userIds.has(t.id)).length;
|
|
2211
2327
|
const shownBundled = totalShown - shownUser;
|
|
2212
2328
|
console.log(
|
|
2213
|
-
|
|
2329
|
+
chalk11.dim(
|
|
2214
2330
|
` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
|
|
2215
2331
|
) + "\n"
|
|
2216
2332
|
);
|
|
@@ -2334,20 +2450,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
|
|
|
2334
2450
|
|
|
2335
2451
|
// src/commands/templates.ts
|
|
2336
2452
|
import { Command as Command9 } from "commander";
|
|
2337
|
-
import
|
|
2338
|
-
import
|
|
2339
|
-
import
|
|
2453
|
+
import chalk12 from "chalk";
|
|
2454
|
+
import fs13 from "fs/promises";
|
|
2455
|
+
import path13 from "path";
|
|
2340
2456
|
var templatesCommand = new Command9("templates").description("Browse available templates").option("--category <cat>", "filter templates by category keyword").option("--json", "output raw JSON array").action(async (options) => {
|
|
2341
2457
|
printCompactBanner();
|
|
2342
2458
|
const templatesDir = getTemplatesDir();
|
|
2343
2459
|
let files;
|
|
2344
2460
|
try {
|
|
2345
|
-
files = await
|
|
2461
|
+
files = await fs13.readdir(templatesDir);
|
|
2346
2462
|
} catch {
|
|
2347
2463
|
console.log(
|
|
2348
|
-
|
|
2464
|
+
chalk12.dim(
|
|
2349
2465
|
" No templates found. Templates will be installed with "
|
|
2350
|
-
) +
|
|
2466
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2351
2467
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2352
2468
|
)
|
|
2353
2469
|
);
|
|
@@ -2356,9 +2472,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2356
2472
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
2357
2473
|
if (jsonFiles.length === 0) {
|
|
2358
2474
|
console.log(
|
|
2359
|
-
|
|
2475
|
+
chalk12.dim(
|
|
2360
2476
|
" No templates found. Templates will be installed with "
|
|
2361
|
-
) +
|
|
2477
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2362
2478
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2363
2479
|
)
|
|
2364
2480
|
);
|
|
@@ -2367,8 +2483,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2367
2483
|
const templates = [];
|
|
2368
2484
|
for (const file of jsonFiles) {
|
|
2369
2485
|
try {
|
|
2370
|
-
const data = await
|
|
2371
|
-
|
|
2486
|
+
const data = await fs13.readFile(
|
|
2487
|
+
path13.join(templatesDir, file),
|
|
2372
2488
|
"utf-8"
|
|
2373
2489
|
);
|
|
2374
2490
|
const spec = JSON.parse(data);
|
|
@@ -2386,7 +2502,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2386
2502
|
}
|
|
2387
2503
|
if (filtered.length === 0) {
|
|
2388
2504
|
console.log(
|
|
2389
|
-
|
|
2505
|
+
chalk12.dim(` No templates matched category "${options.category}".
|
|
2390
2506
|
`)
|
|
2391
2507
|
);
|
|
2392
2508
|
return;
|
|
@@ -2397,8 +2513,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2397
2513
|
const toolCount = spec.tools?.length ?? 0;
|
|
2398
2514
|
const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
|
|
2399
2515
|
const ruleCount = Object.keys(spec.harness?.rules ?? {}).length;
|
|
2400
|
-
console.log(ui.kv("Name",
|
|
2401
|
-
console.log(ui.kv("ID",
|
|
2516
|
+
console.log(ui.kv("Name", chalk12.bold(spec.name)));
|
|
2517
|
+
console.log(ui.kv("ID", chalk12.dim(spec.id)));
|
|
2402
2518
|
console.log(ui.kv("Description", spec.description));
|
|
2403
2519
|
console.log(
|
|
2404
2520
|
ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
|
|
@@ -2406,16 +2522,139 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2406
2522
|
console.log("");
|
|
2407
2523
|
}
|
|
2408
2524
|
console.log(
|
|
2409
|
-
|
|
2525
|
+
chalk12.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
|
|
2410
2526
|
`)
|
|
2411
2527
|
);
|
|
2412
2528
|
});
|
|
2413
2529
|
|
|
2530
|
+
// src/commands/keys.ts
|
|
2531
|
+
import { Command as Command10 } from "commander";
|
|
2532
|
+
import { password as password3 } from "@inquirer/prompts";
|
|
2533
|
+
import chalk13 from "chalk";
|
|
2534
|
+
import fs14 from "fs/promises";
|
|
2535
|
+
import path14 from "path";
|
|
2536
|
+
var keysCommand = new Command10("keys").description("Add or update API keys for the current environment").option("--show", "Show which keys are set vs missing").action(async (options) => {
|
|
2537
|
+
printCompactBanner();
|
|
2538
|
+
const targetDir = process.cwd();
|
|
2539
|
+
const requiredVars = await detectRequiredEnvVars(targetDir);
|
|
2540
|
+
if (requiredVars.length === 0) {
|
|
2541
|
+
console.log(
|
|
2542
|
+
ui.info("No MCP servers found in .mcp.json \u2014 no API keys needed.")
|
|
2543
|
+
);
|
|
2544
|
+
console.log("");
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const existing = await readEnvFile(targetDir);
|
|
2548
|
+
const registry = await loadRegistry();
|
|
2549
|
+
const envSetupMap = /* @__PURE__ */ new Map();
|
|
2550
|
+
for (const tool of registry) {
|
|
2551
|
+
if (!tool.env_vars) continue;
|
|
2552
|
+
for (const ev of tool.env_vars) {
|
|
2553
|
+
if (requiredVars.includes(ev.name)) {
|
|
2554
|
+
envSetupMap.set(ev.name, {
|
|
2555
|
+
toolName: tool.name,
|
|
2556
|
+
envVar: ev.name,
|
|
2557
|
+
description: ev.description,
|
|
2558
|
+
signupUrl: tool.signup_url
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
for (const varName of requiredVars) {
|
|
2564
|
+
if (!envSetupMap.has(varName)) {
|
|
2565
|
+
envSetupMap.set(varName, {
|
|
2566
|
+
toolName: "unknown",
|
|
2567
|
+
envVar: varName,
|
|
2568
|
+
description: "Required by MCP server"
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (options.show) {
|
|
2573
|
+
console.log(ui.section("API Key Status"));
|
|
2574
|
+
console.log("");
|
|
2575
|
+
for (const varName of requiredVars) {
|
|
2576
|
+
const value = existing.get(varName);
|
|
2577
|
+
const info = envSetupMap.get(varName);
|
|
2578
|
+
const toolLabel = info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "";
|
|
2579
|
+
if (value && value.length > 0) {
|
|
2580
|
+
const masked = value.slice(0, 4) + "\u2022".repeat(Math.max(0, value.length - 4));
|
|
2581
|
+
console.log(chalk13.green(` \u2713 ${varName}`) + toolLabel + chalk13.dim(` = ${masked}`));
|
|
2582
|
+
} else {
|
|
2583
|
+
console.log(chalk13.yellow(` \u2717 ${varName}`) + toolLabel + chalk13.dim(" = (not set)"));
|
|
2584
|
+
if (info?.signupUrl) {
|
|
2585
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
const setCount = requiredVars.filter((v) => {
|
|
2590
|
+
const val = existing.get(v);
|
|
2591
|
+
return val && val.length > 0;
|
|
2592
|
+
}).length;
|
|
2593
|
+
const missingCount = requiredVars.length - setCount;
|
|
2594
|
+
console.log("");
|
|
2595
|
+
if (missingCount === 0) {
|
|
2596
|
+
console.log(ui.success(`All ${setCount} key(s) configured`));
|
|
2597
|
+
} else {
|
|
2598
|
+
console.log(
|
|
2599
|
+
ui.warn(`${missingCount} key(s) missing \u2014 run ${chalk13.bold("kairn keys")} to add them`)
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
console.log("");
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const missing = requiredVars.filter((v) => {
|
|
2606
|
+
const val = existing.get(v);
|
|
2607
|
+
return !val || val.length === 0;
|
|
2608
|
+
});
|
|
2609
|
+
if (missing.length === 0) {
|
|
2610
|
+
console.log(ui.success("All API keys are already configured."));
|
|
2611
|
+
console.log(chalk13.dim(" Use --show to see current keys.\n"));
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
console.log(ui.section("API Keys"));
|
|
2615
|
+
console.log(chalk13.dim(` ${missing.length} key(s) need to be set. Press Enter to skip.
|
|
2616
|
+
`));
|
|
2617
|
+
const envEntries = new Map(existing);
|
|
2618
|
+
let keysEntered = 0;
|
|
2619
|
+
for (const varName of missing) {
|
|
2620
|
+
const info = envSetupMap.get(varName);
|
|
2621
|
+
console.log(
|
|
2622
|
+
chalk13.bold(` ${varName}`) + (info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "")
|
|
2623
|
+
);
|
|
2624
|
+
if (info?.signupUrl) {
|
|
2625
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2626
|
+
}
|
|
2627
|
+
const value = await password3({
|
|
2628
|
+
message: varName,
|
|
2629
|
+
mask: "\u2022"
|
|
2630
|
+
});
|
|
2631
|
+
if (value && value.trim()) {
|
|
2632
|
+
envEntries.set(varName, value.trim());
|
|
2633
|
+
console.log(chalk13.green(" \u2713 saved\n"));
|
|
2634
|
+
keysEntered++;
|
|
2635
|
+
} else {
|
|
2636
|
+
console.log(chalk13.dim(" (skipped)\n"));
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
const envLines = [
|
|
2640
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
2641
|
+
"# Do NOT commit this file to git",
|
|
2642
|
+
""
|
|
2643
|
+
];
|
|
2644
|
+
for (const [key, value] of envEntries) {
|
|
2645
|
+
envLines.push(`${key}=${value}`);
|
|
2646
|
+
}
|
|
2647
|
+
const envPath = path14.join(targetDir, ".env");
|
|
2648
|
+
await fs14.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
|
|
2649
|
+
console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
|
|
2650
|
+
console.log("");
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2414
2653
|
// src/cli.ts
|
|
2415
|
-
var program = new
|
|
2654
|
+
var program = new Command11();
|
|
2416
2655
|
program.name("kairn").description(
|
|
2417
2656
|
"Compile natural language intent into optimized Claude Code environments"
|
|
2418
|
-
).version("1.
|
|
2657
|
+
).version("1.8.0").option("--no-color", "Disable colored output");
|
|
2419
2658
|
program.addCommand(initCommand);
|
|
2420
2659
|
program.addCommand(describeCommand);
|
|
2421
2660
|
program.addCommand(optimizeCommand);
|
|
@@ -2425,8 +2664,9 @@ program.addCommand(updateRegistryCommand);
|
|
|
2425
2664
|
program.addCommand(doctorCommand);
|
|
2426
2665
|
program.addCommand(registryCommand);
|
|
2427
2666
|
program.addCommand(templatesCommand);
|
|
2667
|
+
program.addCommand(keysCommand);
|
|
2428
2668
|
if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
|
|
2429
|
-
|
|
2669
|
+
chalk14.level = 0;
|
|
2430
2670
|
}
|
|
2431
2671
|
program.parse();
|
|
2432
2672
|
//# sourceMappingURL=cli.js.map
|