sandstream-kit 1.1.0 → 1.3.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.
Files changed (129) hide show
  1. package/README.md +101 -92
  2. package/dist/adapters/expo-eas.js +1 -1
  3. package/dist/adapters/expo-eas.js.map +1 -1
  4. package/dist/audit-logging-service.js +1 -1
  5. package/dist/audit-logging-service.js.map +1 -1
  6. package/dist/author-verification.js +1 -1
  7. package/dist/author-verification.js.map +1 -1
  8. package/dist/check-web-search.js +37 -6
  9. package/dist/check-web-search.js.map +1 -1
  10. package/dist/cli-shared.d.ts +4 -0
  11. package/dist/cli-shared.js +11 -0
  12. package/dist/cli-shared.js.map +1 -0
  13. package/dist/cli.js +175 -1189
  14. package/dist/cli.js.map +1 -1
  15. package/dist/commands/audit.d.ts +1 -0
  16. package/dist/commands/audit.js +99 -0
  17. package/dist/commands/audit.js.map +1 -0
  18. package/dist/commands/auth.d.ts +1 -0
  19. package/dist/commands/auth.js +121 -0
  20. package/dist/commands/auth.js.map +1 -0
  21. package/dist/commands/env.d.ts +1 -0
  22. package/dist/commands/env.js +149 -0
  23. package/dist/commands/env.js.map +1 -0
  24. package/dist/commands/hooks.d.ts +1 -0
  25. package/dist/commands/hooks.js +146 -0
  26. package/dist/commands/hooks.js.map +1 -0
  27. package/dist/commands/mcp.d.ts +1 -0
  28. package/dist/commands/mcp.js +120 -0
  29. package/dist/commands/mcp.js.map +1 -0
  30. package/dist/commands/memory.d.ts +1 -0
  31. package/dist/commands/memory.js +534 -0
  32. package/dist/commands/memory.js.map +1 -0
  33. package/dist/config.d.ts +2 -0
  34. package/dist/config.js +4 -0
  35. package/dist/config.js.map +1 -1
  36. package/dist/mcp-server.js +59 -6
  37. package/dist/mcp-server.js.map +1 -1
  38. package/dist/memory/amazonq.d.ts +5 -0
  39. package/dist/memory/amazonq.js +161 -0
  40. package/dist/memory/amazonq.js.map +1 -0
  41. package/dist/memory/cline.d.ts +5 -0
  42. package/dist/memory/cline.js +117 -0
  43. package/dist/memory/cline.js.map +1 -0
  44. package/dist/memory/codex.d.ts +5 -0
  45. package/dist/memory/codex.js +150 -0
  46. package/dist/memory/codex.js.map +1 -0
  47. package/dist/memory/continue.d.ts +5 -0
  48. package/dist/memory/continue.js +116 -0
  49. package/dist/memory/continue.js.map +1 -0
  50. package/dist/memory/cursor.d.ts +4 -0
  51. package/dist/memory/cursor.js +116 -0
  52. package/dist/memory/cursor.js.map +1 -0
  53. package/dist/memory/db.d.ts +12 -1
  54. package/dist/memory/db.js +43 -1
  55. package/dist/memory/db.js.map +1 -1
  56. package/dist/memory/gemini.d.ts +5 -0
  57. package/dist/memory/gemini.js +176 -0
  58. package/dist/memory/gemini.js.map +1 -0
  59. package/dist/memory/hook.d.ts +9 -0
  60. package/dist/memory/hook.js +34 -1
  61. package/dist/memory/hook.js.map +1 -1
  62. package/dist/memory/install.js +1 -0
  63. package/dist/memory/install.js.map +1 -1
  64. package/dist/memory/merge.d.ts +18 -0
  65. package/dist/memory/merge.js +109 -0
  66. package/dist/memory/merge.js.map +1 -0
  67. package/dist/memory/parser.d.ts +9 -0
  68. package/dist/memory/parser.js +40 -3
  69. package/dist/memory/parser.js.map +1 -1
  70. package/dist/memory/suggest.d.ts +21 -0
  71. package/dist/memory/suggest.js +36 -0
  72. package/dist/memory/suggest.js.map +1 -0
  73. package/dist/onepassword.js +1 -1
  74. package/dist/onepassword.js.map +1 -1
  75. package/dist/open.js +8 -2
  76. package/dist/open.js.map +1 -1
  77. package/dist/run.js +1 -1
  78. package/dist/run.js.map +1 -1
  79. package/dist/service-auth.d.ts +31 -0
  80. package/dist/service-auth.js +47 -0
  81. package/dist/service-auth.js.map +1 -0
  82. package/dist/stack-detector.js +53 -76
  83. package/dist/stack-detector.js.map +1 -1
  84. package/dist/status.d.ts +9 -0
  85. package/dist/status.js +119 -0
  86. package/dist/status.js.map +1 -0
  87. package/package.json +9 -4
  88. package/dist/memory/backup 2.d.ts +0 -6
  89. package/dist/memory/backup 2.js +0 -80
  90. package/dist/memory/backup 2.js.map +0 -1
  91. package/dist/memory/db 2.d.ts +0 -40
  92. package/dist/memory/db 2.js +0 -233
  93. package/dist/memory/db 2.js.map +0 -1
  94. package/dist/memory/hook 2.d.ts +0 -6
  95. package/dist/memory/hook 2.js +0 -51
  96. package/dist/memory/hook 2.js.map +0 -1
  97. package/dist/memory/hook.test 2.d.ts +0 -1
  98. package/dist/memory/hook.test 2.js +0 -35
  99. package/dist/memory/hook.test 2.js.map +0 -1
  100. package/dist/memory/install 2.d.ts +0 -8
  101. package/dist/memory/install 2.js +0 -72
  102. package/dist/memory/install 2.js.map +0 -1
  103. package/dist/memory/install.test 2.d.ts +0 -1
  104. package/dist/memory/install.test 2.js +0 -59
  105. package/dist/memory/install.test 2.js.map +0 -1
  106. package/dist/memory/pal 2.d.ts +0 -47
  107. package/dist/memory/pal 2.js +0 -154
  108. package/dist/memory/pal 2.js.map +0 -1
  109. package/dist/memory/project 2.d.ts +0 -1
  110. package/dist/memory/project 2.js +0 -24
  111. package/dist/memory/project 2.js.map +0 -1
  112. package/dist/memory/scan 2.d.ts +0 -15
  113. package/dist/memory/scan 2.js +0 -94
  114. package/dist/memory/scan 2.js.map +0 -1
  115. package/dist/memory/scan.test 2.d.ts +0 -1
  116. package/dist/memory/scan.test 2.js +0 -55
  117. package/dist/memory/scan.test 2.js.map +0 -1
  118. package/dist/memory/shared 2.d.ts +0 -33
  119. package/dist/memory/shared 2.js +0 -120
  120. package/dist/memory/shared 2.js.map +0 -1
  121. package/dist/memory/threads 2.d.ts +0 -37
  122. package/dist/memory/threads 2.js +0 -50
  123. package/dist/memory/threads 2.js.map +0 -1
  124. package/dist/memory/threads.test 2.d.ts +0 -1
  125. package/dist/memory/threads.test 2.js +0 -66
  126. package/dist/memory/threads.test 2.js.map +0 -1
  127. package/dist/memory/types 2.d.ts +0 -52
  128. package/dist/memory/types 2.js +0 -5
  129. package/dist/memory/types 2.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, existsSync } from "node:fs";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
3
  import { writeFile, access, mkdir } from "node:fs/promises";
4
4
  import { fileURLToPath } from "node:url";
5
- import { resolve, dirname, join, basename } from "node:path";
6
- import { loadConfig, resolveActiveEnvironment } from "./config.js";
5
+ import { resolve, dirname, join } from "node:path";
6
+ import { loadConfig } from "./config.js";
7
7
  import { checkTools } from "./check-tools.js";
8
8
  import { checkServices } from "./check-services.js";
9
9
  import { checkSecrets } from "./check-secrets.js";
@@ -20,17 +20,16 @@ import { scanPlaintextSecrets } from "./scan-plaintext.js";
20
20
  import { planMigration, writeSecretToBackend, commentOutInFile, } from "./secrets-migrate.js";
21
21
  import { analyzeRepo, renderClaudeMd, renderRulesMd } from "./analyze.js";
22
22
  import { cmdSecretsRotate, pickBackendOpts } from "./secrets-rotate-cli.js";
23
+ import { setSecretValue } from "./secrets-set.js";
23
24
  import { detectTools as detectPurgeTools, previewMatches, purgeHistory, } from "./secrets-purge-history.js";
24
25
  import { propagate, parseTargets, ALL_TARGETS, } from "./secrets-propagate.js";
25
26
  import { initAllowlist, checkAllowlist, addToAllowlist, checkSecretPolicy, } from "./security-policy.js";
26
27
  import { clearBumblebeeCache } from "./bumblebee.js";
27
- import { KNOWN_ENVS, readActiveEnv, writeActiveEnv, } from "./env-switch.js";
28
28
  import { scanStagedFiles } from "./scan-staged.js";
29
29
  import { scanBuildArtifacts } from "./scan-build.js";
30
30
  import { scanTranscripts } from "./scan-transcripts.js";
31
31
  import { sampleCosts } from "./cost-monitor.js";
32
- import { readSecretAuditEvents, groupBySecret, summarize, } from "./audit-secrets.js";
33
- import { requireElevation, consumeElevation, grantElevation, clearElevation, readElevation, verifyTotp, elevationTtlMinutes, enrollTotp, resolveTotpSecret, } from "./elevation.js";
32
+ import { requireElevation, consumeElevation } from "./elevation.js";
34
33
  import { checkGitignore, patchGitignore, findCommittedSensitive, } from "./check-gitignore.js";
35
34
  import { auditPull, reportSeverity } from "./post-pull-audit.js";
36
35
  import { checkOneCliStatus, registerSecretInOneCli, generatePlaceholder, resolveOneCliConfig, } from "./secrets-onecli.js";
@@ -41,23 +40,28 @@ import { checkForUpdate, printUpdateNotice } from "./update-check.js";
41
40
  import { checkSkills } from "./check-skills.js";
42
41
  import { checkLockFiles } from "./check-lock.js";
43
42
  import { collectEscalations, formatEscalationMessage } from "./escalate.js";
44
- import { printToolsTable, printServicesTable, printSecretsTable, printSkillsTable, printWebSearchStatus, printSecurityTable, printLockTable, printSummary, printAuditTable, runStep, stepHeader, } from "./output.js";
43
+ import { printToolsTable, printServicesTable, printSecretsTable, printSkillsTable, printWebSearchStatus, printSecurityTable, printLockTable, printSummary, runStep, stepHeader, } from "./output.js";
45
44
  import { checkRevocationStatus } from "./revocation.js";
46
45
  import { getBudgetStatus, formatBudgetStatus } from "./budget.js";
