kairn-cli 1.7.0 → 1.9.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 +715 -156
- 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)}`,
|
|
@@ -338,8 +338,8 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
|
|
|
338
338
|
|
|
339
339
|
// src/commands/describe.ts
|
|
340
340
|
import { Command as Command2 } from "commander";
|
|
341
|
-
import { input, confirm } from "@inquirer/prompts";
|
|
342
|
-
import
|
|
341
|
+
import { input, confirm, select as select2 } from "@inquirer/prompts";
|
|
342
|
+
import chalk5 from "chalk";
|
|
343
343
|
import ora from "ora";
|
|
344
344
|
|
|
345
345
|
// src/compiler/compile.ts
|
|
@@ -614,6 +614,19 @@ When generating for Hermes runtime, the same EnvironmentSpec JSON is produced. T
|
|
|
614
614
|
|
|
615
615
|
The LLM output format does not change. Adapter-level conversion happens post-compilation.
|
|
616
616
|
|
|
617
|
+
## Autonomy Levels
|
|
618
|
+
|
|
619
|
+
The user may specify an autonomy level (1-4). This affects CLAUDE.md content:
|
|
620
|
+
|
|
621
|
+
- **Level 1 (Guided):** Add a "Workflow" section showing recommended command flow (e.g., spec \u2192 sprint \u2192 plan \u2192 code \u2192 prove \u2192 grill \u2192 commit) and a "When to Use What" reference table.
|
|
622
|
+
- **Level 2 (Assisted):** Level 1 content + mention /project:loop in the workflow section and @pm in the agents section of CLAUDE.md.
|
|
623
|
+
- **Level 3 (Autonomous):** Level 2 content + mention /project:auto and worktree-based PR delivery workflow.
|
|
624
|
+
- **Level 4 (Full Auto):** Level 3 content + add a prominent warning section about autonomous operation.
|
|
625
|
+
|
|
626
|
+
The autonomy-specific commands, agents, and hooks are injected post-compilation. Focus on tailoring the CLAUDE.md content and workflow guidance for the selected level.
|
|
627
|
+
|
|
628
|
+
If no autonomy level is specified, assume Level 1 (Guided).
|
|
629
|
+
|
|
617
630
|
## Output Schema
|
|
618
631
|
|
|
619
632
|
Return ONLY valid JSON matching this structure:
|
|
@@ -884,7 +897,8 @@ async function compile(intent, onProgress) {
|
|
|
884
897
|
id: `env_${crypto.randomUUID()}`,
|
|
885
898
|
intent,
|
|
886
899
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
887
|
-
...parsed
|
|
900
|
+
...parsed,
|
|
901
|
+
autonomy_level: parsed.autonomy_level ?? 1
|
|
888
902
|
};
|
|
889
903
|
validateSpec(spec, onProgress);
|
|
890
904
|
await ensureDirs();
|
|
@@ -919,6 +933,290 @@ async function generateClarifications(intent, onProgress) {
|
|
|
919
933
|
// src/adapter/claude-code.ts
|
|
920
934
|
import fs5 from "fs/promises";
|
|
921
935
|
import path5 from "path";
|
|
936
|
+
|
|
937
|
+
// src/autonomy.ts
|
|
938
|
+
var AUTONOMY_LABELS = {
|
|
939
|
+
1: "Guided",
|
|
940
|
+
2: "Assisted",
|
|
941
|
+
3: "Autonomous",
|
|
942
|
+
4: "Full Auto"
|
|
943
|
+
};
|
|
944
|
+
var WELCOME_HOOK = {
|
|
945
|
+
matcher: "",
|
|
946
|
+
hooks: [{
|
|
947
|
+
type: "command",
|
|
948
|
+
command: "if [ ! -f .claude/.toured ]; then echo '\\n Welcome to your Kairn environment!\\n Type /project:tour for a walkthrough, or /project:help for a quick reference.\\n'; fi"
|
|
949
|
+
}]
|
|
950
|
+
};
|
|
951
|
+
var TOUR_COMMAND = `# Environment Tour
|
|
952
|
+
|
|
953
|
+
Welcome! Let me show you around this Kairn environment.
|
|
954
|
+
|
|
955
|
+
## Your Commands
|
|
956
|
+
Read .claude/commands/ and list each one with a one-line description.
|
|
957
|
+
Group them by workflow phase:
|
|
958
|
+
|
|
959
|
+
PLAN: /project:spec, /project:sprint, /project:plan
|
|
960
|
+
BUILD: (just start coding \u2014 Claude reads CLAUDE.md automatically)
|
|
961
|
+
VERIFY: /project:prove, /project:grill, /project:test
|
|
962
|
+
SHIP: /project:commit, /project:review
|
|
963
|
+
MANAGE: /project:status, /project:tasks, /project:reset
|
|
964
|
+
|
|
965
|
+
## Your Agents
|
|
966
|
+
Read .claude/agents/ and explain each one with how to invoke it.
|
|
967
|
+
|
|
968
|
+
## Your MCP Tools
|
|
969
|
+
!claude mcp list 2>/dev/null || echo "Run /mcp in Claude Code to see active tools"
|
|
970
|
+
|
|
971
|
+
## Workflow
|
|
972
|
+
For this project, the recommended flow is:
|
|
973
|
+
spec \u2192 sprint \u2192 plan \u2192 code \u2192 prove \u2192 grill \u2192 commit
|
|
974
|
+
|
|
975
|
+
## Tips
|
|
976
|
+
- Type / to see all commands
|
|
977
|
+
- Type @ to invoke an agent
|
|
978
|
+
- Paste raw errors \u2014 don't summarize them
|
|
979
|
+
- Use subagents for deep investigation
|
|
980
|
+
- Say "update CLAUDE.md" after any correction
|
|
981
|
+
|
|
982
|
+
After showing the tour, create .claude/.toured to suppress the welcome message:
|
|
983
|
+
!touch .claude/.toured
|
|
984
|
+
|
|
985
|
+
Ready to start? Try /project:spec to define your first feature.`;
|
|
986
|
+
function buildQuickstart(spec) {
|
|
987
|
+
const commands = Object.keys(spec.harness.commands || {});
|
|
988
|
+
const agents = Object.keys(spec.harness.agents || {});
|
|
989
|
+
const commandList = commands.map((c) => `- \`/project:${c}\``).join("\n");
|
|
990
|
+
const agentList = agents.length > 0 ? agents.map((a) => `- \`@${a}\``).join("\n") : "- (none configured)";
|
|
991
|
+
return `# Quick Start Guide
|
|
992
|
+
|
|
993
|
+
This environment was generated by Kairn. Here's how to use it.
|
|
994
|
+
|
|
995
|
+
## First Time
|
|
996
|
+
1. Open terminal in this directory
|
|
997
|
+
2. Run \`claude\`
|
|
998
|
+
3. Type \`/project:tour\` for a guided walkthrough
|
|
999
|
+
|
|
1000
|
+
## Daily Workflow
|
|
1001
|
+
1. \`/project:status\` \u2014 see where things stand
|
|
1002
|
+
2. \`/project:spec\` \u2014 define what to build (Claude will interview you)
|
|
1003
|
+
3. Start coding \u2014 Claude follows CLAUDE.md automatically
|
|
1004
|
+
4. \`/project:prove\` \u2014 verify your work
|
|
1005
|
+
5. \`/project:commit\` \u2014 ship it
|
|
1006
|
+
|
|
1007
|
+
## Commands
|
|
1008
|
+
${commandList}
|
|
1009
|
+
|
|
1010
|
+
## Agents
|
|
1011
|
+
${agentList}
|
|
1012
|
+
|
|
1013
|
+
## Need Help?
|
|
1014
|
+
Type \`/project:help\` in Claude Code for a quick reference.
|
|
1015
|
+
`;
|
|
1016
|
+
}
|
|
1017
|
+
var LOOP_COMMAND_CODE = `# Development Loop
|
|
1018
|
+
|
|
1019
|
+
Run an assisted development cycle for the next feature.
|
|
1020
|
+
|
|
1021
|
+
## Phase 1: SPEC
|
|
1022
|
+
Review docs/TODO.md and docs/SPRINT.md.
|
|
1023
|
+
If no sprint is defined, run /project:spec to interview the user.
|
|
1024
|
+
Wait for user approval of the spec.
|
|
1025
|
+
|
|
1026
|
+
## Phase 2: PLAN
|
|
1027
|
+
Read the approved spec in docs/SPRINT.md.
|
|
1028
|
+
Plan the implementation: files to change, tests to write, approach.
|
|
1029
|
+
Write plan to docs/DECISIONS.md.
|
|
1030
|
+
Wait for user approval of the plan.
|
|
1031
|
+
|
|
1032
|
+
## Phase 3: IMPLEMENT
|
|
1033
|
+
Follow the plan. Implement the feature.
|
|
1034
|
+
Run tests after each change.
|
|
1035
|
+
Commit each logical unit: "feat: description"
|
|
1036
|
+
|
|
1037
|
+
## Phase 4: VERIFY
|
|
1038
|
+
Run /project:prove to verify the implementation.
|
|
1039
|
+
If confidence is LOW or MEDIUM, fix issues and re-verify.
|
|
1040
|
+
|
|
1041
|
+
## Phase 5: REVIEW
|
|
1042
|
+
Run /project:grill for adversarial review.
|
|
1043
|
+
Fix any BLOCKERs.
|
|
1044
|
+
|
|
1045
|
+
## Phase 6: SHIP
|
|
1046
|
+
Run /project:commit.
|
|
1047
|
+
Report what was built and what's next from docs/TODO.md.
|
|
1048
|
+
|
|
1049
|
+
Then ask: "Continue to next feature?"
|
|
1050
|
+
If yes, return to Phase 1.`;
|
|
1051
|
+
var LOOP_COMMAND_RESEARCH = `# Research Loop
|
|
1052
|
+
|
|
1053
|
+
Run an assisted research cycle.
|
|
1054
|
+
|
|
1055
|
+
## Phase 1: QUESTION
|
|
1056
|
+
Review docs/TODO.md for the next research question.
|
|
1057
|
+
If none, ask the user what to investigate.
|
|
1058
|
+
|
|
1059
|
+
## Phase 2: RESEARCH
|
|
1060
|
+
Search, extract, and analyze sources.
|
|
1061
|
+
Log findings to docs/SOURCES.md and docs/LEARNINGS.md.
|
|
1062
|
+
|
|
1063
|
+
## Phase 3: SYNTHESIZE
|
|
1064
|
+
Review all findings. Write structured summary to docs/SUMMARY.md.
|
|
1065
|
+
Cite all sources.
|
|
1066
|
+
|
|
1067
|
+
## Phase 4: REVIEW
|
|
1068
|
+
Present the summary. Ask the user for feedback.
|
|
1069
|
+
Revise based on feedback.
|
|
1070
|
+
|
|
1071
|
+
## Phase 5: NEXT
|
|
1072
|
+
Update docs/TODO.md \u2014 mark question as done, identify follow-ups.
|
|
1073
|
+
Ask: "Continue to next question?"`;
|
|
1074
|
+
var PM_AGENT = `---
|
|
1075
|
+
name: pm
|
|
1076
|
+
description: Project manager agent. Maintains roadmap, specs features, prioritizes work.
|
|
1077
|
+
model: opus
|
|
1078
|
+
---
|
|
1079
|
+
|
|
1080
|
+
You are a project manager for this codebase.
|
|
1081
|
+
|
|
1082
|
+
Your responsibilities:
|
|
1083
|
+
1. Maintain docs/TODO.md \u2014 keep it prioritized and current
|
|
1084
|
+
2. Write specs to docs/SPRINT.md when asked
|
|
1085
|
+
3. Review completed work and suggest what's next
|
|
1086
|
+
4. Track decisions in docs/DECISIONS.md
|
|
1087
|
+
5. Track learnings in docs/LEARNINGS.md
|
|
1088
|
+
|
|
1089
|
+
When invoked:
|
|
1090
|
+
- Read all docs/ files to understand current state
|
|
1091
|
+
- Read recent git log for what changed
|
|
1092
|
+
- Suggest the highest-priority next task
|
|
1093
|
+
- If asked to spec something, interview the user (5-8 questions)
|
|
1094
|
+
|
|
1095
|
+
You do NOT write code. You plan, spec, and prioritize.`;
|
|
1096
|
+
var AUTO_COMMAND = `# Autonomous Development
|
|
1097
|
+
|
|
1098
|
+
PM-driven development loop with PR delivery.
|
|
1099
|
+
|
|
1100
|
+
## Phase 1: PLAN (@pm)
|
|
1101
|
+
Use @pm to:
|
|
1102
|
+
- Read docs/TODO.md and docs/SPRINT.md
|
|
1103
|
+
- Select the highest-priority unfinished task
|
|
1104
|
+
- Write a spec to docs/SPRINT.md
|
|
1105
|
+
- Present the spec for approval
|
|
1106
|
+
|
|
1107
|
+
Wait for user approval. If approved, proceed.
|
|
1108
|
+
|
|
1109
|
+
## Phase 2: BRANCH
|
|
1110
|
+
Create an isolated worktree:
|
|
1111
|
+
git worktree add ../project-feat-{name} -b feat/{name}
|
|
1112
|
+
All implementation happens in the worktree.
|
|
1113
|
+
|
|
1114
|
+
## Phase 3: IMPLEMENT
|
|
1115
|
+
In the worktree directory:
|
|
1116
|
+
- Follow the spec in docs/SPRINT.md
|
|
1117
|
+
- Build, test, commit after each change
|
|
1118
|
+
|
|
1119
|
+
## Phase 4: VERIFY
|
|
1120
|
+
Run verification:
|
|
1121
|
+
- Static analysis (linting, type checks)
|
|
1122
|
+
- Run functional tests
|
|
1123
|
+
- If NEEDS FIXES: fix and re-verify
|
|
1124
|
+
|
|
1125
|
+
## Phase 5: PR
|
|
1126
|
+
Create a pull request:
|
|
1127
|
+
gh pr create --title "feat: {name}" --body "{spec + QA report}"
|
|
1128
|
+
|
|
1129
|
+
## Phase 6: NEXT
|
|
1130
|
+
Report:
|
|
1131
|
+
"PR #{N} ready for review: {link}
|
|
1132
|
+
Next priority from TODO.md: {next task}
|
|
1133
|
+
Continue? (y/n)"
|
|
1134
|
+
|
|
1135
|
+
If yes, return to Phase 1 with next task.`;
|
|
1136
|
+
var AUTOPILOT_COMMAND = `# Autopilot Mode
|
|
1137
|
+
|
|
1138
|
+
Continuous autonomous development. The PM plans, the loop executes,
|
|
1139
|
+
PRs are opened automatically. You review when ready.
|
|
1140
|
+
|
|
1141
|
+
## Configuration
|
|
1142
|
+
- Max features per session: 5 (prevent runaway)
|
|
1143
|
+
- Stop on: test failure, build error, or blocked dependency
|
|
1144
|
+
- All work in isolated worktrees
|
|
1145
|
+
- Every feature = one PR
|
|
1146
|
+
|
|
1147
|
+
## The Loop
|
|
1148
|
+
Repeat until max features reached or stopped:
|
|
1149
|
+
1. @pm selects next priority from docs/TODO.md
|
|
1150
|
+
2. Create worktree + branch
|
|
1151
|
+
3. Implement the feature
|
|
1152
|
+
4. Run verification (build, test, lint)
|
|
1153
|
+
5. Open PR via gh
|
|
1154
|
+
6. Report status
|
|
1155
|
+
7. Move to next feature
|
|
1156
|
+
|
|
1157
|
+
## Stop Conditions
|
|
1158
|
+
- Max 5 features per autopilot session
|
|
1159
|
+
- Any BLOCKER from verification
|
|
1160
|
+
- Build failure that can't be resolved in 3 attempts
|
|
1161
|
+
- User presses Escape`;
|
|
1162
|
+
var AUTOPILOT_WARNING = `
|
|
1163
|
+
## Autopilot Mode Active
|
|
1164
|
+
This environment is configured for autonomous operation.
|
|
1165
|
+
The @pm agent plans features and /project:autopilot executes them.
|
|
1166
|
+
All changes are delivered as PRs \u2014 review before merging.
|
|
1167
|
+
Stop conditions: max 5 features, test failure, or Escape.`;
|
|
1168
|
+
function isResearchProject(spec) {
|
|
1169
|
+
const commands = spec.harness.commands ?? {};
|
|
1170
|
+
return "research" in commands || "summarize" in commands;
|
|
1171
|
+
}
|
|
1172
|
+
function applyAutonomyLevel(spec) {
|
|
1173
|
+
const level = spec.autonomy_level ?? 1;
|
|
1174
|
+
const commands = spec.harness.commands ?? {};
|
|
1175
|
+
const agents = spec.harness.agents ?? {};
|
|
1176
|
+
const docs = spec.harness.docs ?? {};
|
|
1177
|
+
const settings = spec.harness.settings ?? {};
|
|
1178
|
+
if (level >= 1) {
|
|
1179
|
+
if (!("tour" in commands)) {
|
|
1180
|
+
commands.tour = TOUR_COMMAND;
|
|
1181
|
+
}
|
|
1182
|
+
docs.QUICKSTART = buildQuickstart(spec);
|
|
1183
|
+
const hooks = settings.hooks ?? {};
|
|
1184
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
1185
|
+
sessionStart.push(WELCOME_HOOK);
|
|
1186
|
+
hooks.SessionStart = sessionStart;
|
|
1187
|
+
settings.hooks = hooks;
|
|
1188
|
+
}
|
|
1189
|
+
if (level >= 2) {
|
|
1190
|
+
if (!("loop" in commands)) {
|
|
1191
|
+
commands.loop = isResearchProject(spec) ? LOOP_COMMAND_RESEARCH : LOOP_COMMAND_CODE;
|
|
1192
|
+
}
|
|
1193
|
+
if (!("pm" in agents)) {
|
|
1194
|
+
agents.pm = PM_AGENT;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (level >= 3) {
|
|
1198
|
+
if (!("auto" in commands)) {
|
|
1199
|
+
commands.auto = AUTO_COMMAND;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (level >= 4) {
|
|
1203
|
+
if (!("autopilot" in commands)) {
|
|
1204
|
+
commands.autopilot = AUTOPILOT_COMMAND;
|
|
1205
|
+
}
|
|
1206
|
+
if (spec.harness.claude_md && !spec.harness.claude_md.includes("Autopilot Mode")) {
|
|
1207
|
+
spec.harness.claude_md += "\n" + AUTOPILOT_WARNING;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
spec.harness.commands = commands;
|
|
1211
|
+
spec.harness.agents = agents;
|
|
1212
|
+
spec.harness.docs = docs;
|
|
1213
|
+
spec.harness.settings = settings;
|
|
1214
|
+
}
|
|
1215
|
+
function autonomyLabel(level) {
|
|
1216
|
+
return AUTONOMY_LABELS[level];
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/adapter/claude-code.ts
|
|
922
1220
|
var STATUS_LINE = {
|
|
923
1221
|
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
1222
|
};
|
|
@@ -926,25 +1224,40 @@ function isCodeProject(spec) {
|
|
|
926
1224
|
const commands = spec.harness.commands ?? {};
|
|
927
1225
|
return "status" in commands || "test" in commands;
|
|
928
1226
|
}
|
|
929
|
-
|
|
1227
|
+
var ENV_LOADER_HOOK = {
|
|
1228
|
+
matcher: "",
|
|
1229
|
+
hooks: [{
|
|
1230
|
+
type: "command",
|
|
1231
|
+
command: 'if [ -f .env ] && [ -n "$CLAUDE_ENV_FILE" ]; then grep -v "^#" .env | grep -v "^$" | grep "=" >> "$CLAUDE_ENV_FILE"; fi'
|
|
1232
|
+
}]
|
|
1233
|
+
};
|
|
1234
|
+
function resolveSettings(spec, options) {
|
|
930
1235
|
const settings = spec.harness.settings;
|
|
931
|
-
|
|
932
|
-
if ("statusLine" in
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1236
|
+
const base = settings && Object.keys(settings).length > 0 ? { ...settings } : {};
|
|
1237
|
+
if (!("statusLine" in base) && isCodeProject(spec)) {
|
|
1238
|
+
base.statusLine = STATUS_LINE;
|
|
1239
|
+
}
|
|
1240
|
+
if (options?.hasEnvVars) {
|
|
1241
|
+
const hooks = base.hooks ?? {};
|
|
1242
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
1243
|
+
sessionStart.push(ENV_LOADER_HOOK);
|
|
1244
|
+
hooks.SessionStart = sessionStart;
|
|
1245
|
+
base.hooks = hooks;
|
|
1246
|
+
}
|
|
1247
|
+
if (Object.keys(base).length === 0) return null;
|
|
1248
|
+
return base;
|
|
937
1249
|
}
|
|
938
1250
|
async function writeFile(filePath, content) {
|
|
939
1251
|
await fs5.mkdir(path5.dirname(filePath), { recursive: true });
|
|
940
1252
|
await fs5.writeFile(filePath, content, "utf-8");
|
|
941
1253
|
}
|
|
942
|
-
function buildFileMap(spec) {
|
|
1254
|
+
function buildFileMap(spec, options) {
|
|
1255
|
+
applyAutonomyLevel(spec);
|
|
943
1256
|
const files = /* @__PURE__ */ new Map();
|
|
944
1257
|
if (spec.harness.claude_md) {
|
|
945
1258
|
files.set(".claude/CLAUDE.md", spec.harness.claude_md);
|
|
946
1259
|
}
|
|
947
|
-
const resolvedSettings = resolveSettings(spec);
|
|
1260
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
948
1261
|
if (resolvedSettings) {
|
|
949
1262
|
files.set(".claude/settings.json", JSON.stringify(resolvedSettings, null, 2));
|
|
950
1263
|
}
|
|
@@ -981,7 +1294,8 @@ function buildFileMap(spec) {
|
|
|
981
1294
|
}
|
|
982
1295
|
return files;
|
|
983
1296
|
}
|
|
984
|
-
async function writeEnvironment(spec, targetDir) {
|
|
1297
|
+
async function writeEnvironment(spec, targetDir, options) {
|
|
1298
|
+
applyAutonomyLevel(spec);
|
|
985
1299
|
const claudeDir = path5.join(targetDir, ".claude");
|
|
986
1300
|
const written = [];
|
|
987
1301
|
if (spec.harness.claude_md) {
|
|
@@ -989,7 +1303,7 @@ async function writeEnvironment(spec, targetDir) {
|
|
|
989
1303
|
await writeFile(p, spec.harness.claude_md);
|
|
990
1304
|
written.push(".claude/CLAUDE.md");
|
|
991
1305
|
}
|
|
992
|
-
const resolvedSettings = resolveSettings(spec);
|
|
1306
|
+
const resolvedSettings = resolveSettings(spec, options);
|
|
993
1307
|
if (resolvedSettings) {
|
|
994
1308
|
const p = path5.join(claudeDir, "settings.json");
|
|
995
1309
|
await writeFile(p, JSON.stringify(resolvedSettings, null, 2));
|
|
@@ -1192,6 +1506,114 @@ async function writeHermesEnvironment(spec, registry) {
|
|
|
1192
1506
|
return written;
|
|
1193
1507
|
}
|
|
1194
1508
|
|
|
1509
|
+
// src/secrets.ts
|
|
1510
|
+
import { password as password2 } from "@inquirer/prompts";
|
|
1511
|
+
import chalk4 from "chalk";
|
|
1512
|
+
import fs7 from "fs/promises";
|
|
1513
|
+
import path7 from "path";
|
|
1514
|
+
async function collectAndWriteKeys(envSetup, targetDir) {
|
|
1515
|
+
console.log(ui.section("API Keys"));
|
|
1516
|
+
console.log(
|
|
1517
|
+
chalk4.dim(" Some tools need API keys. Enter them now or press Enter to skip.\n")
|
|
1518
|
+
);
|
|
1519
|
+
const envEntries = [
|
|
1520
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1521
|
+
"# Do NOT commit this file to git",
|
|
1522
|
+
""
|
|
1523
|
+
];
|
|
1524
|
+
let keysEntered = 0;
|
|
1525
|
+
let keysSkipped = 0;
|
|
1526
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1527
|
+
for (const env of envSetup) {
|
|
1528
|
+
if (seen.has(env.envVar)) continue;
|
|
1529
|
+
seen.add(env.envVar);
|
|
1530
|
+
console.log(chalk4.bold(` ${env.envVar}`) + chalk4.dim(` (${env.toolName})`));
|
|
1531
|
+
if (env.signupUrl) {
|
|
1532
|
+
console.log(chalk4.dim(` Get one at: ${env.signupUrl}`));
|
|
1533
|
+
}
|
|
1534
|
+
const value = await password2({
|
|
1535
|
+
message: env.envVar,
|
|
1536
|
+
mask: "\u2022"
|
|
1537
|
+
});
|
|
1538
|
+
if (value && value.trim()) {
|
|
1539
|
+
envEntries.push(`${env.envVar}=${value.trim()}`);
|
|
1540
|
+
console.log(chalk4.green(" \u2713 saved\n"));
|
|
1541
|
+
keysEntered++;
|
|
1542
|
+
} else {
|
|
1543
|
+
envEntries.push(`${env.envVar}=`);
|
|
1544
|
+
console.log(chalk4.dim(" (skipped)\n"));
|
|
1545
|
+
keysSkipped++;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1549
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1550
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1551
|
+
console.log(chalk4.green(` \u2713 ${keysEntered} key(s) saved to .env (gitignored)`));
|
|
1552
|
+
if (keysSkipped > 0) {
|
|
1553
|
+
console.log(chalk4.dim(" Skipped keys can be added later: kairn keys"));
|
|
1554
|
+
}
|
|
1555
|
+
return { keysEntered, keysSkipped, envPath };
|
|
1556
|
+
}
|
|
1557
|
+
async function writeEmptyEnvFile(envSetup, targetDir) {
|
|
1558
|
+
const envEntries = [
|
|
1559
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
1560
|
+
"# Do NOT commit this file to git",
|
|
1561
|
+
""
|
|
1562
|
+
];
|
|
1563
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1564
|
+
for (const env of envSetup) {
|
|
1565
|
+
if (seen.has(env.envVar)) continue;
|
|
1566
|
+
seen.add(env.envVar);
|
|
1567
|
+
envEntries.push(`${env.envVar}=`);
|
|
1568
|
+
}
|
|
1569
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1570
|
+
await fs7.writeFile(envPath, envEntries.join("\n") + "\n", "utf-8");
|
|
1571
|
+
await ensureGitignoreEntry(targetDir, ".env");
|
|
1572
|
+
}
|
|
1573
|
+
async function ensureGitignoreEntry(targetDir, entry) {
|
|
1574
|
+
const gitignorePath = path7.join(targetDir, ".gitignore");
|
|
1575
|
+
let gitignore = "";
|
|
1576
|
+
try {
|
|
1577
|
+
gitignore = await fs7.readFile(gitignorePath, "utf-8");
|
|
1578
|
+
} catch {
|
|
1579
|
+
}
|
|
1580
|
+
if (!gitignore.split("\n").some((line) => line.trim() === entry)) {
|
|
1581
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
1582
|
+
await fs7.writeFile(gitignorePath, gitignore + separator + entry + "\n", "utf-8");
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
async function readEnvFile(targetDir) {
|
|
1586
|
+
const envPath = path7.join(targetDir, ".env");
|
|
1587
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1588
|
+
try {
|
|
1589
|
+
const content = await fs7.readFile(envPath, "utf-8");
|
|
1590
|
+
for (const line of content.split("\n")) {
|
|
1591
|
+
const trimmed = line.trim();
|
|
1592
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1593
|
+
const eqIndex = trimmed.indexOf("=");
|
|
1594
|
+
if (eqIndex === -1) continue;
|
|
1595
|
+
const key = trimmed.slice(0, eqIndex);
|
|
1596
|
+
const value = trimmed.slice(eqIndex + 1);
|
|
1597
|
+
entries.set(key, value);
|
|
1598
|
+
}
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
return entries;
|
|
1602
|
+
}
|
|
1603
|
+
async function detectRequiredEnvVars(targetDir) {
|
|
1604
|
+
const mcpPath = path7.join(targetDir, ".mcp.json");
|
|
1605
|
+
const envVars = /* @__PURE__ */ new Set();
|
|
1606
|
+
try {
|
|
1607
|
+
const content = await fs7.readFile(mcpPath, "utf-8");
|
|
1608
|
+
const matches = content.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
|
|
1609
|
+
for (const match of matches) {
|
|
1610
|
+
envVars.add(match[1]);
|
|
1611
|
+
}
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
return [...envVars];
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1195
1617
|
// src/commands/describe.ts
|
|
1196
1618
|
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
1619
|
printFullBanner("The Agent Environment Compiler");
|
|
@@ -1200,7 +1622,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1200
1622
|
console.log(
|
|
1201
1623
|
ui.errorBox(
|
|
1202
1624
|
"No configuration found",
|
|
1203
|
-
`Run ${
|
|
1625
|
+
`Run ${chalk5.bold("kairn init")} to set up your API key.`
|
|
1204
1626
|
)
|
|
1205
1627
|
);
|
|
1206
1628
|
process.exit(1);
|
|
@@ -1209,14 +1631,14 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
1209
1631
|
message: "What do you want your agent to do?"
|
|
1210
1632
|
});
|
|
1211
1633
|
if (!intentRaw.trim()) {
|
|
1212
|
-
console.log(
|
|
1634
|
+
console.log(chalk5.red("\n No description provided. Aborting.\n"));
|
|
1213
1635
|
process.exit(1);
|
|
1214
1636
|
}
|
|
1215
1637
|
let finalIntent = intentRaw;
|
|
1216
1638
|
if (!options.quick) {
|
|
1217
1639
|
console.log(ui.section("Clarification"));
|
|
1218
|
-
console.log(
|
|
1219
|
-
console.log(
|
|
1640
|
+
console.log(chalk5.dim(" Let me understand your project better."));
|
|
1641
|
+
console.log(chalk5.dim(" Press Enter to accept the suggestion, or type your own answer.\n"));
|
|
1220
1642
|
let clarifications = [];
|
|
1221
1643
|
try {
|
|
1222
1644
|
clarifications = await generateClarifications(intentRaw);
|
|
@@ -1238,6 +1660,23 @@ Clarifications:
|
|
|
1238
1660
|
${clarificationLines}`;
|
|
1239
1661
|
}
|
|
1240
1662
|
}
|
|
1663
|
+
let autonomyLevel = 1;
|
|
1664
|
+
if (!options.quick) {
|
|
1665
|
+
console.log(ui.section("Autonomy"));
|
|
1666
|
+
autonomyLevel = await select2({
|
|
1667
|
+
message: "Autonomy level",
|
|
1668
|
+
choices: [
|
|
1669
|
+
{ name: "1. Guided \u2014 orientation + commands, you drive", value: 1 },
|
|
1670
|
+
{ name: "2. Assisted \u2014 workflow loop, you approve phases", value: 2 },
|
|
1671
|
+
{ name: "3. Autonomous \u2014 PM plans, loop executes, you review PRs", value: 3 },
|
|
1672
|
+
{ name: "4. Full Auto \u2014 continuous execution (\u26A0 advanced)", value: 4 }
|
|
1673
|
+
],
|
|
1674
|
+
default: 1
|
|
1675
|
+
});
|
|
1676
|
+
finalIntent += `
|
|
1677
|
+
|
|
1678
|
+
Autonomy level: ${autonomyLevel} (${autonomyLabel(autonomyLevel)})`;
|
|
1679
|
+
}
|
|
1241
1680
|
console.log(ui.section("Compilation"));
|
|
1242
1681
|
const spinner = ora({ text: "Loading tool registry...", indent: 2 }).start();
|
|
1243
1682
|
let spec;
|
|
@@ -1245,11 +1684,12 @@ ${clarificationLines}`;
|
|
|
1245
1684
|
spec = await compile(finalIntent, (msg) => {
|
|
1246
1685
|
spinner.text = msg;
|
|
1247
1686
|
});
|
|
1687
|
+
spec.autonomy_level = autonomyLevel;
|
|
1248
1688
|
spinner.succeed("Environment compiled");
|
|
1249
1689
|
} catch (err) {
|
|
1250
1690
|
spinner.fail("Compilation failed");
|
|
1251
1691
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1252
|
-
console.log(
|
|
1692
|
+
console.log(chalk5.red(`
|
|
1253
1693
|
${msg}
|
|
1254
1694
|
`));
|
|
1255
1695
|
process.exit(1);
|
|
@@ -1259,6 +1699,7 @@ ${clarificationLines}`;
|
|
|
1259
1699
|
console.log("");
|
|
1260
1700
|
console.log(ui.kv("Name:", spec.name));
|
|
1261
1701
|
console.log(ui.kv("Description:", spec.description));
|
|
1702
|
+
console.log(ui.kv("Autonomy:", `Level ${spec.autonomy_level} (${autonomyLabel(spec.autonomy_level)})`));
|
|
1262
1703
|
console.log(ui.kv("Tools:", String(summary.toolCount)));
|
|
1263
1704
|
console.log(ui.kv("Commands:", String(summary.commandCount)));
|
|
1264
1705
|
console.log(ui.kv("Rules:", String(summary.ruleCount)));
|
|
@@ -1279,7 +1720,7 @@ ${clarificationLines}`;
|
|
|
1279
1720
|
default: true
|
|
1280
1721
|
});
|
|
1281
1722
|
if (!proceed) {
|
|
1282
|
-
console.log(
|
|
1723
|
+
console.log(chalk5.dim("\n Aborted. Environment saved to ~/.kairn/envs/\n"));
|
|
1283
1724
|
return;
|
|
1284
1725
|
}
|
|
1285
1726
|
const targetDir = process.cwd();
|
|
@@ -1288,25 +1729,24 @@ ${clarificationLines}`;
|
|
|
1288
1729
|
await writeHermesEnvironment(spec, registry);
|
|
1289
1730
|
console.log("\n" + ui.success("Environment written for Hermes"));
|
|
1290
1731
|
console.log(
|
|
1291
|
-
|
|
1732
|
+
chalk5.cyan("\n Ready! Run ") + chalk5.bold("hermes") + chalk5.cyan(" to start.\n")
|
|
1292
1733
|
);
|
|
1293
1734
|
} else {
|
|
1294
|
-
const
|
|
1735
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1736
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1295
1737
|
console.log(ui.section("Files Written"));
|
|
1296
1738
|
console.log("");
|
|
1297
1739
|
for (const file of written) {
|
|
1298
1740
|
console.log(ui.file(file));
|
|
1299
1741
|
}
|
|
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("");
|
|
1742
|
+
if (hasEnvVars) {
|
|
1743
|
+
if (options.quick) {
|
|
1744
|
+
await writeEmptyEnvFile(summary.envSetup, targetDir);
|
|
1745
|
+
console.log(ui.success("Empty .env written (gitignored) \u2014 fill in keys later: kairn keys"));
|
|
1746
|
+
} else {
|
|
1747
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
1309
1748
|
}
|
|
1749
|
+
console.log("");
|
|
1310
1750
|
}
|
|
1311
1751
|
if (summary.pluginCommands.length > 0) {
|
|
1312
1752
|
console.log(ui.section("Plugins"));
|
|
@@ -1324,28 +1764,28 @@ ${clarificationLines}`;
|
|
|
1324
1764
|
|
|
1325
1765
|
// src/commands/list.ts
|
|
1326
1766
|
import { Command as Command3 } from "commander";
|
|
1327
|
-
import
|
|
1328
|
-
import
|
|
1329
|
-
import
|
|
1767
|
+
import chalk6 from "chalk";
|
|
1768
|
+
import fs8 from "fs/promises";
|
|
1769
|
+
import path8 from "path";
|
|
1330
1770
|
var listCommand = new Command3("list").description("Show saved environments").action(async () => {
|
|
1331
1771
|
printCompactBanner();
|
|
1332
1772
|
const envsDir = getEnvsDir();
|
|
1333
1773
|
let files;
|
|
1334
1774
|
try {
|
|
1335
|
-
files = await
|
|
1775
|
+
files = await fs8.readdir(envsDir);
|
|
1336
1776
|
} catch {
|
|
1337
|
-
console.log(
|
|
1777
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1338
1778
|
return;
|
|
1339
1779
|
}
|
|
1340
1780
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1341
1781
|
if (jsonFiles.length === 0) {
|
|
1342
|
-
console.log(
|
|
1782
|
+
console.log(chalk6.dim(" No environments yet. Run ") + chalk6.bold("kairn describe") + chalk6.dim(" to create one.\n"));
|
|
1343
1783
|
return;
|
|
1344
1784
|
}
|
|
1345
1785
|
let first = true;
|
|
1346
1786
|
for (const file of jsonFiles) {
|
|
1347
1787
|
try {
|
|
1348
|
-
const data = await
|
|
1788
|
+
const data = await fs8.readFile(path8.join(envsDir, file), "utf-8");
|
|
1349
1789
|
const spec = JSON.parse(data);
|
|
1350
1790
|
const date = new Date(spec.created_at).toLocaleDateString();
|
|
1351
1791
|
const toolCount = spec.tools?.length ?? 0;
|
|
@@ -1353,10 +1793,10 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1353
1793
|
console.log(ui.divider());
|
|
1354
1794
|
}
|
|
1355
1795
|
first = false;
|
|
1356
|
-
console.log(ui.kv("Name",
|
|
1796
|
+
console.log(ui.kv("Name", chalk6.bold(spec.name)));
|
|
1357
1797
|
console.log(ui.kv("Description", spec.description));
|
|
1358
1798
|
console.log(ui.kv("Date", `${date} \xB7 ${toolCount} tools`));
|
|
1359
|
-
console.log(ui.kv("ID",
|
|
1799
|
+
console.log(ui.kv("ID", chalk6.dim(spec.id)));
|
|
1360
1800
|
console.log("");
|
|
1361
1801
|
} catch {
|
|
1362
1802
|
}
|
|
@@ -1365,9 +1805,9 @@ var listCommand = new Command3("list").description("Show saved environments").ac
|
|
|
1365
1805
|
|
|
1366
1806
|
// src/commands/activate.ts
|
|
1367
1807
|
import { Command as Command4 } from "commander";
|
|
1368
|
-
import
|
|
1369
|
-
import
|
|
1370
|
-
import
|
|
1808
|
+
import chalk7 from "chalk";
|
|
1809
|
+
import fs9 from "fs/promises";
|
|
1810
|
+
import path9 from "path";
|
|
1371
1811
|
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
1812
|
printCompactBanner();
|
|
1373
1813
|
const envsDir = getEnvsDir();
|
|
@@ -1377,7 +1817,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1377
1817
|
let fromTemplate = false;
|
|
1378
1818
|
let envFiles = [];
|
|
1379
1819
|
try {
|
|
1380
|
-
envFiles = await
|
|
1820
|
+
envFiles = await fs9.readdir(envsDir);
|
|
1381
1821
|
} catch {
|
|
1382
1822
|
}
|
|
1383
1823
|
match = envFiles.find(
|
|
@@ -1388,7 +1828,7 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1388
1828
|
} else {
|
|
1389
1829
|
let templateFiles = [];
|
|
1390
1830
|
try {
|
|
1391
|
-
templateFiles = await
|
|
1831
|
+
templateFiles = await fs9.readdir(templatesDir);
|
|
1392
1832
|
} catch {
|
|
1393
1833
|
}
|
|
1394
1834
|
match = templateFiles.find(
|
|
@@ -1399,16 +1839,16 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1399
1839
|
fromTemplate = true;
|
|
1400
1840
|
} else {
|
|
1401
1841
|
console.log(ui.error(`Environment "${envId}" not found.`));
|
|
1402
|
-
console.log(
|
|
1403
|
-
console.log(
|
|
1842
|
+
console.log(chalk7.dim(" Run kairn list to see saved environments."));
|
|
1843
|
+
console.log(chalk7.dim(" Run kairn templates to see available templates.\n"));
|
|
1404
1844
|
process.exit(1);
|
|
1405
1845
|
}
|
|
1406
1846
|
}
|
|
1407
|
-
const data = await
|
|
1847
|
+
const data = await fs9.readFile(path9.join(sourceDir, match), "utf-8");
|
|
1408
1848
|
const spec = JSON.parse(data);
|
|
1409
|
-
const label = fromTemplate ?
|
|
1410
|
-
console.log(
|
|
1411
|
-
console.log(
|
|
1849
|
+
const label = fromTemplate ? chalk7.dim(" (template)") : "";
|
|
1850
|
+
console.log(chalk7.cyan(` Activating: ${spec.name}`) + label);
|
|
1851
|
+
console.log(chalk7.dim(` ${spec.description}
|
|
1412
1852
|
`));
|
|
1413
1853
|
const targetDir = process.cwd();
|
|
1414
1854
|
const written = await writeEnvironment(spec, targetDir);
|
|
@@ -1421,22 +1861,22 @@ var activateCommand = new Command4("activate").description("Re-deploy a saved en
|
|
|
1421
1861
|
|
|
1422
1862
|
// src/commands/update-registry.ts
|
|
1423
1863
|
import { Command as Command5 } from "commander";
|
|
1424
|
-
import
|
|
1425
|
-
import
|
|
1426
|
-
import
|
|
1864
|
+
import chalk8 from "chalk";
|
|
1865
|
+
import fs10 from "fs/promises";
|
|
1866
|
+
import path10 from "path";
|
|
1427
1867
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1428
1868
|
var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
|
|
1429
1869
|
async function getLocalRegistryPath() {
|
|
1430
1870
|
const __filename3 = fileURLToPath3(import.meta.url);
|
|
1431
|
-
const __dirname3 =
|
|
1871
|
+
const __dirname3 = path10.dirname(__filename3);
|
|
1432
1872
|
const candidates = [
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1873
|
+
path10.resolve(__dirname3, "../registry/tools.json"),
|
|
1874
|
+
path10.resolve(__dirname3, "../src/registry/tools.json"),
|
|
1875
|
+
path10.resolve(__dirname3, "../../src/registry/tools.json")
|
|
1436
1876
|
];
|
|
1437
1877
|
for (const candidate of candidates) {
|
|
1438
1878
|
try {
|
|
1439
|
-
await
|
|
1879
|
+
await fs10.access(candidate);
|
|
1440
1880
|
return candidate;
|
|
1441
1881
|
} catch {
|
|
1442
1882
|
continue;
|
|
@@ -1447,15 +1887,15 @@ async function getLocalRegistryPath() {
|
|
|
1447
1887
|
var updateRegistryCommand = new Command5("update-registry").description("Fetch the latest tool registry from GitHub").option("--url <url>", "Custom registry URL").action(async (options) => {
|
|
1448
1888
|
printCompactBanner();
|
|
1449
1889
|
const url = options.url || REGISTRY_URL;
|
|
1450
|
-
console.log(
|
|
1890
|
+
console.log(chalk8.dim(` Fetching registry from ${url}...`));
|
|
1451
1891
|
try {
|
|
1452
1892
|
const response = await fetch(url);
|
|
1453
1893
|
if (!response.ok) {
|
|
1454
1894
|
console.log(
|
|
1455
1895
|
ui.error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
|
|
1456
1896
|
);
|
|
1457
|
-
console.log(
|
|
1458
|
-
console.log(
|
|
1897
|
+
console.log(chalk8.dim(" The remote registry may not be available yet."));
|
|
1898
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1459
1899
|
return;
|
|
1460
1900
|
}
|
|
1461
1901
|
const text = await response.text();
|
|
@@ -1473,35 +1913,35 @@ var updateRegistryCommand = new Command5("update-registry").description("Fetch t
|
|
|
1473
1913
|
const registryPath = await getLocalRegistryPath();
|
|
1474
1914
|
const backupPath = registryPath + ".bak";
|
|
1475
1915
|
try {
|
|
1476
|
-
await
|
|
1916
|
+
await fs10.copyFile(registryPath, backupPath);
|
|
1477
1917
|
} catch {
|
|
1478
1918
|
}
|
|
1479
|
-
await
|
|
1919
|
+
await fs10.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
|
|
1480
1920
|
console.log(ui.success(`Registry updated: ${tools.length} tools`));
|
|
1481
|
-
console.log(
|
|
1482
|
-
console.log(
|
|
1921
|
+
console.log(chalk8.dim(` Saved to: ${registryPath}`));
|
|
1922
|
+
console.log(chalk8.dim(` Backup: ${backupPath}
|
|
1483
1923
|
`));
|
|
1484
1924
|
} catch (err) {
|
|
1485
1925
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1486
1926
|
console.log(ui.error(`Network error: ${msg}`));
|
|
1487
|
-
console.log(
|
|
1927
|
+
console.log(chalk8.dim(" Your local registry is still active.\n"));
|
|
1488
1928
|
}
|
|
1489
1929
|
});
|
|
1490
1930
|
|
|
1491
1931
|
// src/commands/optimize.ts
|
|
1492
1932
|
import { Command as Command6 } from "commander";
|
|
1493
1933
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1494
|
-
import
|
|
1934
|
+
import chalk9 from "chalk";
|
|
1495
1935
|
import ora2 from "ora";
|
|
1496
|
-
import
|
|
1497
|
-
import
|
|
1936
|
+
import fs12 from "fs/promises";
|
|
1937
|
+
import path12 from "path";
|
|
1498
1938
|
|
|
1499
1939
|
// src/scanner/scan.ts
|
|
1500
|
-
import
|
|
1501
|
-
import
|
|
1940
|
+
import fs11 from "fs/promises";
|
|
1941
|
+
import path11 from "path";
|
|
1502
1942
|
async function fileExists(p) {
|
|
1503
1943
|
try {
|
|
1504
|
-
await
|
|
1944
|
+
await fs11.access(p);
|
|
1505
1945
|
return true;
|
|
1506
1946
|
} catch {
|
|
1507
1947
|
return false;
|
|
@@ -1509,7 +1949,7 @@ async function fileExists(p) {
|
|
|
1509
1949
|
}
|
|
1510
1950
|
async function readJsonSafe(p) {
|
|
1511
1951
|
try {
|
|
1512
|
-
const data = await
|
|
1952
|
+
const data = await fs11.readFile(p, "utf-8");
|
|
1513
1953
|
return JSON.parse(data);
|
|
1514
1954
|
} catch {
|
|
1515
1955
|
return null;
|
|
@@ -1517,14 +1957,14 @@ async function readJsonSafe(p) {
|
|
|
1517
1957
|
}
|
|
1518
1958
|
async function readFileSafe(p) {
|
|
1519
1959
|
try {
|
|
1520
|
-
return await
|
|
1960
|
+
return await fs11.readFile(p, "utf-8");
|
|
1521
1961
|
} catch {
|
|
1522
1962
|
return null;
|
|
1523
1963
|
}
|
|
1524
1964
|
}
|
|
1525
1965
|
async function listDirSafe(p) {
|
|
1526
1966
|
try {
|
|
1527
|
-
const entries = await
|
|
1967
|
+
const entries = await fs11.readdir(p);
|
|
1528
1968
|
return entries.filter((e) => !e.startsWith("."));
|
|
1529
1969
|
} catch {
|
|
1530
1970
|
return [];
|
|
@@ -1576,7 +2016,7 @@ function extractEnvKeys(content) {
|
|
|
1576
2016
|
return keys;
|
|
1577
2017
|
}
|
|
1578
2018
|
async function scanProject(dir) {
|
|
1579
|
-
const pkg = await readJsonSafe(
|
|
2019
|
+
const pkg = await readJsonSafe(path11.join(dir, "package.json"));
|
|
1580
2020
|
const deps = pkg?.dependencies ? Object.keys(pkg.dependencies) : [];
|
|
1581
2021
|
const devDeps = pkg?.devDependencies ? Object.keys(pkg.devDependencies) : [];
|
|
1582
2022
|
const allDeps = [...deps, ...devDeps];
|
|
@@ -1604,19 +2044,19 @@ async function scanProject(dir) {
|
|
|
1604
2044
|
const framework = detectFramework(allDeps);
|
|
1605
2045
|
const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
|
|
1606
2046
|
const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
|
|
1607
|
-
const hasTests = testCommand !== null || await fileExists(
|
|
2047
|
+
const hasTests = testCommand !== null || await fileExists(path11.join(dir, "tests")) || await fileExists(path11.join(dir, "__tests__")) || await fileExists(path11.join(dir, "test"));
|
|
1608
2048
|
const buildCommand = scripts.build || null;
|
|
1609
2049
|
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(
|
|
2050
|
+
const hasSrc = await fileExists(path11.join(dir, "src"));
|
|
2051
|
+
const hasDocker = await fileExists(path11.join(dir, "docker-compose.yml")) || await fileExists(path11.join(dir, "Dockerfile"));
|
|
2052
|
+
const hasCi = await fileExists(path11.join(dir, ".github/workflows"));
|
|
2053
|
+
const hasEnvFile = await fileExists(path11.join(dir, ".env")) || await fileExists(path11.join(dir, ".env.example"));
|
|
1614
2054
|
let envKeys = [];
|
|
1615
|
-
const envExample = await readFileSafe(
|
|
2055
|
+
const envExample = await readFileSafe(path11.join(dir, ".env.example"));
|
|
1616
2056
|
if (envExample) {
|
|
1617
2057
|
envKeys = extractEnvKeys(envExample);
|
|
1618
2058
|
}
|
|
1619
|
-
const claudeDir =
|
|
2059
|
+
const claudeDir = path11.join(dir, ".claude");
|
|
1620
2060
|
const hasClaudeDir = await fileExists(claudeDir);
|
|
1621
2061
|
let existingClaudeMd = null;
|
|
1622
2062
|
let existingSettings = null;
|
|
@@ -1628,21 +2068,21 @@ async function scanProject(dir) {
|
|
|
1628
2068
|
let mcpServerCount = 0;
|
|
1629
2069
|
let claudeMdLineCount = 0;
|
|
1630
2070
|
if (hasClaudeDir) {
|
|
1631
|
-
existingClaudeMd = await readFileSafe(
|
|
2071
|
+
existingClaudeMd = await readFileSafe(path11.join(claudeDir, "CLAUDE.md"));
|
|
1632
2072
|
if (existingClaudeMd) {
|
|
1633
2073
|
claudeMdLineCount = existingClaudeMd.split("\n").length;
|
|
1634
2074
|
}
|
|
1635
|
-
existingSettings = await readJsonSafe(
|
|
1636
|
-
existingMcpConfig = await readJsonSafe(
|
|
2075
|
+
existingSettings = await readJsonSafe(path11.join(claudeDir, "settings.json"));
|
|
2076
|
+
existingMcpConfig = await readJsonSafe(path11.join(dir, ".mcp.json"));
|
|
1637
2077
|
if (existingMcpConfig?.mcpServers) {
|
|
1638
2078
|
mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
|
|
1639
2079
|
}
|
|
1640
|
-
existingCommands = (await listDirSafe(
|
|
1641
|
-
existingRules = (await listDirSafe(
|
|
1642
|
-
existingSkills = await listDirSafe(
|
|
1643
|
-
existingAgents = (await listDirSafe(
|
|
2080
|
+
existingCommands = (await listDirSafe(path11.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
2081
|
+
existingRules = (await listDirSafe(path11.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
2082
|
+
existingSkills = await listDirSafe(path11.join(claudeDir, "skills"));
|
|
2083
|
+
existingAgents = (await listDirSafe(path11.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
1644
2084
|
}
|
|
1645
|
-
const name = pkg?.name ||
|
|
2085
|
+
const name = pkg?.name || path11.basename(dir);
|
|
1646
2086
|
const description = pkg?.description || "";
|
|
1647
2087
|
return {
|
|
1648
2088
|
name,
|
|
@@ -1687,31 +2127,31 @@ function simpleDiff(oldContent, newContent) {
|
|
|
1687
2127
|
const oldLine = oldLines[i];
|
|
1688
2128
|
const newLine = newLines[i];
|
|
1689
2129
|
if (oldLine === void 0) {
|
|
1690
|
-
output.push(
|
|
2130
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1691
2131
|
} else if (newLine === void 0) {
|
|
1692
|
-
output.push(
|
|
2132
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
1693
2133
|
} else if (oldLine !== newLine) {
|
|
1694
|
-
output.push(
|
|
1695
|
-
output.push(
|
|
2134
|
+
output.push(chalk9.red(`- ${oldLine}`));
|
|
2135
|
+
output.push(chalk9.green(`+ ${newLine}`));
|
|
1696
2136
|
}
|
|
1697
2137
|
}
|
|
1698
2138
|
return output;
|
|
1699
2139
|
}
|
|
1700
|
-
async function generateDiff(spec, targetDir) {
|
|
1701
|
-
const fileMap = buildFileMap(spec);
|
|
2140
|
+
async function generateDiff(spec, targetDir, options) {
|
|
2141
|
+
const fileMap = buildFileMap(spec, options);
|
|
1702
2142
|
const results = [];
|
|
1703
2143
|
for (const [relativePath, newContent] of fileMap) {
|
|
1704
|
-
const absolutePath =
|
|
2144
|
+
const absolutePath = path12.join(targetDir, relativePath);
|
|
1705
2145
|
let oldContent = null;
|
|
1706
2146
|
try {
|
|
1707
|
-
oldContent = await
|
|
2147
|
+
oldContent = await fs12.readFile(absolutePath, "utf-8");
|
|
1708
2148
|
} catch {
|
|
1709
2149
|
}
|
|
1710
2150
|
if (oldContent === null) {
|
|
1711
2151
|
results.push({
|
|
1712
2152
|
path: relativePath,
|
|
1713
2153
|
status: "new",
|
|
1714
|
-
diff:
|
|
2154
|
+
diff: chalk9.green("+ NEW FILE")
|
|
1715
2155
|
});
|
|
1716
2156
|
} else if (oldContent === newContent) {
|
|
1717
2157
|
results.push({
|
|
@@ -1857,7 +2297,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1857
2297
|
console.log(ui.success("No obvious issues found"));
|
|
1858
2298
|
}
|
|
1859
2299
|
if (options.auditOnly) {
|
|
1860
|
-
console.log(
|
|
2300
|
+
console.log(chalk9.dim("\n Audit complete. Run without --audit-only to generate optimized environment.\n"));
|
|
1861
2301
|
return;
|
|
1862
2302
|
}
|
|
1863
2303
|
if (!options.yes) {
|
|
@@ -1867,19 +2307,19 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1867
2307
|
default: false
|
|
1868
2308
|
});
|
|
1869
2309
|
if (!proceed) {
|
|
1870
|
-
console.log(
|
|
2310
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1871
2311
|
return;
|
|
1872
2312
|
}
|
|
1873
2313
|
}
|
|
1874
2314
|
} else {
|
|
1875
|
-
console.log(
|
|
2315
|
+
console.log(chalk9.dim("\n No existing .claude/ directory found \u2014 generating from scratch.\n"));
|
|
1876
2316
|
if (!options.yes) {
|
|
1877
2317
|
const proceed = await confirm2({
|
|
1878
2318
|
message: "Generate Claude Code environment for this project?",
|
|
1879
2319
|
default: true
|
|
1880
2320
|
});
|
|
1881
2321
|
if (!proceed) {
|
|
1882
|
-
console.log(
|
|
2322
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1883
2323
|
return;
|
|
1884
2324
|
}
|
|
1885
2325
|
}
|
|
@@ -1915,8 +2355,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1915
2355
|
console.log(ui.tool(name, tool.reason));
|
|
1916
2356
|
}
|
|
1917
2357
|
}
|
|
2358
|
+
const hasEnvVars = summary.envSetup.length > 0;
|
|
1918
2359
|
if (options.diff) {
|
|
1919
|
-
const diffs = await generateDiff(spec, targetDir);
|
|
2360
|
+
const diffs = await generateDiff(spec, targetDir, { hasEnvVars });
|
|
1920
2361
|
const changedDiffs = diffs.filter((d) => d.status !== "unchanged");
|
|
1921
2362
|
if (changedDiffs.length === 0) {
|
|
1922
2363
|
console.log(ui.success("No changes needed \u2014 environment is already up to date."));
|
|
@@ -1925,7 +2366,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1925
2366
|
}
|
|
1926
2367
|
console.log(ui.section("Changes Preview"));
|
|
1927
2368
|
for (const d of changedDiffs) {
|
|
1928
|
-
console.log(
|
|
2369
|
+
console.log(chalk9.cyan(`
|
|
1929
2370
|
--- ${d.path}`));
|
|
1930
2371
|
if (d.status === "new") {
|
|
1931
2372
|
console.log(` ${d.diff}`);
|
|
@@ -1941,7 +2382,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1941
2382
|
default: true
|
|
1942
2383
|
});
|
|
1943
2384
|
if (!apply) {
|
|
1944
|
-
console.log(
|
|
2385
|
+
console.log(chalk9.dim("\n Aborted.\n"));
|
|
1945
2386
|
return;
|
|
1946
2387
|
}
|
|
1947
2388
|
}
|
|
@@ -1952,20 +2393,14 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1952
2393
|
console.log(ui.success(`Ready! Run: $ hermes`));
|
|
1953
2394
|
console.log("");
|
|
1954
2395
|
} else {
|
|
1955
|
-
const written = await writeEnvironment(spec, targetDir);
|
|
2396
|
+
const written = await writeEnvironment(spec, targetDir, { hasEnvVars });
|
|
1956
2397
|
console.log(ui.section("Files Written"));
|
|
1957
2398
|
for (const file of written) {
|
|
1958
2399
|
console.log(ui.file(file));
|
|
1959
2400
|
}
|
|
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
|
-
}
|
|
2401
|
+
if (hasEnvVars) {
|
|
2402
|
+
await collectAndWriteKeys(summary.envSetup, targetDir);
|
|
2403
|
+
console.log("");
|
|
1969
2404
|
}
|
|
1970
2405
|
if (summary.pluginCommands.length > 0) {
|
|
1971
2406
|
console.log(ui.section("Plugins"));
|
|
@@ -1982,7 +2417,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1982
2417
|
|
|
1983
2418
|
// src/commands/doctor.ts
|
|
1984
2419
|
import { Command as Command7 } from "commander";
|
|
1985
|
-
import
|
|
2420
|
+
import chalk10 from "chalk";
|
|
1986
2421
|
function runChecks(profile) {
|
|
1987
2422
|
const checks = [];
|
|
1988
2423
|
if (!profile.existingClaudeMd) {
|
|
@@ -2114,12 +2549,12 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2114
2549
|
).action(async () => {
|
|
2115
2550
|
printFullBanner("Doctor");
|
|
2116
2551
|
const targetDir = process.cwd();
|
|
2117
|
-
console.log(
|
|
2552
|
+
console.log(chalk10.dim(" Checking .claude/ environment...\n"));
|
|
2118
2553
|
const profile = await scanProject(targetDir);
|
|
2119
2554
|
if (!profile.hasClaudeDir) {
|
|
2120
2555
|
console.log(ui.error("No .claude/ directory found.\n"));
|
|
2121
2556
|
console.log(
|
|
2122
|
-
|
|
2557
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn describe") + chalk10.dim(" or ") + chalk10.bold("kairn optimize") + chalk10.dim(" to generate one.\n")
|
|
2123
2558
|
);
|
|
2124
2559
|
process.exit(1);
|
|
2125
2560
|
}
|
|
@@ -2142,7 +2577,7 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2142
2577
|
return sum;
|
|
2143
2578
|
}, 0);
|
|
2144
2579
|
const percentage = Math.round(score / maxScore * 100);
|
|
2145
|
-
const scoreColor = percentage >= 80 ?
|
|
2580
|
+
const scoreColor = percentage >= 80 ? chalk10.green : percentage >= 50 ? chalk10.yellow : chalk10.red;
|
|
2146
2581
|
console.log(
|
|
2147
2582
|
`
|
|
2148
2583
|
Score: ${scoreColor(`${score}/${maxScore}`)} (${scoreColor(`${percentage}%`)})
|
|
@@ -2150,15 +2585,15 @@ var doctorCommand = new Command7("doctor").description(
|
|
|
2150
2585
|
);
|
|
2151
2586
|
if (percentage < 80) {
|
|
2152
2587
|
console.log(
|
|
2153
|
-
|
|
2588
|
+
chalk10.dim(" Run ") + chalk10.bold("kairn optimize") + chalk10.dim(" to fix issues.\n")
|
|
2154
2589
|
);
|
|
2155
2590
|
}
|
|
2156
2591
|
});
|
|
2157
2592
|
|
|
2158
2593
|
// src/commands/registry.ts
|
|
2159
2594
|
import { Command as Command8 } from "commander";
|
|
2160
|
-
import
|
|
2161
|
-
import { input as input2, select as
|
|
2595
|
+
import chalk11 from "chalk";
|
|
2596
|
+
import { input as input2, select as select3 } from "@inquirer/prompts";
|
|
2162
2597
|
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
2598
|
printCompactBanner();
|
|
2164
2599
|
let all;
|
|
@@ -2182,7 +2617,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2182
2617
|
);
|
|
2183
2618
|
}
|
|
2184
2619
|
if (tools.length === 0) {
|
|
2185
|
-
console.log(
|
|
2620
|
+
console.log(chalk11.dim("\n No tools found.\n"));
|
|
2186
2621
|
return;
|
|
2187
2622
|
}
|
|
2188
2623
|
const bundledCount = all.filter((t) => !userIds.has(t.id)).length;
|
|
@@ -2196,13 +2631,13 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2196
2631
|
`tier ${tool.tier}`,
|
|
2197
2632
|
tool.auth
|
|
2198
2633
|
].join(", ");
|
|
2199
|
-
console.log(` ${ui.accent(tool.id)}` +
|
|
2200
|
-
console.log(
|
|
2634
|
+
console.log(` ${ui.accent(tool.id)}` + chalk11.dim(` (${meta})`));
|
|
2635
|
+
console.log(chalk11.dim(` ${tool.description}`));
|
|
2201
2636
|
if (tool.best_for.length > 0) {
|
|
2202
|
-
console.log(
|
|
2637
|
+
console.log(chalk11.dim(` Best for: ${tool.best_for.join(", ")}`));
|
|
2203
2638
|
}
|
|
2204
2639
|
if (isUser) {
|
|
2205
|
-
console.log(
|
|
2640
|
+
console.log(chalk11.yellow(" [USER-DEFINED]"));
|
|
2206
2641
|
}
|
|
2207
2642
|
console.log("");
|
|
2208
2643
|
}
|
|
@@ -2210,7 +2645,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
|
|
|
2210
2645
|
const shownUser = tools.filter((t) => userIds.has(t.id)).length;
|
|
2211
2646
|
const shownBundled = totalShown - shownUser;
|
|
2212
2647
|
console.log(
|
|
2213
|
-
|
|
2648
|
+
chalk11.dim(
|
|
2214
2649
|
` ${totalShown} tool${totalShown !== 1 ? "s" : ""} (${shownBundled} bundled, ${shownUser} user-defined)`
|
|
2215
2650
|
) + "\n"
|
|
2216
2651
|
);
|
|
@@ -2228,7 +2663,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
|
|
|
2228
2663
|
});
|
|
2229
2664
|
const name = await input2({ message: "Display name" });
|
|
2230
2665
|
const description = await input2({ message: "Description" });
|
|
2231
|
-
const category = await
|
|
2666
|
+
const category = await select3({
|
|
2232
2667
|
message: "Category",
|
|
2233
2668
|
choices: [
|
|
2234
2669
|
{ value: "universal" },
|
|
@@ -2242,7 +2677,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
|
|
|
2242
2677
|
{ value: "sandbox" }
|
|
2243
2678
|
]
|
|
2244
2679
|
});
|
|
2245
|
-
const tier = await
|
|
2680
|
+
const tier = await select3({
|
|
2246
2681
|
message: "Tier",
|
|
2247
2682
|
choices: [
|
|
2248
2683
|
{ name: "1 \u2014 Universal", value: 1 },
|
|
@@ -2250,7 +2685,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
|
|
|
2250
2685
|
{ name: "3 \u2014 Specialized", value: 3 }
|
|
2251
2686
|
]
|
|
2252
2687
|
});
|
|
2253
|
-
const type = await
|
|
2688
|
+
const type = await select3({
|
|
2254
2689
|
message: "Type",
|
|
2255
2690
|
choices: [
|
|
2256
2691
|
{ value: "mcp_server" },
|
|
@@ -2258,7 +2693,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
|
|
|
2258
2693
|
{ value: "hook" }
|
|
2259
2694
|
]
|
|
2260
2695
|
});
|
|
2261
|
-
const auth = await
|
|
2696
|
+
const auth = await select3({
|
|
2262
2697
|
message: "Auth",
|
|
2263
2698
|
choices: [
|
|
2264
2699
|
{ value: "none" },
|
|
@@ -2274,7 +2709,7 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
|
|
|
2274
2709
|
const varName = await input2({ message: "Env var name" });
|
|
2275
2710
|
const varDesc = await input2({ message: "Env var description" });
|
|
2276
2711
|
env_vars.push({ name: varName, description: varDesc });
|
|
2277
|
-
const another = await
|
|
2712
|
+
const another = await select3({
|
|
2278
2713
|
message: "Add another env var?",
|
|
2279
2714
|
choices: [
|
|
2280
2715
|
{ name: "No", value: false },
|
|
@@ -2334,20 +2769,20 @@ var registryCommand = new Command8("registry").description("Manage the tool regi
|
|
|
2334
2769
|
|
|
2335
2770
|
// src/commands/templates.ts
|
|
2336
2771
|
import { Command as Command9 } from "commander";
|
|
2337
|
-
import
|
|
2338
|
-
import
|
|
2339
|
-
import
|
|
2772
|
+
import chalk12 from "chalk";
|
|
2773
|
+
import fs13 from "fs/promises";
|
|
2774
|
+
import path13 from "path";
|
|
2340
2775
|
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
2776
|
printCompactBanner();
|
|
2342
2777
|
const templatesDir = getTemplatesDir();
|
|
2343
2778
|
let files;
|
|
2344
2779
|
try {
|
|
2345
|
-
files = await
|
|
2780
|
+
files = await fs13.readdir(templatesDir);
|
|
2346
2781
|
} catch {
|
|
2347
2782
|
console.log(
|
|
2348
|
-
|
|
2783
|
+
chalk12.dim(
|
|
2349
2784
|
" No templates found. Templates will be installed with "
|
|
2350
|
-
) +
|
|
2785
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2351
2786
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2352
2787
|
)
|
|
2353
2788
|
);
|
|
@@ -2356,9 +2791,9 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2356
2791
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
2357
2792
|
if (jsonFiles.length === 0) {
|
|
2358
2793
|
console.log(
|
|
2359
|
-
|
|
2794
|
+
chalk12.dim(
|
|
2360
2795
|
" No templates found. Templates will be installed with "
|
|
2361
|
-
) +
|
|
2796
|
+
) + chalk12.bold("kairn init") + chalk12.dim(
|
|
2362
2797
|
" or you can add .json files to ~/.kairn/templates/\n"
|
|
2363
2798
|
)
|
|
2364
2799
|
);
|
|
@@ -2367,8 +2802,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2367
2802
|
const templates = [];
|
|
2368
2803
|
for (const file of jsonFiles) {
|
|
2369
2804
|
try {
|
|
2370
|
-
const data = await
|
|
2371
|
-
|
|
2805
|
+
const data = await fs13.readFile(
|
|
2806
|
+
path13.join(templatesDir, file),
|
|
2372
2807
|
"utf-8"
|
|
2373
2808
|
);
|
|
2374
2809
|
const spec = JSON.parse(data);
|
|
@@ -2386,7 +2821,7 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2386
2821
|
}
|
|
2387
2822
|
if (filtered.length === 0) {
|
|
2388
2823
|
console.log(
|
|
2389
|
-
|
|
2824
|
+
chalk12.dim(` No templates matched category "${options.category}".
|
|
2390
2825
|
`)
|
|
2391
2826
|
);
|
|
2392
2827
|
return;
|
|
@@ -2397,8 +2832,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2397
2832
|
const toolCount = spec.tools?.length ?? 0;
|
|
2398
2833
|
const commandCount = Object.keys(spec.harness?.commands ?? {}).length;
|
|
2399
2834
|
const ruleCount = Object.keys(spec.harness?.rules ?? {}).length;
|
|
2400
|
-
console.log(ui.kv("Name",
|
|
2401
|
-
console.log(ui.kv("ID",
|
|
2835
|
+
console.log(ui.kv("Name", chalk12.bold(spec.name)));
|
|
2836
|
+
console.log(ui.kv("ID", chalk12.dim(spec.id)));
|
|
2402
2837
|
console.log(ui.kv("Description", spec.description));
|
|
2403
2838
|
console.log(
|
|
2404
2839
|
ui.kv("Contents", `${toolCount} tools \xB7 ${commandCount} commands \xB7 ${ruleCount} rules`)
|
|
@@ -2406,16 +2841,139 @@ var templatesCommand = new Command9("templates").description("Browse available t
|
|
|
2406
2841
|
console.log("");
|
|
2407
2842
|
}
|
|
2408
2843
|
console.log(
|
|
2409
|
-
|
|
2844
|
+
chalk12.dim(` ${filtered.length} template${filtered.length === 1 ? "" : "s"} available
|
|
2410
2845
|
`)
|
|
2411
2846
|
);
|
|
2412
2847
|
});
|
|
2413
2848
|
|
|
2849
|
+
// src/commands/keys.ts
|
|
2850
|
+
import { Command as Command10 } from "commander";
|
|
2851
|
+
import { password as password3 } from "@inquirer/prompts";
|
|
2852
|
+
import chalk13 from "chalk";
|
|
2853
|
+
import fs14 from "fs/promises";
|
|
2854
|
+
import path14 from "path";
|
|
2855
|
+
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) => {
|
|
2856
|
+
printCompactBanner();
|
|
2857
|
+
const targetDir = process.cwd();
|
|
2858
|
+
const requiredVars = await detectRequiredEnvVars(targetDir);
|
|
2859
|
+
if (requiredVars.length === 0) {
|
|
2860
|
+
console.log(
|
|
2861
|
+
ui.info("No MCP servers found in .mcp.json \u2014 no API keys needed.")
|
|
2862
|
+
);
|
|
2863
|
+
console.log("");
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
const existing = await readEnvFile(targetDir);
|
|
2867
|
+
const registry = await loadRegistry();
|
|
2868
|
+
const envSetupMap = /* @__PURE__ */ new Map();
|
|
2869
|
+
for (const tool of registry) {
|
|
2870
|
+
if (!tool.env_vars) continue;
|
|
2871
|
+
for (const ev of tool.env_vars) {
|
|
2872
|
+
if (requiredVars.includes(ev.name)) {
|
|
2873
|
+
envSetupMap.set(ev.name, {
|
|
2874
|
+
toolName: tool.name,
|
|
2875
|
+
envVar: ev.name,
|
|
2876
|
+
description: ev.description,
|
|
2877
|
+
signupUrl: tool.signup_url
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
for (const varName of requiredVars) {
|
|
2883
|
+
if (!envSetupMap.has(varName)) {
|
|
2884
|
+
envSetupMap.set(varName, {
|
|
2885
|
+
toolName: "unknown",
|
|
2886
|
+
envVar: varName,
|
|
2887
|
+
description: "Required by MCP server"
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
if (options.show) {
|
|
2892
|
+
console.log(ui.section("API Key Status"));
|
|
2893
|
+
console.log("");
|
|
2894
|
+
for (const varName of requiredVars) {
|
|
2895
|
+
const value = existing.get(varName);
|
|
2896
|
+
const info = envSetupMap.get(varName);
|
|
2897
|
+
const toolLabel = info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "";
|
|
2898
|
+
if (value && value.length > 0) {
|
|
2899
|
+
const masked = value.slice(0, 4) + "\u2022".repeat(Math.max(0, value.length - 4));
|
|
2900
|
+
console.log(chalk13.green(` \u2713 ${varName}`) + toolLabel + chalk13.dim(` = ${masked}`));
|
|
2901
|
+
} else {
|
|
2902
|
+
console.log(chalk13.yellow(` \u2717 ${varName}`) + toolLabel + chalk13.dim(" = (not set)"));
|
|
2903
|
+
if (info?.signupUrl) {
|
|
2904
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
const setCount = requiredVars.filter((v) => {
|
|
2909
|
+
const val = existing.get(v);
|
|
2910
|
+
return val && val.length > 0;
|
|
2911
|
+
}).length;
|
|
2912
|
+
const missingCount = requiredVars.length - setCount;
|
|
2913
|
+
console.log("");
|
|
2914
|
+
if (missingCount === 0) {
|
|
2915
|
+
console.log(ui.success(`All ${setCount} key(s) configured`));
|
|
2916
|
+
} else {
|
|
2917
|
+
console.log(
|
|
2918
|
+
ui.warn(`${missingCount} key(s) missing \u2014 run ${chalk13.bold("kairn keys")} to add them`)
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
console.log("");
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
const missing = requiredVars.filter((v) => {
|
|
2925
|
+
const val = existing.get(v);
|
|
2926
|
+
return !val || val.length === 0;
|
|
2927
|
+
});
|
|
2928
|
+
if (missing.length === 0) {
|
|
2929
|
+
console.log(ui.success("All API keys are already configured."));
|
|
2930
|
+
console.log(chalk13.dim(" Use --show to see current keys.\n"));
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
console.log(ui.section("API Keys"));
|
|
2934
|
+
console.log(chalk13.dim(` ${missing.length} key(s) need to be set. Press Enter to skip.
|
|
2935
|
+
`));
|
|
2936
|
+
const envEntries = new Map(existing);
|
|
2937
|
+
let keysEntered = 0;
|
|
2938
|
+
for (const varName of missing) {
|
|
2939
|
+
const info = envSetupMap.get(varName);
|
|
2940
|
+
console.log(
|
|
2941
|
+
chalk13.bold(` ${varName}`) + (info?.toolName !== "unknown" ? chalk13.dim(` (${info?.toolName})`) : "")
|
|
2942
|
+
);
|
|
2943
|
+
if (info?.signupUrl) {
|
|
2944
|
+
console.log(chalk13.dim(` Get one at: ${info.signupUrl}`));
|
|
2945
|
+
}
|
|
2946
|
+
const value = await password3({
|
|
2947
|
+
message: varName,
|
|
2948
|
+
mask: "\u2022"
|
|
2949
|
+
});
|
|
2950
|
+
if (value && value.trim()) {
|
|
2951
|
+
envEntries.set(varName, value.trim());
|
|
2952
|
+
console.log(chalk13.green(" \u2713 saved\n"));
|
|
2953
|
+
keysEntered++;
|
|
2954
|
+
} else {
|
|
2955
|
+
console.log(chalk13.dim(" (skipped)\n"));
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
const envLines = [
|
|
2959
|
+
"# Generated by Kairn \u2014 API keys for MCP servers",
|
|
2960
|
+
"# Do NOT commit this file to git",
|
|
2961
|
+
""
|
|
2962
|
+
];
|
|
2963
|
+
for (const [key, value] of envEntries) {
|
|
2964
|
+
envLines.push(`${key}=${value}`);
|
|
2965
|
+
}
|
|
2966
|
+
const envPath = path14.join(targetDir, ".env");
|
|
2967
|
+
await fs14.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
|
|
2968
|
+
console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
|
|
2969
|
+
console.log("");
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2414
2972
|
// src/cli.ts
|
|
2415
|
-
var program = new
|
|
2973
|
+
var program = new Command11();
|
|
2416
2974
|
program.name("kairn").description(
|
|
2417
2975
|
"Compile natural language intent into optimized Claude Code environments"
|
|
2418
|
-
).version("1.
|
|
2976
|
+
).version("1.9.0").option("--no-color", "Disable colored output");
|
|
2419
2977
|
program.addCommand(initCommand);
|
|
2420
2978
|
program.addCommand(describeCommand);
|
|
2421
2979
|
program.addCommand(optimizeCommand);
|
|
@@ -2425,8 +2983,9 @@ program.addCommand(updateRegistryCommand);
|
|
|
2425
2983
|
program.addCommand(doctorCommand);
|
|
2426
2984
|
program.addCommand(registryCommand);
|
|
2427
2985
|
program.addCommand(templatesCommand);
|
|
2986
|
+
program.addCommand(keysCommand);
|
|
2428
2987
|
if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
|
|
2429
|
-
|
|
2988
|
+
chalk14.level = 0;
|
|
2430
2989
|
}
|
|
2431
2990
|
program.parse();
|
|
2432
2991
|
//# sourceMappingURL=cli.js.map
|