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