47
- import { readAuditLog } from "./audit.js";
48
46
  import { formatGovernanceStatus, mergeGovernanceConfigAsync } from "./governance.js";
49
47
  import { withGovernance } from "./governance-middleware.js";
50
- import { installHooks, SKIPPED_COMMITS_LOG } from "./hooks.js";
48
+ import { SKIPPED_COMMITS_LOG } from "./hooks.js";
51
49
  import { writeAgentConfig, detectAgentTargets } from "./agent-config.js";
52
50
  import { checkHooks, isGitRepository } from "./check-hooks.js";
53
51
  import { readkitMeta, readSkillsLock, readCliLock, updateSkillsLock, updateCliLock, } from "./lock.js";
54
52
  import { provisionService, listAvailableServices, getServiceInfo } from "./provision.js";
55
53
  import { cmdFix } from "./fix.js";
56
54
  import { promptConfirm } from "./utils/prompt.js";
57
- import { startMcpServer } from "./mcp-server.js";
58
55
  import { c } from "./utils/colors.js";
56
+ import { gatherStatus } from "./status.js";
57
+ import { KIT_FILE, resolveConfigPath } from "./cli-shared.js";
58
+ import { cmdEnv } from "./commands/env.js";
59
+ import { cmdAuth } from "./commands/auth.js";
60
+ import { cmdAudit } from "./commands/audit.js";
61
+ import { cmdMcp } from "./commands/mcp.js";
62
+ import { cmdHooks } from "./commands/hooks.js";
63
+ import { resolveAllAuth } from "./service-auth.js";
59
64
  import { runDoctor } from "./doctor.js";
60
- import { inspectEnv } from "./env-inspect.js";
61
65
  import { detectStack } from "./stack-detector.js";
62
66
  import { generateToml } from "./toml-generator.js";
63
67
  import { createPlugin } from "./create-plugin.js";
@@ -68,22 +72,9 @@ import { listServices, openService } from "./open.js";
68
72
  import { gatherProjectContext } from "./context.js";
69
73
  import { runTriage, listTriageTools } from "./triage.js";
70
74
  import { parsePkgSpec, installPkg } from "./pkg.js";
71
- import { openMemoryDb, getStats, getMemoryDbPath, searchMessages } from "./memory/db.js";
72
- import { indexClaudeTranscripts, getClaudeProjectsDir } from "./memory/parser.js";
73
- import { getCurrentProjectRoot } from "./memory/project.js";
74
- import { scanDbForSecrets } from "./memory/scan.js";
75
- import { backupEncrypted, restoreEncrypted } from "./memory/backup.js";
76
- import { shareEntry, listAreas, queryArea, getSharedPath, } from "./memory/shared.js";
77
- import { userPromptSubmitReminder, runSessionEndIndex } from "./memory/hook.js";
78
- import { installMemoryHooks, uninstallMemoryHooks, getClaudeSettingsPath, } from "./memory/install.js";
79
- import { palAdd, palList, palDone, palSnooze, palAutoVerify, importLegacyLedger, } from "./memory/pal.js";
80
- import { saveThread, listThreads, removeThread, latestSessionId, resolveThread, } from "./memory/threads.js";
81
- const KIT_FILE = ".kit.toml";
75
+ import { cmdMemory } from "./commands/memory.js";
82
76
  const __dirname = dirname(fileURLToPath(import.meta.url));
83
77
  const KIT_VERSION = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
