sandstream-kit 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -92
- package/dist/adapters/expo-eas.js +1 -1
- package/dist/adapters/expo-eas.js.map +1 -1
- package/dist/audit-logging-service.js +1 -1
- package/dist/audit-logging-service.js.map +1 -1
- package/dist/author-verification.js +1 -1
- package/dist/author-verification.js.map +1 -1
- package/dist/cli-shared.d.ts +4 -0
- package/dist/cli-shared.js +11 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli.js +156 -1232
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +99 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +121 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/env.d.ts +1 -0
- package/dist/commands/env.js +149 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/hooks.d.ts +1 -0
- package/dist/commands/hooks.js +146 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +120 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +1 -0
- package/dist/commands/memory.js +534 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/mcp-server.js +59 -6
- package/dist/mcp-server.js.map +1 -1
- package/dist/memory/amazonq.d.ts +5 -0
- package/dist/memory/amazonq.js +161 -0
- package/dist/memory/amazonq.js.map +1 -0
- package/dist/memory/cline.d.ts +5 -0
- package/dist/memory/cline.js +117 -0
- package/dist/memory/cline.js.map +1 -0
- package/dist/memory/cursor.d.ts +4 -0
- package/dist/memory/cursor.js +116 -0
- package/dist/memory/cursor.js.map +1 -0
- package/dist/memory/merge.js +7 -1
- package/dist/memory/merge.js.map +1 -1
- package/dist/memory/pal.js +8 -2
- package/dist/memory/pal.js.map +1 -1
- package/dist/memory/parser.js +6 -0
- package/dist/memory/parser.js.map +1 -1
- package/dist/memory/suggest.d.ts +21 -0
- package/dist/memory/suggest.js +36 -0
- package/dist/memory/suggest.js.map +1 -0
- package/dist/onepassword.js +1 -1
- package/dist/onepassword.js.map +1 -1
- package/dist/open.js +8 -2
- package/dist/open.js.map +1 -1
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/service-auth.d.ts +31 -0
- package/dist/service-auth.js +47 -0
- package/dist/service-auth.js.map +1 -0
- package/dist/stack-detector.js +53 -76
- package/dist/stack-detector.js.map +1 -1
- package/package.json +9 -4
- package/dist/memory/backup 2.d.ts +0 -6
- package/dist/memory/backup 2.js +0 -80
- package/dist/memory/backup 2.js.map +0 -1
- package/dist/memory/db 2.d.ts +0 -40
- package/dist/memory/db 2.js +0 -233
- package/dist/memory/db 2.js.map +0 -1
- package/dist/memory/hook 2.d.ts +0 -6
- package/dist/memory/hook 2.js +0 -51
- package/dist/memory/hook 2.js.map +0 -1
- package/dist/memory/hook.test 2.d.ts +0 -1
- package/dist/memory/hook.test 2.js +0 -35
- package/dist/memory/hook.test 2.js.map +0 -1
- package/dist/memory/install 2.d.ts +0 -8
- package/dist/memory/install 2.js +0 -72
- package/dist/memory/install 2.js.map +0 -1
- package/dist/memory/install.test 2.d.ts +0 -1
- package/dist/memory/install.test 2.js +0 -59
- package/dist/memory/install.test 2.js.map +0 -1
- package/dist/memory/pal 2.d.ts +0 -47
- package/dist/memory/pal 2.js +0 -154
- package/dist/memory/pal 2.js.map +0 -1
- package/dist/memory/project 2.d.ts +0 -1
- package/dist/memory/project 2.js +0 -24
- package/dist/memory/project 2.js.map +0 -1
- package/dist/memory/scan 2.d.ts +0 -15
- package/dist/memory/scan 2.js +0 -94
- package/dist/memory/scan 2.js.map +0 -1
- package/dist/memory/scan.test 2.d.ts +0 -1
- package/dist/memory/scan.test 2.js +0 -55
- package/dist/memory/scan.test 2.js.map +0 -1
- package/dist/memory/shared 2.d.ts +0 -33
- package/dist/memory/shared 2.js +0 -120
- package/dist/memory/shared 2.js.map +0 -1
- package/dist/memory/threads 2.d.ts +0 -37
- package/dist/memory/threads 2.js +0 -50
- package/dist/memory/threads 2.js.map +0 -1
- package/dist/memory/threads.test 2.d.ts +0 -1
- package/dist/memory/threads.test 2.js +0 -66
- package/dist/memory/threads.test 2.js.map +0 -1
- package/dist/memory/types 2.d.ts +0 -52
- package/dist/memory/types 2.js +0 -5
- 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,
|
|
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
|
|
6
|
-
import { loadConfig
|
|
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 {
|
|
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,
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
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
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
case
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
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
|