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