84
- function resolveConfigPath() {
85
- return resolve(process.cwd(), KIT_FILE);
86
- }
87
78
  async function cmdCheck() {
88
79
  const jsonMode = hasFlag(process.argv, "--json");
89
80
  const enforceTests = hasFlag(process.argv, "--enforce-tests");
@@ -469,6 +460,23 @@ async function cmdLogin() {
469
460
  servicesConfig = { [serviceFilter]: servicesConfig[serviceFilter] };
470
461
  console.log(`${c.dim}Filtering to service "${serviceFilter}"${retryCount ? ` (retries=${retryCount})` : ""}${c.reset}`);
471
462
  }
463
+ // `--plan`: read-only. Show the resolved auth strategy per service (vault /
464
+ // interactive / capture, + passkey warnings) without logging in to anything.
465
+ if (hasFlag(args, "--plan")) {
466
+ const plan = resolveAllAuth(servicesConfig);
467
+ if (hasFlag(args, "--json")) {
468
+ console.log(JSON.stringify(plan, null, 2));
469
+ return true;
470
+ }
471
+ console.log(`${c.bold}auth plan${c.reset} ${c.dim}${plan.length} service(s)${c.reset}`);
472
+ for (const p of plan) {
473
+ const tag = p.passkey
474
+ ? `${c.yellow}${p.strategy} ⚿${c.reset}`
475
+ : `${c.cyan}${p.strategy}${c.reset}`;
476
+ console.log(` ${tag} ${p.name} ${c.dim}${p.instruction}${c.reset}`);
477
+ }
478
+ return true;
479
+ }
472
480
  console.log(`${c.bold}${c.cyan}Authenticating services...${c.reset}`);
473
481
  return await withGovernance(config, {
474
482
  operation: "services.login",
@@ -546,6 +554,9 @@ async function cmdSecrets() {
546
554
  if (process.argv[3] === "propagate") {
547
555
  return cmdSecretsPropagateStandalone();
548
556
  }
557
+ if (process.argv[3] === "set") {
558
+ return cmdSecretsSet();
559
+ }
549
560
  if (process.argv[3] === "revoke-old") {
550
561
  return cmdSecretsRevokeOld();
551
562
  }
@@ -1023,612 +1034,6 @@ async function offerFirstInstallPrescan() {
1023
1034
  }
1024
1035
  console.log();
1025
1036
  }
1026
- /** Built-in pre-commit hook: scans staged files for known credential patterns. */
1027
- const BUILTIN_HOOKS = {
1028
- "secret-scan": {
1029
- hookName: "pre-commit",
1030
- commands: ["kit security scan-staged"],
1031
- description: "Block commits that stage known credential patterns (Stripe, AWS, JWT, etc).",
1032
- },
1033
- "post-pull-audit": {
1034
- hookName: "post-merge",
1035
- commands: ["kit security verify-pull"],
1036
- description: "After git pull/merge, audit new deps, gitignore drops, and introduced secrets.",
1037
- },
1038
- };
1039
- async function cmdHooks() {
1040
- const subcommand = process.argv[3];
1041
- // Built-in hooks bypass the .kit.toml config path so they're available
1042
- // on any repo, including ones that haven't run `kit init` yet.
1043
- if (subcommand === "add") {
1044
- return cmdHooksAdd();
1045
- }
1046
- const config = await loadConfig(resolveConfigPath());
1047
- if (!config.hooks || Object.keys(config.hooks).length === 0) {
1048
- console.log(`${c.dim}No hooks configured in ${KIT_FILE}${c.reset}`);
1049
- return true;
1050
- }
1051
- if (!isGitRepository()) {
1052
- console.log(`${c.red}Not a git repository${c.reset}`);
1053
- return false;
1054
- }
1055
- if (subcommand === "install" || !subcommand) {
1056
- console.log(`${c.bold}${c.cyan}Installing git hooks...${c.reset}\n`);
1057
- const results = await installHooks(config.hooks);
1058
- let allOk = true;
1059
- for (const r of results) {
1060
- const icon = r.action === "failed"
1061
- ? `${c.red}✗${c.reset}`
1062
- : `${c.green}✓${c.reset}`;
1063
- const label = r.action === "installed"
1064
- ? `${c.green}installed${c.reset}`
1065
- : r.action === "updated"
1066
- ? `${c.green}updated${c.reset}`
1067
- : r.action === "skipped"
1068
- ? `${c.dim}skipped${c.reset}`
1069
- : `${c.red}failed${c.reset}`;
1070
- console.log(` ${icon} ${r.hookName} ${label} ${c.dim}${r.detail}${c.reset}`);
1071
- if (r.action === "failed")
1072
- allOk = false;
1073
- }
1074
- console.log();
1075
- return allOk;
1076
- }
1077
- else if (subcommand === "check") {
1078
- console.log(`${c.bold}${c.cyan}Git Hooks${c.reset}\n`);
1079
- const results = await checkHooks(config.hooks);
1080
- let allOk = true;
1081
- for (const r of results) {
1082
- const icon = !r.installed
1083
- ? `${c.red}✗${c.reset}`
1084
- : !r.upToDate
1085
- ? `${c.yellow}!${c.reset}`
1086
- : `${c.green}✓${c.reset}`;
1087
- const status = !r.installed
1088
- ? `${c.red}not installed${c.reset}`
1089
- : !r.upToDate
1090
- ? `${c.yellow}outdated${c.reset}`
1091
- : `${c.green}up-to-date${c.reset}`;
1092
- console.log(` ${icon} ${r.hookName} ${status} ${c.dim}${r.detail}${c.reset}`);
1093
- if (!r.installed || !r.upToDate)
1094
- allOk = false;
1095
- }
1096
- if (!allOk) {
1097
- console.log(`\n${c.dim}Run ${c.reset}${c.bold}kit hooks install${c.reset}${c.dim} to install/update hooks${c.reset}`);
1098
- }
1099
- console.log();
1100
- return allOk;
1101
- }
1102
- else {
1103
- console.error(`Unknown hooks subcommand: ${subcommand}`);
1104
- console.error(`Usage: kit hooks [install|check|add <name>]`);
1105
- return false;
1106
- }
1107
- }
1108
- async function cmdHooksAdd() {
1109
- const name = process.argv[4];
1110
- if (!name) {
1111
- console.error(`${c.red}Usage: kit hooks add <name>${c.reset}\n${c.dim}Available built-in hooks:${c.reset}`);
1112
- for (const [k, v] of Object.entries(BUILTIN_HOOKS)) {
1113
- console.error(` ${c.bold}${k}${c.reset} (git ${v.hookName}) — ${v.description}`);
1114
- }
1115
- return false;
1116
- }
1117
- const builtin = BUILTIN_HOOKS[name];
1118
- if (!builtin) {
1119
- console.error(`${c.red}No built-in hook named "${name}"${c.reset}`);
1120
- console.error(`${c.dim}Available: ${Object.keys(BUILTIN_HOOKS).join(", ")}${c.reset}`);
1121
- return false;
1122
- }
1123
- if (!isGitRepository()) {
1124
- console.error(`${c.red}Not a git repository${c.reset}`);
1125
- return false;
1126
- }
1127
- console.log(`${c.bold}${c.cyan}kit hooks add ${name}${c.reset} ${c.dim}(git ${builtin.hookName})${c.reset}`);
1128
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1129
- // Check whether the target hook is already present and contains a kit-
1130
- // managed step. If so, just confirm without re-writing.
1131
- const { hooksDir } = await ensureHooksDir();
1132
- const hookPath = `${hooksDir}/${builtin.hookName}`;
1133
- const { existsSync, readFileSync } = await import("node:fs");
1134
- if (existsSync(hookPath)) {
1135
- const existing = readFileSync(hookPath, "utf-8");
1136
- if (existing.includes(builtin.commands[0])) {
1137
- console.log(` ${c.green}✓${c.reset} ${builtin.hookName} already has ${c.bold}${builtin.commands[0]}${c.reset}\n`);
1138
- return true;
1139
- }
1140
- console.log(`${c.yellow}⚠ ${builtin.hookName} already exists at ${hookPath}.${c.reset}`);
1141
- console.log(`${c.dim}kit will overwrite it with a managed version that calls back into kit.${c.reset}\n`);
1142
- if (!isNonInteractive()) {
1143
- const ok = await promptConfirm(`Overwrite? [Y/n] (auto-yes in 8s): `, 8000);
1144
- if (!ok) {
1145
- console.log(`${c.dim}Aborted.${c.reset}`);
1146
- return false;
1147
- }
1148
- }
1149
- }
1150
- const results = await installHooks({ [builtin.hookName]: builtin.commands });
1151
- const r = results[0];
1152
- if (r.action === "failed") {
1153
- console.error(`${c.red}✗ install failed: ${r.detail}${c.reset}`);
1154
- return false;
1155
- }
1156
- console.log(` ${c.green}✓${c.reset} ${builtin.hookName} ${c.green}${r.action}${c.reset} ${c.dim}${r.detail}${c.reset}`);
1157
- console.log(`\n${c.dim}Test by staging a file with a fake credential (e.g. ${c.bold}sk_${"test"}_${"A".repeat(20)}${c.reset}${c.dim}) and running ${c.bold}git commit${c.reset}${c.dim} — the commit should be blocked.${c.reset}\n`);
1158
- return true;
1159
- }
1160
- async function ensureHooksDir() {
1161
- const { resolve } = await import("node:path");
1162
- return { hooksDir: resolve(process.cwd(), ".git", "hooks") };
1163
- }
1164
- /**
1165
- * `kit mcp <list|status|auth|set-token|clear>` — declarative
1166
- * MCP-server registry. The block in .kit.toml [mcp.<name>] declares
1167
- * which MCPs the project intends to use; this command surfaces auth-state
1168
- * and stages tokens for the kit-plugins to consume.
1169
- *
1170
- * Browser-OAuth flow itself is delegated — `kit mcp auth <name>`
1171
- * prints the vendor's authorization URL and asks the operator to paste
1172
- * back the callback URL (same pattern as the Sentry MCP auth flow this
1173
- * session walked through). `kit mcp set-token <name>` is the
1174
- * headless alternative for CI: read access-token from an env var.
1175
- */
1176
- async function cmdMcp() {
1177
- const sub = process.argv[3];
1178
- // An MCP client launches `kit mcp` and speaks the stdio transport — no
1179
- // sub-command and a non-TTY stdin. Start the server in that case. Interactive
1180
- // use (TTY) or any explicit sub falls through to the orchestrator below.
1181
- if (!sub && !process.stdin.isTTY) {
1182
- await startMcpServer();
1183
- return true;
1184
- }
1185
- const config = await loadConfig(resolveConfigPath()).catch(() => null);
1186
- const mcpConfig = config?.mcp;
1187
- const { statusAll, statusForMcp, clearMcpToken, storeStaticToken, } = await import("./mcp-orchestrator.js");
1188
- if (!sub || sub === "list" || sub === "status") {
1189
- console.log(`${c.bold}${c.cyan}kit mcp${c.reset}`);
1190
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1191
- const entries = await statusAll(mcpConfig);
1192
- if (entries.length === 0) {
1193
- console.log(`${c.dim}No [mcp.*] blocks declared in .kit.toml${c.reset}`);
1194
- console.log(`${c.dim}Add e.g. [mcp.sentry] scopes = ["org:read", "project:write"]${c.reset}`);
1195
- return true;
1196
- }
1197
- for (const e of entries) {
1198
- const color = e.status === "ok"
1199
- ? c.green
1200
- : e.status === "missing" || e.status === "expired"
1201
- ? c.yellow
1202
- : c.red;
1203
- const marker = e.status === "ok" ? "✓" : e.status === "missing" ? "?" : "✗";
1204
- const scopeStr = e.declared?.scopes ? ` ${c.dim}[${e.declared.scopes.join(", ")}]${c.reset}` : "";
1205
- console.log(` ${color}${marker}${c.reset} ${e.name.padEnd(14)} ${color}${e.status}${c.reset}${scopeStr}`);
1206
- if (e.detail)
1207
- console.log(` ${c.dim}${e.detail}${c.reset}`);
1208
- }
1209
- console.log();
1210
- return entries.every((e) => e.status === "ok" || e.status === "missing");
1211
- }
1212
- if (sub === "auth") {
1213
- const name = process.argv[4];
1214
- if (!name) {
1215
- console.error(`${c.red}Usage: kit mcp auth <name>${c.reset}`);
1216
- return false;
1217
- }
1218
- const declared = mcpConfig?.[name];
1219
- if (!declared) {
1220
- console.error(`${c.red}No [mcp.${name}] block in .kit.toml${c.reset}`);
1221
- return false;
1222
- }
1223
- // For now we surface vendor-specific guidance; full OAuth flow is
1224
- // delegated to the operator (paste callback URL). When the vendor's
1225
- // MCP server publishes a stable /authorize endpoint we can fully
1226
- // automate.
1227
- const authUrl = declared.url ?? `https://mcp.${name}.dev`;
1228
- console.log(`${c.bold}Authorize kit for MCP server "${name}"${c.reset}`);
1229
- console.log(`${c.dim}Vendor URL: ${authUrl}${c.reset}`);
1230
- console.log(`${c.dim}Required scopes: ${(declared.scopes ?? ["(none declared)"]).join(", ")}${c.reset}\n`);
1231
- console.log(`${c.yellow}OAuth-flow not yet automated for "${name}". Two options:${c.reset}`);
1232
- console.log(` 1. Set env var: ${c.bold}kit mcp set-token ${name} --from-env <VAR>${c.reset}`);
1233
- console.log(` 2. Paste token: ${c.bold}kit mcp set-token ${name} --paste${c.reset}`);
1234
- return true;
1235
- }
1236
- if (sub === "set-token") {
1237
- const name = process.argv[4];
1238
- if (!name) {
1239
- console.error(`${c.red}Usage: kit mcp set-token <name> [--from-env VAR | --paste]${c.reset}`);
1240
- return false;
1241
- }
1242
- const args = process.argv.slice(5);
1243
- const fromEnvIdx = args.indexOf("--from-env");
1244
- let accessToken;
1245
- if (fromEnvIdx >= 0 && args[fromEnvIdx + 1]) {
1246
- const envVar = args[fromEnvIdx + 1];
1247
- accessToken = process.env[envVar];
1248
- if (!accessToken) {
1249
- console.error(`${c.red}Env var ${envVar} is empty${c.reset}`);
1250
- return false;
1251
- }
1252
- }
1253
- else if (hasFlag(args, "--paste")) {
1254
- const readline = await import("node:readline/promises");
1255
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1256
- try {
1257
- accessToken = (await rl.question(`Paste access token for "${name}": `)).trim();
1258
- }
1259
- finally {
1260
- rl.close();
1261
- }
1262
- }
1263
- else {
1264
- console.error(`${c.red}Missing --from-env <VAR> or --paste${c.reset}`);
1265
- return false;
1266
- }
1267
- const declared = mcpConfig?.[name];
1268
- await storeStaticToken(name, accessToken, {
1269
- scopes: declared?.scopes,
1270
- });
1271
- console.log(`${c.green}✓${c.reset} Token stored for ${name} in ~/.kit/mcp-tokens.json (chmod 0o600)`);
1272
- const status = await statusForMcp(name, declared ?? null);
1273
- console.log(`${c.dim}Status: ${status.status}${c.reset}`);
1274
- return true;
1275
- }
1276
- if (sub === "clear") {
1277
- const name = process.argv[4];
1278
- if (!name) {
1279
- console.error(`${c.red}Usage: kit mcp clear <name>${c.reset}`);
1280
- return false;
1281
- }
1282
- await clearMcpToken(name);
1283
- console.log(`${c.green}✓${c.reset} Cleared token for ${name}`);
1284
- return true;
1285
- }
1286
- console.error(`${c.red}Usage: kit mcp [list | status | auth <name> | set-token <name> | clear <name>]${c.reset}`);
1287
- return false;
1288
- }
1289
- async function cmdAuth() {
1290
- const sub = process.argv[3];
1291
- if (sub === "elevate")
1292
- return cmdAuthElevate();
1293
- if (sub === "status")
1294
- return cmdAuthStatus();
1295
- if (sub === "revoke")
1296
- return cmdAuthRevoke();
1297
- if (sub === "setup-totp")
1298
- return cmdAuthSetupTotp();
1299
- console.error(`${c.red}Usage: kit auth [elevate | status | revoke | setup-totp]${c.reset}`);
1300
- return false;
1301
- }
1302
- async function cmdAuthSetupTotp() {
1303
- // kit auth setup-totp [--issuer <name>] [--account <user@host>] [--overwrite]
1304
- const args = process.argv.slice(4);
1305
- const issuerIdx = args.indexOf("--issuer");
1306
- const accountIdx = args.indexOf("--account");
1307
- const overwrite = hasFlag(args, "--overwrite");
1308
- const issuer = issuerIdx >= 0 ? args[issuerIdx + 1] : "kit";
1309
- const defaultAccount = `${process.env.USER ?? "user"}@${process.env.HOSTNAME ?? "host"}`;
1310
- const accountName = accountIdx >= 0 ? args[accountIdx + 1] : defaultAccount;
1311
- console.log(`${c.bold}${c.cyan}kit auth setup-totp${c.reset}`);
1312
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1313
- let result;
1314
- try {
1315
- result = await enrollTotp({ accountName, issuer, overwrite });
1316
- }
1317
- catch (err) {
1318
- const msg = err instanceof Error ? err.message : String(err);
1319
- console.error(`${c.red}✗ ${msg}${c.reset}`);
1320
- console.error(`${c.dim}If you intentionally want to replace the existing secret, re-run with ${c.bold}--overwrite${c.reset}${c.dim} (your old authenticator entry will stop working).${c.reset}\n`);
1321
- return false;
1322
- }
1323
- console.log(` ${c.green}✓${c.reset} secret written to ${result.filePath} ${c.dim}(chmod 600)${c.reset}\n`);
1324
- console.log(`${c.bold}Provisioning URI:${c.reset}`);
1325
- console.log(` ${c.dim}${result.uri}${c.reset}\n`);
1326
- console.log(`${c.bold}Or enter the secret manually in your authenticator app:${c.reset}`);
1327
- console.log(` ${c.bold}Secret:${c.reset} ${result.secret}`);
1328
- console.log(` ${c.bold}Account:${c.reset} ${accountName}`);
1329
- console.log(` ${c.bold}Issuer:${c.reset} ${issuer}\n`);
1330
- console.log(`${c.bold}Verify enrollment — the next 6-digit code should be:${c.reset}`);
1331
- console.log(` ${c.bold}${result.currentCode}${c.reset} ${c.dim}(or the one shown in your authenticator right now)${c.reset}\n`);
1332
- console.log(`${c.dim}Future ${c.bold}kit auth elevate${c.reset}${c.dim} runs will prompt for the TOTP code automatically.${c.reset}`);
1333
- console.log(`${c.dim}Override with ${c.bold}KIT_TOTP_SECRET=...${c.reset}${c.dim} in CI / scripted contexts.${c.reset}\n`);
1334
- return true;
1335
- }
1336
- async function cmdAuthElevate() {
1337
- const args = process.argv.slice(4);
1338
- const scope = flagValue(args, "--scope") ?? "all";
1339
- const ttlIdx = args.indexOf("--ttl-minutes");
1340
- if (ttlIdx >= 0 && args[ttlIdx + 1]) {
1341
- const n = parseInt(args[ttlIdx + 1], 10);
1342
- if (Number.isFinite(n) && n > 0 && n <= 240) {
1343
- process.env.KIT_ELEVATION_TTL_MINUTES = String(n);
1344
- }
1345
- }
1346
- console.log(`${c.bold}${c.cyan}kit auth elevate${c.reset} ${c.dim}(scope=${scope})${c.reset}`);
1347
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1348
- if (isNonInteractive()) {
1349
- console.error(`${c.red}✗ Elevation requires an interactive TTY. Cannot run from agent / CI.${c.reset}`);
1350
- console.error(`${c.dim}For CI deploy jobs that legitimately need a destructive op, set ${c.bold}KIT_ELEVATED=1${c.reset}${c.dim} (and audit-log the use).${c.reset}\n`);
1351
- return false;
1352
- }
1353
- // Resolve from env first, then ~/.kit/totp-secret (created by
1354
- // `kit auth setup-totp`).
1355
- const totpSecret = await resolveTotpSecret();
1356
- let method = "yes-prompt";
1357
- if (totpSecret) {
1358
- method = "totp";
1359
- const readline = await import("node:readline/promises");
1360
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1361
- try {
1362
- const code = (await rl.question(`Enter 6-digit TOTP from your authenticator: `)).trim();
1363
- if (!verifyTotp(code, totpSecret)) {
1364
- console.error(`${c.red}✗ Invalid TOTP code.${c.reset}`);
1365
- return false;
1366
- }
1367
- }
1368
- finally {
1369
- rl.close();
1370
- }
1371
- }
1372
- else {
1373
- const ok = await promptConfirm(`Confirm elevation [type YES, default no in 15s]: `, 15_000, false);
1374
- if (!ok) {
1375
- console.log(`${c.dim}Aborted.${c.reset}`);
1376
- return false;
1377
- }
1378
- }
1379
- const state = await grantElevation(scope, method);
1380
- console.log(` ${c.green}✓${c.reset} elevated ${c.dim}scope=${state.scope} method=${state.method} expires=${state.expiresAt}${c.reset}`);
1381
- console.log(`\n${c.dim}Destructive secret ops (rotate, migrate, onecli register, propagate) are unlocked for ${elevationTtlMinutes()}m. Run ${c.bold}kit auth revoke${c.reset}${c.dim} to drop early.${c.reset}\n`);
1382
- return true;
1383
- }
1384
- async function cmdAuthStatus() {
1385
- const state = await readElevation(process.cwd());
1386
- if (!state) {
1387
- console.log(`${c.dim}Not elevated.${c.reset}`);
1388
- return true;
1389
- }
1390
- const expires = Date.parse(state.expiresAt);
1391
- const valid = Number.isFinite(expires) && expires > Date.now();
1392
- const status = valid
1393
- ? `${c.green}active${c.reset} ${c.dim}(expires ${state.expiresAt})${c.reset}`
1394
- : `${c.red}expired${c.reset} ${c.dim}(at ${state.expiresAt})${c.reset}`;
1395
- console.log(`${status} scope=${state.scope} method=${state.method} granter=${state.granter}`);
1396
- return true;
1397
- }
1398
- async function cmdAuthRevoke() {
1399
- await clearElevation(process.cwd());
1400
- console.log(`${c.green}✓${c.reset} elevation marker cleared.`);
1401
- return true;
1402
- }
1403
- async function cmdAuditSecrets() {
1404
- const args = process.argv.slice(4); // after "audit secrets"
1405
- const sinceIdx = args.indexOf("--since-days");
1406
- const sinceDays = sinceIdx >= 0 && args[sinceIdx + 1] ? parseInt(args[sinceIdx + 1], 10) : 30;
1407
- const keyFilter = flagValue(args, "--key");
1408
- const jsonMode = hasFlag(args, "--json");
1409
- const events = await readSecretAuditEvents(process.cwd(), sinceDays);
1410
- const { reports, unattributed } = groupBySecret(events);
1411
- const filteredReports = keyFilter
1412
- ? reports.filter((r) => r.key === keyFilter)
1413
- : reports;
1414
- const summary = summarize(filteredReports, sinceDays);
1415
- if (jsonMode) {
1416
- console.log(JSON.stringify({ summary, reports: filteredReports, unattributed }, null, 2));
1417
- return true;
1418
- }
1419
- console.log(`${c.bold}${c.cyan}kit audit secrets${c.reset} ${c.dim}(last ${sinceDays}d)${c.reset}`);
1420
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1421
- if (events.length === 0) {
1422
- console.log(`${c.dim}No secret-related events in audit log (.kit-audit.jsonl).${c.reset}\n`);
1423
- return true;
1424
- }
1425
- console.log(`${c.bold}${summary.totalEvents}${c.reset} event(s) across ${c.bold}${summary.keyCount}${c.reset} key(s)`);
1426
- if (summary.topKey) {
1427
- console.log(`${c.dim}Most touched: ${c.bold}${summary.topKey.key}${c.reset}${c.dim} (${summary.topKey.count} events)${c.reset}`);
1428
- }
1429
- console.log();
1430
- for (const r of filteredReports.slice(0, 20)) {
1431
- console.log(`${c.bold}${r.key}${c.reset} ${c.dim}(${r.events.length} events)${c.reset}`);
1432
- for (const e of r.events.slice(-10)) {
1433
- const icon = e.success ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
1434
- const ts = e.timestamp.slice(0, 19);
1435
- const agent = e.agent ? `[${e.agent}]` : "";
1436
- const detail = e.detail ? ` ${c.dim}${e.detail}${c.reset}` : "";
1437
- console.log(` ${icon} ${ts} ${e.operation} ${c.dim}${agent}${c.reset}${detail}`);
1438
- }
1439
- if (r.events.length > 10) {
1440
- console.log(` ${c.dim}… ${r.events.length - 10} earlier events truncated${c.reset}`);
1441
- }
1442
- console.log();
1443
- }
1444
- if (!keyFilter && unattributed.length > 0) {
1445
- console.log(`${c.dim}+ ${unattributed.length} event(s) couldn't be tied to a specific key. Use ${c.bold}--json${c.reset}${c.dim} for the full set.${c.reset}\n`);
1446
- }
1447
- return true;
1448
- }
1449
- async function cmdAudit() {
1450
- const args = process.argv.slice(3);
1451
- // Sub-sub: kit audit secrets [--since-days N] [--key NAME] [--json]
1452
- if (args[0] === "secrets") {
1453
- return cmdAuditSecrets();
1454
- }
1455
- // Parse --limit N
1456
- let limit = 20;
1457
- const limitIdx = args.indexOf("--limit");
1458
- if (limitIdx !== -1 && args[limitIdx + 1]) {
1459
- const parsed = parseInt(args[limitIdx + 1], 10);
1460
- if (!isNaN(parsed) && parsed > 0)
1461
- limit = parsed;
1462
- }
1463
- // Parse --operation <name>
1464
- let operationFilter;
1465
- const opIdx = args.indexOf("--operation");
1466
- if (opIdx !== -1 && args[opIdx + 1]) {
1467
- operationFilter = args[opIdx + 1];
1468
- }
1469
- // Determine log file path (use config if available, else default)
1470
- let logFile = ".kit-audit.jsonl";
1471
- try {
1472
- const config = await loadConfig(resolveConfigPath());
1473
- const govFile = config.governance?.audit?.log_file;
1474
- if (govFile)
1475
- logFile = govFile;
1476
- }
1477
- catch {
1478
- // No .kit.toml — use default log file
1479
- }
1480
- let events = await readAuditLog(logFile, limit * 5); // read extra to allow filtering
1481
- if (operationFilter) {
1482
- events = events.filter((e) => e.operation.includes(operationFilter));
1483
- }
1484
- // Apply limit after filter
1485
- events = events.slice(-limit);
1486
- printAuditTable(events);
1487
- if (events.length > 0) {
1488
- console.log();
1489
- console.log(`${c.dim}Showing ${events.length} entries from ${logFile}${c.reset}`);
1490
- }
1491
- return true;
1492
- }
1493
- async function cmdEnvList() {
1494
- let config;
1495
- try {
1496
- // Load base config without env merge so we can inspect [env.*] sections
1497
- config = await loadConfig(resolveConfigPath(), "dev");
1498
- }
1499
- catch {
1500
- console.log(`${c.dim}No .kit.toml found — no environments configured.${c.reset}`);
1501
- return true;
1502
- }
1503
- const activeEnv = resolveActiveEnvironment();
1504
- const envSections = config.env ?? {};
1505
- const envNames = ["dev", ...Object.keys(envSections).filter((k) => k !== "dev")];
1506
- console.log(`${c.bold}${c.cyan}Environments${c.reset} ${c.dim}(active: ${activeEnv})${c.reset}\n`);
1507
- for (const name of envNames) {
1508
- const isActive = name === activeEnv;
1509
- const marker = isActive ? `${c.green}●${c.reset} ` : " ";
1510
- const override = envSections[name];
1511
- if (!override) {
1512
- console.log(` ${marker}${c.bold}${name}${c.reset} ${c.dim}base config (default)${c.reset}`);
1513
- continue;
1514
- }
1515
- // Summarise which keys are overridden
1516
- const overrideKeys = [];
1517
- if (override.tools)
1518
- overrideKeys.push(`tools(${Object.keys(override.tools).join(",")})`);
1519
- if (override.secrets)
1520
- overrideKeys.push(`secrets.store=${override.secrets.store ?? "?"}`);
1521
- if (override.services)
1522
- overrideKeys.push(`services(${Object.keys(override.services).join(",")})`);
1523
- if (override.governance)
1524
- overrideKeys.push("governance");
1525
- if (override.skills)
1526
- overrideKeys.push("skills");
1527
- const summary = overrideKeys.length > 0 ? overrideKeys.join(", ") : "no overrides";
1528
- console.log(` ${marker}${c.bold}${name}${c.reset} ${c.dim}${summary}${c.reset}`);
1529
- }
1530
- if (Object.keys(envSections).length === 0) {
1531
- console.log(`${c.dim}\nNo [env.*] sections defined in .kit.toml.${c.reset}`);
1532
- console.log(`${c.dim}Add [env.staging.*] or [env.production.*] to configure per-environment overrides.${c.reset}`);
1533
- }
1534
- console.log();
1535
- return true;
1536
- }
1537
- async function cmdEnvSwitch() {
1538
- // argv layout: [node, cli.js, "env", "switch", "<target>"]
1539
- const target = process.argv[4];
1540
- if (!target || !KNOWN_ENVS.includes(target)) {
1541
- console.error(`${c.red}Usage: kit env switch <${KNOWN_ENVS.join("|")}>${c.reset}`);
1542
- return false;
1543
- }
1544
- const env = target;
1545
- console.log(`${c.bold}${c.cyan}kit env switch ${env}${c.reset}`);
1546
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1547
- if (env === "prod" && !isNonInteractive()) {
1548
- console.log(`${c.red}⚠ Switching to ${c.bold}prod${c.reset}${c.red} unlocks production credentials in this shell.${c.reset}`);
1549
- console.log(`${c.dim}Anything that reads ${c.bold}.env.local${c.reset}${c.dim} after this point can call live services.${c.reset}\n`);
1550
- const ok = await promptConfirm(`Continue? [y/N] (auto-no in 10s): `, 10_000, false);
1551
- if (!ok) {
1552
- console.log(`${c.dim}Aborted — active env unchanged.${c.reset}`);
1553
- return false;
1554
- }
1555
- }
1556
- const state = await writeActiveEnv(env, process.cwd());
1557
- console.log(` ${c.green}✓${c.reset} active env is now ${c.bold}${state.env}${c.reset} ${c.dim}(by ${state.switchedBy} at ${state.switchedAt})${c.reset}`);
1558
- if (env === "prod") {
1559
- console.log(`\n${c.dim}Re-run ${c.bold}kit secrets${c.reset}${c.dim} to materialize prod values into .env.local. ${c.bold}kit env switch dev${c.reset}${c.dim} when done.${c.reset}\n`);
1560
- }
1561
- else {
1562
- console.log(`\n${c.dim}Prod-scoped keys are now refused unless you switch back or set ${c.bold}KIT_PROD_OK=1${c.reset}${c.dim}.${c.reset}\n`);
1563
- }
1564
- return true;
1565
- }
1566
- async function cmdEnvCurrent() {
1567
- const state = await readActiveEnv(process.cwd());
1568
- if (!state) {
1569
- console.log(`${c.dim}No active env set (defaulting to ${c.bold}dev${c.reset}${c.dim}). Run ${c.bold}kit env switch <env>${c.reset}${c.dim} to set one.${c.reset}`);
1570
- console.log(`dev`);
1571
- return true;
1572
- }
1573
- const color = state.env === "prod" ? c.red : state.env === "staging" ? c.yellow : c.green;
1574
- console.log(`${color}${state.env}${c.reset} ${c.dim}(set ${state.switchedAt} by ${state.switchedBy})${c.reset}`);
1575
- return true;
1576
- }
1577
- async function cmdEnv() {
1578
- const args = process.argv.slice(2); // includes "env"
1579
- // Route subcommand: kit env list / switch / current / validate
1580
- if (args[1] === "list")
1581
- return cmdEnvList();
1582
- if (args[1] === "switch")
1583
- return cmdEnvSwitch();
1584
- if (args[1] === "current")
1585
- return cmdEnvCurrent();
1586
- const showValues = hasFlag(args, "--show-values");
1587
- const missingOnly = hasFlag(args, "--missing");
1588
- const jsonMode = hasFlag(args, "--json");
1589
- let config = {};
1590
- try {
1591
- config = await loadConfig(resolveConfigPath());
1592
- }
1593
- catch {
1594
- // Works without .kit.toml — shows .env.local contents only
1595
- }
1596
- const result = await inspectEnv(config, { showValues, missingOnly, cwd: process.cwd() });
1597
- if (jsonMode) {
1598
- console.log(JSON.stringify(result, null, 2));
1599
- return result.ok;
1600
- }
1601
- console.log(`${c.bold}${c.cyan}Environment${c.reset}`);
1602
- console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
1603
- if (!result.envLocalExists) {
1604
- console.log(`${c.yellow}⚠${c.reset} .env.local not found — run ${c.bold}kit secrets${c.reset} to generate it\n`);
1605
- }
1606
- if (result.keys.length === 0) {
1607
- if (missingOnly) {
1608
- console.log(`${c.green}✓${c.reset} All keys are set\n`);
1609
- }
1610
- else {
1611
- console.log(`${c.dim}No keys configured or found${c.reset}\n`);
1612
- }
1613
- return result.ok;
1614
- }
1615
- for (const key of result.keys) {
1616
- const icon = key.set ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
1617
- const valueStr = key.set
1618
- ? showValues
1619
- ? `${c.dim}${key.value}${c.reset}`
1620
- : `${c.dim}${key.redacted}${c.reset}`
1621
- : `${c.red}not set${c.reset}`;
1622
- const src = `${c.dim}[${key.source}]${c.reset}`;
1623
- console.log(` ${icon} ${key.name} ${valueStr} ${src}`);
1624
- }
1625
- console.log();
1626
- if (!result.ok && !missingOnly) {
1627
- console.log(`${c.dim}Run ${c.reset}${c.bold}kit env --missing${c.reset}${c.dim} to see only unset keys${c.reset}`);
1628
- console.log(`${c.dim}Run ${c.reset}${c.bold}kit secrets${c.reset}${c.dim} to generate .env.local${c.reset}\n`);
1629
- }
1630
- return result.ok;
1631
- }
1632
1037
  async function cmdDoctor() {
1633
1038
  console.log(`${c.bold}${c.cyan}kit doctor${c.reset}`);
1634
1039
  console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
@@ -1932,6 +1337,52 @@ async function cmdSecretsRevokeOld() {
1932
1337
  console.log(`\n${c.dim}Verify with ${c.bold}kit secrets onecli status${c.reset}${c.dim} or the Supabase Dashboard.${c.reset}\n`);
1933
1338
  return true;
1934
1339
  }
1340
+ async function cmdSecretsSet() {
1341
+ // kit secrets set <KEY> [--value <v> | --stdin] [--store <backend>]
1342
+ // Capture-to-vault: write a user-provided value to the configured vault. This
1343
+ // is the execution behind a service's `auth = "capture"` strategy. The value
1344
+ // is read via --stdin (safer — not in argv/ps) or --value; kit reuses the
1345
+ // existing setSecretValue path, so it never echoes or logs the secret.
1346
+ const args = process.argv.slice(3);
1347
+ const keyName = process.argv[4];
1348
+ if (!keyName || keyName.startsWith("--")) {
1349
+ console.error(`${c.red}Usage: kit secrets set <KEY> [--value <v> | --stdin] [--store <backend>]${c.reset}`);
1350
+ return false;
1351
+ }
1352
+ const config = await loadConfig(resolveConfigPath());
1353
+ // Read value: --value <v> (visible in argv/ps) or --stdin (safer).
1354
+ let value;
1355
+ const valueIdx = args.indexOf("--value");
1356
+ if (valueIdx >= 0)
1357
+ value = args[valueIdx + 1];
1358
+ if (!value && hasFlag(args, "--stdin")) {
1359
+ value = await new Promise((resolve) => {
1360
+ let buf = "";
1361
+ process.stdin.setEncoding("utf-8");
1362
+ process.stdin.on("data", (chunk) => {
1363
+ buf += chunk;
1364
+ });
1365
+ process.stdin.on("end", () => resolve(buf.trim()));
1366
+ });
1367
+ }
1368
+ if (!value) {
1369
+ console.error(`${c.red}Provide the value via --stdin (safer) or --value <v> (visible in argv/ps).${c.reset}`);
1370
+ return false;
1371
+ }
1372
+ const storeIdx = args.indexOf("--store");
1373
+ const storeOverride = storeIdx >= 0 ? args[storeIdx + 1] : undefined;
1374
+ const backendOpts = pickBackendOpts(config.secrets ?? {}, keyName, { envFallback: true });
1375
+ const result = await setSecretValue(config.secrets, keyName, value, {
1376
+ ...backendOpts,
1377
+ store: storeOverride,
1378
+ });
1379
+ if (!result.ok) {
1380
+ console.error(`${c.red}✗ ${result.detail}${c.reset}`);
1381
+ return false;
1382
+ }
1383
+ console.log(`${c.green}✓${c.reset} captured ${c.bold}${keyName}${c.reset} to the vault ${c.dim}(${result.detail})${c.reset}`);
1384
+ return true;
1385
+ }
1935
1386
  async function cmdSecretsPropagateStandalone() {
1936
1387
  // kit secrets propagate <KEY> --value <v> | --stdin --to <targets> [opts]
1937
1388
  const args = process.argv.slice(4);
@@ -2225,7 +1676,7 @@ async function cmdSecretsVaultMigrate() {
2225
1676
  console.log("");
2226
1677
  console.log("Migration is gated by elevation (one-shot — consumed on use):");
2227
1678
  console.log(" kit auth elevate --scope vault-migrate");
2228
- return !fromArg || !toArg ? false : true;
1679
+ return !(!fromArg || !toArg);
2229
1680
  }
2230
1681
  console.log(`${c.bold}${c.cyan}kit secrets vault-migrate${c.reset}`);
2231
1682
  console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
@@ -2352,8 +1803,9 @@ function emitGitlabJunit(checks, allOk) {
2352
1803
  }
2353
1804
  lines.push(` </testsuite>`, `</testsuites>`);
2354
1805
  const xml = lines.join("\n");
2355
- // Write to file for GitLab artifact collection
2356
- import("node:fs/promises").then(({ writeFile }) => writeFile("kit-report.xml", xml, "utf8"));
1806
+ // Write to file for GitLab artifact collection — synchronous so the report is
1807
+ // guaranteed flushed before this (sync) function returns.
1808
+ writeFileSync("kit-report.xml", xml, "utf8");
2357
1809
  if (!allOk || warnings.length > 0) {
2358
1810
  console.log(`CI report written to kit-report.xml (${failures.length} failures, ${warnings.length} warnings)`);
2359
1811
  }
@@ -3218,7 +2670,7 @@ async function cmdOpen() {
3218
2670
  const args = process.argv.slice(2);
3219
2671
  const serviceName = args[1];
3220
2672
  // Load env from .env.local for dashboard URL resolution
3221
- let env = {};
2673
+ const env = {};
3222
2674
  try {
3223
2675
  const envPath = resolve(process.cwd(), ".env.local");
3224
2676
  const { readFileSync } = await import("node:fs");
@@ -3329,6 +2781,21 @@ async function cmdContext() {
3329
2781
  return false;
3330
2782
  }
3331
2783
  }
2784
+ async function cmdStatus() {
2785
+ const items = await gatherStatus();
2786
+ if (hasFlag(process.argv, "--json")) {
2787
+ console.log(JSON.stringify(items, null, 2));
2788
+ return true;
2789
+ }
2790
+ const done = items.filter((i) => i.ok).length;
2791
+ console.log(`${c.bold}kit status${c.reset} ${c.dim}${done}/${items.length} set up${c.reset}`);
2792
+ for (const item of items) {
2793
+ const mark = item.ok ? `${c.green}✓${c.reset}` : `${c.yellow}○${c.reset}`;
2794
+ const hint = !item.ok && item.hint ? ` ${c.dim}→ ${item.hint}${c.reset}` : "";
2795
+ console.log(` ${mark} ${item.label} ${c.dim}${item.detail}${c.reset}${hint}`);
2796
+ }
2797
+ return true;
2798
+ }
3332
2799
  async function cmdWhoami() {
3333
2800
  const jsonMode = hasFlag(process.argv, "--json");
3334
2801
  let config = {};
@@ -3400,12 +2867,15 @@ function cmdVersion() {
3400
2867
  return true;
3401
2868
  }
3402
2869
  const COMMAND_HELP = {
2870
+ status: "Adoption checklist — what's set up across kit + the next step for each gap",
3403
2871
  check: "Check status of all tools, services, secrets, and lock files",
3404
2872
  memory: "Local conversation memory — index transcripts + show stats",
3405
2873
  "memory index": "Index ~/.claude transcripts into the SQLite memory store",
3406
2874
  "memory search": "Full-text search memory (current project; --global for all)",
3407
2875
  "memory stats": "Show what the local memory store contains",
3408
- "memory install": "Wire UserPromptSubmit + SessionEnd hooks into ~/.claude/settings.json",
2876
+ "memory suggest": "Emit a BYO-LLM review prompt (recent activity + open items) pipe to your own model",
2877
+ "memory merge": "Merge another machine's memory.db into this one (dedup by uuid)",
2878
+ "memory install": "Wire UserPromptSubmit + SessionEnd + SessionStart (recovery) hooks into ~/.claude/settings.json",
3409
2879
  "memory scan": "Scan the memory store for stored secrets (exit 1 if any found)",
3410
2880
  "memory backup": "Encrypted backup of the memory store (AES-256-GCM; KIT_MEMORY_PASSPHRASE)",
3411
2881
  "memory restore": "Restore an encrypted memory backup (e.g. on a new machine)",
@@ -3420,9 +2890,11 @@ const COMMAND_HELP = {
3420
2890
  upgrade: "Update lock files from .kit.toml",
3421
2891
  install: "Install missing tools via mise",
3422
2892
  login: "Guided login to all configured services",
2893
+ "login --plan": "Show the resolved auth strategy per service (vault/interactive/capture + passkey) without logging in",
3423
2894
  secrets: "Generate .env.local from template + secret store",
3424
2895
  "secrets sync": "Push resolved secrets to GitHub Actions / .env.ci / stdout",
3425
2896
  "secrets migrate": "Migrate plaintext secrets in .env* → configured vault",
2897
+ "secrets set": "Capture a value to the vault: kit secrets set <KEY> --stdin (safer) | --value <v>",
3426
2898
  "secrets rotate": "Rotate a key: write new value to vault (explicit / random)",
3427
2899
  "secrets onecli": "Register a key with OneCLI gateway so agent never sees the real value",
3428
2900
  "secrets purge-history": "Destructive: rewrite git history to remove a leaked value (--force-history)",
@@ -3887,430 +3359,6 @@ async function showSkippedCommitBanner() {
3887
3359
  * `kit memory` — local conversation memory (SQLite + FTS5). Deterministic and
3888
3360
  * zero-LLM: it stores raw transcripts and searches them; it never calls a model.
3889
3361
  */
3890
- async function cmdMemory() {
3891
- const subcommand = process.argv[3];
3892
- const jsonMode = hasFlag(process.argv, "--json");
3893
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
3894
- console.log("kit memory — local conversation memory (SQLite + FTS5)");
3895
- console.log("\nUsage:");
3896
- console.log(" kit memory index Index ~/.claude transcripts into the memory store");
3897
- console.log(" kit memory search <query> Search memory (current project; --global for all)");
3898
- console.log(" kit memory stats Show what the memory store contains");
3899
- console.log(" kit memory install Wire the 2 hooks into ~/.claude/settings.json");
3900
- console.log(" kit memory uninstall Remove the hooks");
3901
- console.log(" kit memory pal [list|add|done|snooze|verify|import] Pending action ledger");
3902
- console.log(" kit memory save <name> Bookmark the current session as a named copilot");
3903
- console.log(" kit memory threads List saved copilots (--global for all)");
3904
- console.log(" kit memory resume <name|n> Print the resume command for a saved copilot");
3905
- console.log(" kit memory forget <name> Remove a saved copilot");
3906
- console.log(" kit memory scan Scan the store for stored secrets");
3907
- console.log(" kit memory backup <file> Encrypted backup (set KIT_MEMORY_PASSPHRASE)");
3908
- console.log(" kit memory restore <file> Restore an encrypted backup (new machine)");
3909
- console.log(" kit memory share … Promote a curated entry to shared (team) memory");
3910
- console.log(" kit memory areas List shared responsibility areas");
3911
- console.log(" kit memory area <name> Show shared entries for one area");
3912
- return true;
3913
- }
3914
- if (subcommand === "index") {
3915
- const db = openMemoryDb();
3916
- const t0 = Date.now();
3917
- const res = indexClaudeTranscripts(db);
3918
- const ms = Date.now() - t0;
3919
- db.close();
3920
- if (jsonMode) {
3921
- console.log(JSON.stringify({ ...res, ms }));
3922
- return true;
3923
- }
3924
- console.log(`${c.green}✓${c.reset} indexed ${c.bold}${res.messages}${c.reset} messages + ${res.toolUses} tool-uses from ${res.files} sessions ${c.dim}(${ms}ms)${c.reset}`);
3925
- console.log(`${c.dim}source: ${getClaudeProjectsDir()}${c.reset}`);
3926
- return true;
3927
- }
3928
- if (subcommand === "stats") {
3929
- const db = openMemoryDb();
3930
- const s = getStats(db);
3931
- db.close();
3932
- if (jsonMode) {
3933
- console.log(JSON.stringify(s));
3934
- return true;
3935
- }
3936
- console.log(`${c.bold}kit memory${c.reset} ${c.dim}${s.dbPath}${c.reset}`);
3937
- console.log(` sessions ${s.sessions}`);
3938
- console.log(` messages ${s.messages}`);
3939
- console.log(` tool-uses ${s.toolUses}`);
3940
- console.log(` pending ${s.pendingOpen} ${c.dim}(open action items)${c.reset}`);
3941
- console.log(` size ${Math.round(s.sizeBytes / 1024)} KB`);
3942
- return true;
3943
- }
3944
- if (subcommand === "search") {
3945
- const terms = process.argv.slice(4).filter((a) => !a.startsWith("--"));
3946
- const query = terms.join(" ").trim();
3947
- if (!query) {
3948
- console.error(`${c.red}usage: kit memory search <query> [--global] [--project=<path>] [--limit=N]${c.reset}`);
3949
- return false;
3950
- }
3951
- const limit = Number(flagValue(process.argv, "--limit") ?? "20") || 20;
3952
- const projectPath = hasFlag(process.argv, "--global")
3953
- ? undefined
3954
- : (flagValue(process.argv, "--project") ?? getCurrentProjectRoot());
3955
- const db = openMemoryDb();
3956
- const hits = searchMessages(db, query, { limit, projectPath });
3957
- db.close();
3958
- if (jsonMode) {
3959
- console.log(JSON.stringify(hits));
3960
- return true;
3961
- }
3962
- const scope = projectPath ? `${c.dim}in ${projectPath}${c.reset}` : `${c.dim}(global)${c.reset}`;
3963
- if (!hits.length) {
3964
- console.log(`${c.dim}no matches for "${query}" ${projectPath ?? "(global)"}${c.reset}`);
3965
- return true;
3966
- }
3967
- console.log(`${c.bold}${hits.length}${c.reset} match(es) ${scope}`);
3968
- for (const h of hits) {
3969
- const snippet = (h.content ?? "").replace(/\s+/g, " ").slice(0, 120);
3970
- console.log(` ${c.dim}${h.timestamp ?? "?"}${c.reset} ${c.bold}${h.role ?? h.uuid ?? ""}${c.reset} ${snippet}`);
3971
- }
3972
- return true;
3973
- }
3974
- if (subcommand === "hook") {
3975
- // Internal: invoked by Claude Code hooks. Fail-open — never block.
3976
- const event = process.argv[4];
3977
- if (event === "user-prompt-submit") {
3978
- const text = userPromptSubmitReminder();
3979
- if (text)
3980
- console.log(text);
3981
- return true;
3982
- }
3983
- if (event === "session-end") {
3984
- runSessionEndIndex();
3985
- return true;
3986
- }
3987
- console.error(`${c.red}Unknown hook event: ${event ?? "(none)"}${c.reset}`);
3988
- return false;
3989
- }
3990
- if (subcommand === "install") {
3991
- const { added, alreadyPresent } = installMemoryHooks();
3992
- for (const e of added)
3993
- console.log(`${c.green}✓${c.reset} installed ${e} hook`);
3994
- for (const e of alreadyPresent)
3995
- console.log(`${c.dim}• ${e} hook already present${c.reset}`);
3996
- console.log(`${c.dim}settings: ${getClaudeSettingsPath()}${c.reset}`);
3997
- return true;
3998
- }
3999
- if (subcommand === "uninstall") {
4000
- const { removed } = uninstallMemoryHooks();
4001
- if (removed.length) {
4002
- for (const e of removed)
4003
- console.log(`${c.green}✓${c.reset} removed ${e} hook`);
4004
- }
4005
- else {
4006
- console.log(`${c.dim}no kit memory hooks were installed${c.reset}`);
4007
- }
4008
- return true;
4009
- }
4010
- if (subcommand === "share") {
4011
- const area = flagValue(process.argv, "--area");
4012
- const title = flagValue(process.argv, "--title");
4013
- const kind = (flagValue(process.argv, "--kind") ?? "note");
4014
- const body = flagValue(process.argv, "--body") ?? "";
4015
- const ref = flagValue(process.argv, "--ref");
4016
- if (!area || !title) {
4017
- console.error(`${c.red}usage: kit memory share --area <a> --title <t> [--kind decision|convention|how-built|status|security|note] [--body <b>] [--ref <r>]${c.reset}`);
4018
- return false;
4019
- }
4020
- const root = getCurrentProjectRoot();
4021
- try {
4022
- const e = shareEntry(root, { area, kind, title, body, refs: ref ? [ref] : [] }, new Date().toISOString());
4023
- console.log(`${c.green}✓${c.reset} shared ${c.bold}${e.id}${c.reset} to area ${c.bold}${area}${c.reset} ${c.dim}(${getSharedPath(root)})${c.reset}`);
4024
- console.log(`${c.dim}commit .kit/shared/memory.jsonl + open a PR — shared memory is reviewed like code${c.reset}`);
4025
- }
4026
- catch (err) {
4027
- console.error(`${c.red}${err.message}${c.reset}`);
4028
- return false;
4029
- }
4030
- return true;
4031
- }
4032
- if (subcommand === "areas") {
4033
- const areas = listAreas(getCurrentProjectRoot());
4034
- if (jsonMode) {
4035
- console.log(JSON.stringify(areas));
4036
- return true;
4037
- }
4038
- if (!areas.length) {
4039
- console.log(`${c.dim}no shared areas yet — add one with kit memory share${c.reset}`);
4040
- return true;
4041
- }
4042
- console.log(`${c.bold}${areas.length}${c.reset} responsibility area(s):`);
4043
- for (const a of areas) {
4044
- console.log(` ${c.bold}${a.area}${c.reset} ${c.dim}· ${a.count} entr${a.count === 1 ? "y" : "ies"}${c.reset}`);
4045
- }
4046
- return true;
4047
- }
4048
- if (subcommand === "area") {
4049
- const name = process.argv[4];
4050
- if (!name) {
4051
- console.error(`${c.red}usage: kit memory area <name>${c.reset}`);
4052
- return false;
4053
- }
4054
- const entries = queryArea(getCurrentProjectRoot(), name);
4055
- if (jsonMode) {
4056
- console.log(JSON.stringify(entries));
4057
- return true;
4058
- }
4059
- if (!entries.length) {
4060
- console.log(`${c.dim}no shared memory for area '${name}'${c.reset}`);
4061
- return true;
4062
- }
4063
- console.log(`${c.bold}${name}${c.reset} ${c.dim}· ${entries.length} entr${entries.length === 1 ? "y" : "ies"}${c.reset}`);
4064
- for (const e of entries) {
4065
- const prov = `${e.author}${e.source_ref ? ` @${e.source_ref}` : ""}`;
4066
- console.log(` ${c.bold}[${e.kind}]${c.reset} ${e.title} ${c.dim}— ${prov}${c.reset}`);
4067
- if (e.body)
4068
- console.log(` ${e.body}`);
4069
- if (e.refs.length)
4070
- console.log(` ${c.dim}refs: ${e.refs.join(", ")}${c.reset}`);
4071
- }
4072
- return true;
4073
- }
4074
- if (subcommand === "scan") {
4075
- const db = openMemoryDb();
4076
- const findings = scanDbForSecrets(db);
4077
- db.close();
4078
- if (jsonMode) {
4079
- console.log(JSON.stringify(findings));
4080
- return !findings.some((f) => f.confidence === "high");
4081
- }
4082
- if (!findings.length) {
4083
- console.log(`${c.green}✓${c.reset} no stored secrets found in the memory store`);
4084
- return true;
4085
- }
4086
- const high = findings.filter((f) => f.confidence === "high");
4087
- const heuristic = findings.filter((f) => f.confidence === "heuristic");
4088
- const times = (n) => (n > 1 ? ` ×${n}` : "");
4089
- if (high.length) {
4090
- console.log(`${c.red}⚠ ${high.length} high-confidence secret(s):${c.reset}`);
4091
- for (const f of high) {
4092
- const proj = f.projects.length ? `${c.bold}[${f.projects.join(", ")}]${c.reset}${c.dim} · ` : "";
4093
- console.log(` ${c.bold}${f.label}${c.reset} ${c.dim}${f.preview}${times(f.count)} · ${proj}${f.sample}${c.reset}`);
4094
- }
4095
- }
4096
- else {
4097
- console.log(`${c.green}✓${c.reset} no high-confidence secrets`);
4098
- }
4099
- if (heuristic.length) {
4100
- const showAll = hasFlag(process.argv, "--all");
4101
- if (showAll) {
4102
- console.log(`${c.dim}${heuristic.length} heuristic match(es) (KEY=value patterns — usually env vars / paths):${c.reset}`);
4103
- for (const f of heuristic) {
4104
- console.log(` ${c.dim}${f.label} ${f.preview}${times(f.count)} · ${f.sample}${c.reset}`);
4105
- }
4106
- }
4107
- else {
4108
- console.log(`${c.dim}+ ${heuristic.length} heuristic match(es) (likely env vars / paths) — run with --all to see${c.reset}`);
4109
- }
4110
- }
4111
- return high.length === 0; // exit non-zero only on high-confidence findings
4112
- }
4113
- if (subcommand === "backup") {
4114
- const out = process.argv[4];
4115
- const pass = process.env.KIT_MEMORY_PASSPHRASE ?? flagValue(process.argv, "--passphrase");
4116
- if (!out) {
4117
- console.error(`${c.red}usage: kit memory backup <file> (set KIT_MEMORY_PASSPHRASE)${c.reset}`);
4118
- return false;
4119
- }
4120
- if (!pass) {
4121
- console.error(`${c.red}set KIT_MEMORY_PASSPHRASE (or --passphrase) — the key is never stored${c.reset}`);
4122
- return false;
4123
- }
4124
- try {
4125
- backupEncrypted(pass, getMemoryDbPath(), out);
4126
- }
4127
- catch (err) {
4128
- console.error(`${c.red}${err.message}${c.reset}`);
4129
- return false;
4130
- }
4131
- console.log(`${c.green}✓${c.reset} encrypted backup → ${out} ${c.dim}(AES-256-GCM · scrypt)${c.reset}`);
4132
- return true;
4133
- }
4134
- if (subcommand === "restore") {
4135
- const inFile = process.argv[4];
4136
- const pass = process.env.KIT_MEMORY_PASSPHRASE ?? flagValue(process.argv, "--passphrase");
4137
- if (!inFile) {
4138
- console.error(`${c.red}usage: kit memory restore <file> [--to <path>] [--force]${c.reset}`);
4139
- return false;
4140
- }
4141
- if (!pass) {
4142
- console.error(`${c.red}set KIT_MEMORY_PASSPHRASE (or --passphrase)${c.reset}`);
4143
- return false;
4144
- }
4145
- const dest = flagValue(process.argv, "--to") ?? getMemoryDbPath();
4146
- if (existsSync(dest) && !hasFlag(process.argv, "--force")) {
4147
- console.error(`${c.red}${dest} exists — pass --force to overwrite${c.reset}`);
4148
- return false;
4149
- }
4150
- try {
4151
- restoreEncrypted(pass, inFile, dest);
4152
- }
4153
- catch {
4154
- console.error(`${c.red}restore failed — wrong passphrase or corrupt backup${c.reset}`);
4155
- return false;
4156
- }
4157
- console.log(`${c.green}✓${c.reset} restored → ${dest}`);
4158
- return true;
4159
- }
4160
- if (subcommand === "save") {
4161
- const name = process.argv.slice(4).filter((a) => !a.startsWith("--")).join(" ").trim();
4162
- if (!name) {
4163
- console.error(`${c.red}usage: kit memory save <name> [--session=<id>]${c.reset}`);
4164
- return false;
4165
- }
4166
- const root = getCurrentProjectRoot();
4167
- const db = openMemoryDb();
4168
- const sessionId = flagValue(process.argv, "--session") ?? latestSessionId(db, { projectPath: root });
4169
- if (!sessionId) {
4170
- db.close();
4171
- console.error(`${c.red}no session found for ${root} — index first or pass --session=<id>${c.reset}`);
4172
- return false;
4173
- }
4174
- saveThread(db, { name, sessionId, projectPath: root });
4175
- db.close();
4176
- console.log(`${c.green}✓${c.reset} saved copilot ${c.bold}${name}${c.reset} ${c.dim}→ ${sessionId}${c.reset}`);
4177
- return true;
4178
- }
4179
- if (subcommand === "threads") {
4180
- const projectPath = hasFlag(process.argv, "--global") ? undefined : getCurrentProjectRoot();
4181
- const db = openMemoryDb();
4182
- const list = listThreads(db, { projectPath });
4183
- db.close();
4184
- if (jsonMode) {
4185
- console.log(JSON.stringify(list));
4186
- return true;
4187
- }
4188
- if (!list.length) {
4189
- console.log(`${c.dim}no saved copilots${projectPath ? ` in ${projectPath}` : ""}${c.reset}`);
4190
- return true;
4191
- }
4192
- const scope = projectPath ? `${c.dim}in ${projectPath}${c.reset}` : `${c.dim}(global)${c.reset}`;
4193
- console.log(`${c.bold}${list.length}${c.reset} saved copilot(s) ${scope}:`);
4194
- list.forEach((t, i) => {
4195
- console.log(` ${c.bold}${i + 1}${c.reset}. ${t.name} ${c.dim}${t.session_id}${c.reset}`);
4196
- });
4197
- console.log(`${c.dim}resume with: kit memory resume <name|number>${c.reset}`);
4198
- return true;
4199
- }
4200
- if (subcommand === "resume") {
4201
- const ref = process.argv[4];
4202
- if (!ref) {
4203
- console.error(`${c.red}usage: kit memory resume <name|number>${c.reset}`);
4204
- return false;
4205
- }
4206
- const projectPath = hasFlag(process.argv, "--global") ? undefined : getCurrentProjectRoot();
4207
- const db = openMemoryDb();
4208
- const t = resolveThread(db, ref, { projectPath });
4209
- db.close();
4210
- if (!t) {
4211
- console.error(`${c.red}no saved copilot '${ref}'${c.reset}`);
4212
- return false;
4213
- }
4214
- console.log(`${c.bold}${t.name}${c.reset} — run:`);
4215
- console.log(` claude --resume ${t.session_id}`);
4216
- return true;
4217
- }
4218
- if (subcommand === "forget") {
4219
- const name = process.argv.slice(4).filter((a) => !a.startsWith("--")).join(" ").trim();
4220
- if (!name) {
4221
- console.error(`${c.red}usage: kit memory forget <name>${c.reset}`);
4222
- return false;
4223
- }
4224
- const db = openMemoryDb();
4225
- const ok = removeThread(db, name);
4226
- db.close();
4227
- console.log(ok ? `${c.green}✓${c.reset} forgot ${name}` : `${c.dim}no copilot '${name}'${c.reset}`);
4228
- return true;
4229
- }
4230
- if (subcommand === "pal") {
4231
- const action = process.argv[4] && !process.argv[4].startsWith("--") ? process.argv[4] : "list";
4232
- const db = openMemoryDb();
4233
- try {
4234
- if (action === "list") {
4235
- const scope = hasFlag(process.argv, "--global")
4236
- ? undefined
4237
- : basename(getCurrentProjectRoot());
4238
- const items = palList(db, { scope });
4239
- if (jsonMode) {
4240
- console.log(JSON.stringify(items));
4241
- return true;
4242
- }
4243
- if (!items.length) {
4244
- console.log(`${c.dim}no open action items${c.reset}`);
4245
- return true;
4246
- }
4247
- console.log(`${c.bold}${items.length}${c.reset} open action item(s):`);
4248
- for (const p of items) {
4249
- const tag = p.kind === "auto" ? ` ${c.dim}· auto${c.reset}` : "";
4250
- const scope = p.scope ? ` ${c.dim}[${p.scope}]${c.reset}` : "";
4251
- console.log(` ${c.bold}${p.id}${c.reset} ${p.title}${scope}${tag}`);
4252
- }
4253
- return true;
4254
- }
4255
- if (action === "add") {
4256
- const title = process.argv.slice(5).filter((a) => !a.startsWith("--")).join(" ").trim();
4257
- if (!title) {
4258
- console.error(`${c.red}usage: kit memory pal add <title> [--verify=<cmd>] [--scope=<s>]${c.reset}`);
4259
- return false;
4260
- }
4261
- const id = palAdd(db, {
4262
- title,
4263
- verifyCmd: flagValue(process.argv, "--verify"),
4264
- scope: flagValue(process.argv, "--scope") ?? basename(getCurrentProjectRoot()),
4265
- });
4266
- console.log(`${c.green}✓${c.reset} added ${c.bold}${id}${c.reset}`);
4267
- return true;
4268
- }
4269
- if (action === "done") {
4270
- const id = process.argv[5];
4271
- if (!id) {
4272
- console.error(`${c.red}usage: kit memory pal done <id>${c.reset}`);
4273
- return false;
4274
- }
4275
- console.log(palDone(db, id)
4276
- ? `${c.green}✓${c.reset} closed ${id}`
4277
- : `${c.dim}${id} not found or already closed${c.reset}`);
4278
- return true;
4279
- }
4280
- if (action === "snooze") {
4281
- const id = process.argv[5];
4282
- const days = Number(process.argv[6] ?? "7") || 7;
4283
- if (!id) {
4284
- console.error(`${c.red}usage: kit memory pal snooze <id> [days]${c.reset}`);
4285
- return false;
4286
- }
4287
- console.log(palSnooze(db, id, days)
4288
- ? `${c.green}✓${c.reset} snoozed ${id} for ${days}d`
4289
- : `${c.dim}${id} not found${c.reset}`);
4290
- return true;
4291
- }
4292
- if (action === "verify") {
4293
- const r = palAutoVerify(db);
4294
- console.log(`${c.dim}checked ${r.checked} · closed ${r.closed.length} · reopened ${r.reopened.length}${c.reset}`);
4295
- return true;
4296
- }
4297
- if (action === "import") {
4298
- const r = importLegacyLedger(db);
4299
- console.log(`${c.green}✓${c.reset} imported ${r.imported} item(s) from the legacy ledger`);
4300
- return true;
4301
- }
4302
- console.error(`${c.red}Unknown pal action: ${action}${c.reset}`);
4303
- console.error("Use: kit memory pal [list|add|done|snooze|verify|import]");
4304
- return false;
4305
- }
4306
- finally {
4307
- db.close();
4308
- }
4309
- }
4310
- console.error(`${c.red}Unknown memory subcommand: ${subcommand}${c.reset}`);
4311
- console.error("Use: kit memory index | search <query> | stats | install | uninstall | pal");
4312
- return false;
4313
- }
4314
3362
  async function main() {
4315
3363
  const args = process.argv.slice(2);
4316
3364
  const command = args[0];
@@ -4360,135 +3408,71 @@ async function main() {
4360
3408
  process.exitCode = 0;
4361
3409
  return;
4362
3410
  }
4363
- switch (command) {
4364
- case "version":
4365
- ok = cmdVersion();
4366
- break;
4367
- case "help":
4368
- ok = cmdHelp(args[1]);
4369
- break;
4370
- case "completions": {
4371
- const shell = args[1];
4372
- const script = generateCompletions(shell);
4373
- if (!script) {
4374
- console.error(`Unknown shell: ${shell}. Use: bash, zsh, fish`);
4375
- process.exitCode = 1;
4376
- return;
4377
- }
4378
- process.stdout.write(script);
4379
- ok = true;
4380
- break;
3411
+ // version/help/completions need bespoke handling; everything else is a flat
3412
+ // command->fn dispatch (was a ~40-case switch — the main complexity driver).
3413
+ if (command === "version") {
3414
+ ok = cmdVersion();
3415
+ }
3416
+ else if (command === "help") {
3417
+ ok = cmdHelp(args[1]);
3418
+ }
3419
+ else if (command === "completions") {
3420
+ const shell = args[1];
3421
+ const script = generateCompletions(shell);
3422
+ if (!script) {
3423
+ console.error(`Unknown shell: ${shell}. Use: bash, zsh, fish`);
3424
+ process.exitCode = 1;
3425
+ return;
4381
3426
  }
4382
- case "whoami":
4383
- ok = await cmdWhoami();
4384
- break;
4385
- case "check":
4386
- case undefined:
4387
- ok = await cmdCheck();
4388
- break;
4389
- case "init":
4390
- ok = await cmdInit();
4391
- break;
4392
- case "upgrade":
4393
- ok = await cmdUpgrade();
4394
- break;
4395
- case "install":
4396
- ok = await cmdInstall();
4397
- break;
4398
- case "login":
4399
- ok = await cmdLogin();
4400
- break;
4401
- case "secrets":
4402
- ok = await cmdSecrets();
4403
- break;
4404
- case "setup":
4405
- ok = await cmdSetup();
4406
- break;
4407
- case "skills":
4408
- ok = await cmdSkills();
4409
- break;
4410
- case "fix":
4411
- ok = await cmdFix();
4412
- break;
4413
- case "escalate":
4414
- ok = await cmdEscalate();
4415
- break;
4416
- case "governance":
4417
- ok = await cmdGovernance();
4418
- break;
4419
- case "agent-config":
4420
- ok = await cmdAgentConfig();
4421
- break;
4422
- case "hooks":
4423
- ok = await cmdHooks();
4424
- break;
4425
- case "add":
4426
- ok = await cmdAdd();
4427
- break;
4428
- case "audit":
4429
- ok = await cmdAudit();
4430
- break;
4431
- case "auth":
4432
- ok = await cmdAuth();
4433
- break;
4434
- case "mcp":
4435
- ok = await cmdMcp();
4436
- break;
4437
- case "env":
4438
- ok = await cmdEnv();
4439
- break;
4440
- case "doctor":
4441
- ok = await cmdDoctor();
4442
- break;
4443
- case "analyze":
4444
- ok = await cmdAnalyze();
4445
- break;
4446
- case "security":
4447
- ok = await cmdSecurity();
4448
- break;
4449
- case "create-plugin":
4450
- ok = await cmdCreatePlugin();
4451
- break;
4452
- case "plugin":
4453
- ok = await cmdPlugin();
4454
- break;
4455
- case "ci":
4456
- ok = await cmdCi();
4457
- break;
4458
- case "clone":
4459
- ok = await cmdClone();
4460
- break;
4461
- case "run":
4462
- ok = await cmdRun();
4463
- break;
4464
- case "open":
4465
- ok = await cmdOpen();
4466
- break;
4467
- case "context":
4468
- ok = await cmdContext();
4469
- break;
4470
- case "triage":
4471
- ok = await cmdTriage();
4472
- break;
4473
- case "baseline":
4474
- ok = await cmdBaseline();
4475
- break;
4476
- case "design":
4477
- ok = await cmdDesign();
4478
- break;
4479
- case "review":
4480
- ok = await cmdReview();
4481
- break;
4482
- case "pkg":
4483
- ok = await cmdPkg();
4484
- break;
4485
- case "team":
4486
- ok = await cmdTeam();
4487
- break;
4488
- case "memory":
4489
- ok = await cmdMemory();
4490
- break;
4491
- default: {
3427
+ process.stdout.write(script);
3428
+ ok = true;
3429
+ }
3430
+ else {
3431
+ const COMMANDS = {
3432
+ status: cmdStatus,
3433
+ whoami: cmdWhoami,
3434
+ check: cmdCheck,
3435
+ init: cmdInit,
3436
+ upgrade: cmdUpgrade,
3437
+ install: cmdInstall,
3438
+ login: cmdLogin,
3439
+ secrets: cmdSecrets,
3440
+ setup: cmdSetup,
3441
+ skills: cmdSkills,
3442
+ fix: cmdFix,
3443
+ escalate: cmdEscalate,
3444
+ governance: cmdGovernance,
3445
+ "agent-config": cmdAgentConfig,
3446
+ hooks: cmdHooks,
3447
+ add: cmdAdd,
3448
+ audit: cmdAudit,
3449
+ auth: cmdAuth,
3450
+ mcp: cmdMcp,
3451
+ env: cmdEnv,
3452
+ doctor: cmdDoctor,
3453
+ analyze: cmdAnalyze,
3454
+ security: cmdSecurity,
3455
+ "create-plugin": cmdCreatePlugin,
3456
+ plugin: cmdPlugin,
3457
+ ci: cmdCi,
3458
+ clone: cmdClone,
3459
+ run: cmdRun,
3460
+ open: cmdOpen,
3461
+ context: cmdContext,
3462
+ triage: cmdTriage,
3463
+ baseline: cmdBaseline,
3464
+ design: cmdDesign,
3465
+ review: cmdReview,
3466
+ pkg: cmdPkg,
3467
+ team: cmdTeam,
3468
+ memory: cmdMemory,
3469
+ };
3470
+ // no command → `check` (the prior `case undefined` behaviour).
3471
+ const handler = COMMANDS[command ?? "check"];
3472
+ if (handler) {
3473
+ ok = await handler();
3474
+ }
3475
+ else {
4492
3476
  console.error(`Unknown command: ${command}`);
4493
3477
  const { didYouMean } = await import("./utils/didYouMean.js");
4494
3478
  const knownCommands = [
@@ -4526,5 +3510,7 @@ async function main() {
4526
3510
  }
4527
3511
  }
4528
3512
  }
4529
- main();
3513
+ // Entrypoint — fire-and-forget by design; main() handles its own errors and sets
3514
+ // process.exitCode. `void` marks the intentional non-await for no-floating-promises.
3515
+ void main();
4530
3516
  //# sourceMappingURL=cli.js.map