sandstream-kit 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/agent-config.d.ts +25 -1
- package/dist/agent-config.js +75 -2
- package/dist/agent-config.js.map +1 -1
- package/dist/check-security.d.ts +6 -0
- package/dist/check-security.js +165 -21
- package/dist/check-security.js.map +1 -1
- package/dist/cli.js +217 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory.js +6 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js.map +1 -1
- package/dist/context-lock.d.ts +8 -0
- package/dist/context-lock.js +10 -0
- package/dist/context-lock.js.map +1 -1
- package/dist/memory/install.d.ts +1 -0
- package/dist/memory/install.js +35 -12
- package/dist/memory/install.js.map +1 -1
- package/dist/recommended.d.ts +22 -0
- package/dist/recommended.js +36 -0
- package/dist/recommended.js.map +1 -0
- package/dist/secret-backends.d.ts +3 -0
- package/dist/secret-backends.js +35 -11
- package/dist/secret-backends.js.map +1 -1
- package/dist/toml-generator.d.ts +9 -0
- package/dist/toml-generator.js +69 -6
- package/dist/toml-generator.js.map +1 -1
- package/dist/utils/resolveTool.d.ts +15 -0
- package/dist/utils/resolveTool.js +31 -0
- package/dist/utils/resolveTool.js.map +1 -0
- package/dist/vault-meta.d.ts +37 -0
- package/dist/vault-meta.js +22 -0
- package/dist/vault-meta.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { writeFile, access, mkdir } from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolve, dirname, join } from "node:path";
|
|
@@ -46,7 +46,8 @@ import { getBudgetStatus, formatBudgetStatus } from "./budget.js";
|
|
|
46
46
|
import { formatGovernanceStatus, mergeGovernanceConfigAsync } from "./governance.js";
|
|
47
47
|
import { withGovernance } from "./governance-middleware.js";
|
|
48
48
|
import { SKIPPED_COMMITS_LOG } from "./hooks.js";
|
|
49
|
-
import { writeAgentConfig, detectAgentTargets } from "./agent-config.js";
|
|
49
|
+
import { writeAgentConfig, detectAgentTargets, installKitPermissions } from "./agent-config.js";
|
|
50
|
+
import { applyRecommendedHardening } from "./recommended.js";
|
|
50
51
|
import { checkHooks, isGitRepository } from "./check-hooks.js";
|
|
51
52
|
import { readkitMeta, readSkillsLock, readCliLock, updateSkillsLock, updateCliLock, } from "./lock.js";
|
|
52
53
|
import { provisionService, listAvailableServices, getServiceInfo } from "./provision.js";
|
|
@@ -55,7 +56,7 @@ import { promptConfirm } from "./utils/prompt.js";
|
|
|
55
56
|
import { c } from "./utils/colors.js";
|
|
56
57
|
import { gatherStatus } from "./status.js";
|
|
57
58
|
import { KIT_FILE, resolveConfigPath } from "./cli-shared.js";
|
|
58
|
-
import { checkContext, applyContext, contextPrompt, gatherLive, suggestContextToml } from "./context-lock.js";
|
|
59
|
+
import { checkContext, applyContext, contextPrompt, gatherLive, suggestContextToml, hasLockableContext } from "./context-lock.js";
|
|
59
60
|
import { cmdEnv } from "./commands/env.js";
|
|
60
61
|
import { cmdAuth } from "./commands/auth.js";
|
|
61
62
|
import { cmdAudit } from "./commands/audit.js";
|
|
@@ -65,6 +66,8 @@ import { resolveAllAuth } from "./service-auth.js";
|
|
|
65
66
|
import { runDoctor } from "./doctor.js";
|
|
66
67
|
import { detectStack } from "./stack-detector.js";
|
|
67
68
|
import { generateToml } from "./toml-generator.js";
|
|
69
|
+
import { vaultMeta } from "./vault-meta.js";
|
|
70
|
+
import { vaultCliInstalled } from "./secret-backends.js";
|
|
68
71
|
import { createPlugin } from "./create-plugin.js";
|
|
69
72
|
import { cmdPlugin } from "./plugins-cli.js";
|
|
70
73
|
import { cloneRepository } from "./clone.js";
|
|
@@ -629,6 +632,27 @@ async function cmdSecrets() {
|
|
|
629
632
|
else if (skipped === "nothing-resolved") {
|
|
630
633
|
console.log(`\n ${c.yellow}!${c.reset} Skipped .env.local ${c.dim}— no secrets resolved (vault empty/unauthed); existing file left intact${c.reset}`);
|
|
631
634
|
}
|
|
635
|
+
// Loud, actionable vault-readiness flag. A chosen vault that resolves zero
|
|
636
|
+
// secrets is almost always "CLI installed but not logged in" — surface
|
|
637
|
+
// that once, with the exact next command, instead of leaving the user to
|
|
638
|
+
// infer it from a column of per-key ✗ lines.
|
|
639
|
+
const meta = vaultMeta(secretsConfig.store);
|
|
640
|
+
const resolvedCount = results.filter((r) => r.resolved).length;
|
|
641
|
+
if (meta && results.length > 0 && resolvedCount === 0) {
|
|
642
|
+
const installed = meta.miseTool ? await vaultCliInstalled(meta.miseTool) : true;
|
|
643
|
+
console.log(`\n ${c.yellow}${c.bold}! Vault "${secretsConfig.store}" is configured but resolved 0 secrets.${c.reset}`);
|
|
644
|
+
if (meta.miseTool && !installed) {
|
|
645
|
+
console.log(` ${c.dim}The ${meta.label} CLI isn't installed yet — run ${c.reset}${c.bold}kit setup${c.reset}${c.dim} (installs it via mise).${c.reset}`);
|
|
646
|
+
}
|
|
647
|
+
else if (meta.loginCmd) {
|
|
648
|
+
console.log(` ${c.dim}The ${meta.label} CLI is installed but not authenticated. Log in:${c.reset}`);
|
|
649
|
+
console.log(` ${c.bold}${meta.loginCmd}${c.reset}`);
|
|
650
|
+
if (meta.initCmd) {
|
|
651
|
+
console.log(` ${c.dim}then bind this repo:${c.reset} ${c.bold}${meta.initCmd}${c.reset}`);
|
|
652
|
+
}
|
|
653
|
+
console.log(` ${c.dim}then re-run ${c.reset}${c.bold}kit secrets${c.reset}${c.dim}.${c.reset}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
632
656
|
console.log();
|
|
633
657
|
return allOk;
|
|
634
658
|
});
|
|
@@ -705,10 +729,52 @@ async function cmdEscalate() {
|
|
|
705
729
|
return false;
|
|
706
730
|
});
|
|
707
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* Run a command string from a `.kit.toml [setup]` field. These are commands the
|
|
734
|
+
* user configured themselves, but kit's exec invariant forbids a shell — so we
|
|
735
|
+
* split on whitespace (fine for `pnpm install`, `supabase db push`, `uv sync`,
|
|
736
|
+
* `go mod download`) and REFUSE anything with shell operators rather than
|
|
737
|
+
* mis-running it. Returns true on exit 0.
|
|
738
|
+
*/
|
|
739
|
+
async function runConfiguredCommand(label, cmdStr) {
|
|
740
|
+
if (/[&|;<>`$()]/.test(cmdStr)) {
|
|
741
|
+
console.log(` ${c.yellow}!${c.reset} ${label}: ${c.dim}has shell operators — run it yourself: ${c.reset}${c.bold}${cmdStr}${c.reset}`);
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
console.log(` ${c.dim}$ ${cmdStr}${c.reset}`);
|
|
745
|
+
const res = await executeCommand({ commandArgs: cmdStr.trim().split(/\s+/), cwd: process.cwd() });
|
|
746
|
+
if (res.exitCode === 0) {
|
|
747
|
+
console.log(` ${c.green}✓${c.reset} ${label}`);
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
console.log(` ${c.red}✗${c.reset} ${label} ${c.dim}(exit ${res.exitCode})${c.reset}`);
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
708
753
|
async function cmdSetup() {
|
|
709
754
|
console.log(`${c.bold}${c.cyan}kit setup${c.reset}`);
|
|
710
755
|
console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
|
|
711
756
|
const config = await loadConfig(resolveConfigPath());
|
|
757
|
+
// Recommended profile: an explicit flag always wins; otherwise ASK
|
|
758
|
+
// interactively (the flag is just the scriptable answer to this question).
|
|
759
|
+
// CI/agents without a flag get the core setup — we never silently wire global
|
|
760
|
+
// ~/.claude hooks or the repo's git hooks without an explicit yes.
|
|
761
|
+
let recommended;
|
|
762
|
+
if (hasFlag(process.argv, "--recommended")) {
|
|
763
|
+
recommended = true;
|
|
764
|
+
}
|
|
765
|
+
else if (hasFlag(process.argv, "--minimal") || hasFlag(process.argv, "--no-recommended")) {
|
|
766
|
+
recommended = false;
|
|
767
|
+
}
|
|
768
|
+
else if (isNonInteractive()) {
|
|
769
|
+
recommended = false;
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
recommended = await promptConfirm(`Use the recommended profile? Wires cross-harness memory hooks (in ~/.claude) + git secret-scan${config.context ? " + context-check" : ""} gates after the core steps. [Y/n] `, 10000, true);
|
|
773
|
+
console.log();
|
|
774
|
+
}
|
|
775
|
+
if (recommended) {
|
|
776
|
+
console.log(`${c.dim}Recommended profile on — memory + git hooks wired after the core steps.${c.reset}\n`);
|
|
777
|
+
}
|
|
712
778
|
// Step 1: Install
|
|
713
779
|
console.log(`${c.bold}[1/6] Install${c.reset}`);
|
|
714
780
|
const installOk = await cmdInstall();
|
|
@@ -717,6 +783,13 @@ async function cmdSetup() {
|
|
|
717
783
|
console.log(`${c.dim}Fix the issues above and run ${c.reset}${c.bold}kit setup${c.reset}${c.dim} again.${c.reset}`);
|
|
718
784
|
return false;
|
|
719
785
|
}
|
|
786
|
+
// Project dependencies. cmdInstall above provisions the TOOLCHAIN (node, pnpm,
|
|
787
|
+
// … via mise); now install the project's own deps so the repo actually works
|
|
788
|
+
// after setup. The generated [setup].install was never executed before — kit
|
|
789
|
+
// installed the toolchain but left node_modules absent.
|
|
790
|
+
if (config.setup?.install) {
|
|
791
|
+
await runConfiguredCommand("deps installed", config.setup.install);
|
|
792
|
+
}
|
|
720
793
|
// Step 2: Git Hooks
|
|
721
794
|
if (config.hooks && Object.keys(config.hooks).length > 0 && isGitRepository()) {
|
|
722
795
|
console.log(`${c.bold}[2/6] Git Hooks${c.reset}`);
|
|
@@ -731,7 +804,39 @@ async function cmdSetup() {
|
|
|
731
804
|
}
|
|
732
805
|
// Step 4: Secrets
|
|
733
806
|
console.log(`${c.bold}[4/6] Secrets${c.reset}`);
|
|
807
|
+
// Harden .gitignore BEFORE secrets are materialized. kit's headline is
|
|
808
|
+
// "secret-safe", but cmdSecrets writes .env.local below — if the repo's
|
|
809
|
+
// .gitignore doesn't already cover it, the next `git add .` stages real
|
|
810
|
+
// secrets. Patching is a non-destructive, repo-local append, so we do it by
|
|
811
|
+
// default and announce it (standalone `kit security check-gitignore --fix`
|
|
812
|
+
// remains for the manual path).
|
|
813
|
+
if (isGitRepository()) {
|
|
814
|
+
const gi = await checkGitignore(process.cwd());
|
|
815
|
+
if (gi.missingPatterns.length > 0) {
|
|
816
|
+
const patched = await patchGitignore(process.cwd());
|
|
817
|
+
const names = gi.missingPatterns.slice(0, 3).map((m) => m.pattern).join(", ");
|
|
818
|
+
console.log(` ${c.green}✓${c.reset} hardened .gitignore ${c.dim}(+${patched.added}: ${names}${gi.missingPatterns.length > 3 ? ", …" : ""}) — review + commit it${c.reset}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
734
821
|
const secretsOk = await cmdSecrets();
|
|
822
|
+
// [setup].migrate / .seed are intentionally NOT auto-run: a configured migrate
|
|
823
|
+
// (`supabase db push`, `prisma migrate deploy`) can mutate a linked — possibly
|
|
824
|
+
// production — database. Run only on explicit opt-in; otherwise surface the
|
|
825
|
+
// exact command so applying it stays a deliberate human action.
|
|
826
|
+
if (config.setup?.migrate || config.setup?.seed) {
|
|
827
|
+
if (hasFlag(process.argv, "--with-migrate")) {
|
|
828
|
+
console.log(`${c.bold}[+] Migrate / seed${c.reset}`);
|
|
829
|
+
if (config.setup.migrate)
|
|
830
|
+
await runConfiguredCommand("migrate", config.setup.migrate);
|
|
831
|
+
if (config.setup.seed)
|
|
832
|
+
await runConfiguredCommand("seed", config.setup.seed);
|
|
833
|
+
console.log();
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
const cmds = [config.setup.migrate, config.setup.seed].filter(Boolean).join(" · ");
|
|
837
|
+
console.log(` ${c.yellow}!${c.reset} ${c.dim}Skipping migrate/seed (may mutate a real DB). Run deliberately: ${c.reset}${c.bold}${cmds}${c.reset}${c.dim} or ${c.reset}${c.bold}kit setup --with-migrate${c.reset}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
735
840
|
// Step 5: Agent config — teach the present agent(s) to use kit. Idempotent;
|
|
736
841
|
// only writes a managed block, so re-running setup leaves it unchanged.
|
|
737
842
|
console.log(`${c.bold}[5/6] Agent config${c.reset}`);
|
|
@@ -745,11 +850,43 @@ async function cmdSetup() {
|
|
|
745
850
|
console.log(` ${mark}${c.reset} ${r.agent} ${c.dim}→ ${r.file} (${r.action})${c.reset}`);
|
|
746
851
|
}
|
|
747
852
|
}
|
|
853
|
+
// Let the agent actually run kit: grant the read-only kit commands (same as
|
|
854
|
+
// `kit agent-config`). Without this the agent hits the permission wall.
|
|
855
|
+
const perms = await installKitPermissions();
|
|
856
|
+
if (perms.action === "created" || perms.action === "updated") {
|
|
857
|
+
console.log(` ${c.green}✓${c.reset} allowed ${perms.added.length} read-only kit command(s) ${c.dim}→ ${perms.file}${c.reset}`);
|
|
858
|
+
}
|
|
748
859
|
console.log();
|
|
749
860
|
// Step 6: Verify
|
|
750
861
|
console.log(`${c.bold}[6/6] Verify${c.reset}`);
|
|
751
862
|
const verifyOk = await cmdCheck();
|
|
752
|
-
|
|
863
|
+
// Recommended hardening: memory hooks + git hooks (opt-in via --recommended).
|
|
864
|
+
if (recommended) {
|
|
865
|
+
console.log(`\n${c.bold}[+] Recommended hardening${c.reset}`);
|
|
866
|
+
const h = await applyRecommendedHardening(config);
|
|
867
|
+
for (const e of h.memory.added)
|
|
868
|
+
console.log(` ${c.green}✓${c.reset} memory hook ${c.dim}${e}${c.reset}`);
|
|
869
|
+
if (h.memory.added.length === 0)
|
|
870
|
+
console.log(` ${c.dim}= memory hooks already wired${c.reset}`);
|
|
871
|
+
if (!h.memory.resolved) {
|
|
872
|
+
console.log(` ${c.yellow}!${c.reset} ${c.dim}memory hooks use a bare \`kit\` (kit not resolvable to an absolute path)${c.reset}`);
|
|
873
|
+
}
|
|
874
|
+
for (const r of h.hooks) {
|
|
875
|
+
const icon = r.action === "failed" ? `${c.red}✗` : r.action === "skipped" ? `${c.yellow}!` : `${c.green}✓`;
|
|
876
|
+
console.log(` ${icon}${c.reset} git ${r.hookName} ${c.dim}(${r.action})${c.reset}`);
|
|
877
|
+
}
|
|
878
|
+
console.log();
|
|
879
|
+
}
|
|
880
|
+
// Project verify (e.g. the configured build). Distinct from cmdCheck above,
|
|
881
|
+
// which audits setup STATE — this proves the app actually builds. Run last so
|
|
882
|
+
// deps + secrets are in place.
|
|
883
|
+
let setupVerifyOk = true;
|
|
884
|
+
if (config.setup?.verify) {
|
|
885
|
+
console.log(`\n${c.bold}[+] Verify build${c.reset}`);
|
|
886
|
+
setupVerifyOk = await runConfiguredCommand(config.setup.verify, config.setup.verify);
|
|
887
|
+
console.log();
|
|
888
|
+
}
|
|
889
|
+
const allOk = installOk && loginOk && secretsOk && verifyOk && setupVerifyOk;
|
|
753
890
|
if (allOk) {
|
|
754
891
|
console.log(`${c.bold}${c.green}Setup complete — you're ready to go! ✓${c.reset}\n`);
|
|
755
892
|
}
|
|
@@ -776,8 +913,20 @@ async function cmdAgentConfig() {
|
|
|
776
913
|
console.log(` ${mark}${c.reset} ${r.agent} ${c.dim}→ ${r.file} (${r.action})${c.reset}`);
|
|
777
914
|
}
|
|
778
915
|
}
|
|
779
|
-
|
|
780
|
-
|
|
916
|
+
// Let the agent actually RUN kit: grant read-only kit commands in
|
|
917
|
+
// .claude/settings.json, so they don't hit the permission wall in auto mode.
|
|
918
|
+
const perms = await installKitPermissions();
|
|
919
|
+
if (perms.action === "created" || perms.action === "updated") {
|
|
920
|
+
console.log(`\n ${c.green}✓${c.reset} allowed ${c.bold}${perms.added.length}${c.reset} read-only kit command(s) in ${c.dim}${perms.file}${c.reset} ${c.dim}(so the agent can run them without a prompt)${c.reset}`);
|
|
921
|
+
}
|
|
922
|
+
else if (perms.action === "unchanged") {
|
|
923
|
+
console.log(`\n ${c.dim}= read-only kit commands already allowed in ${perms.file}${c.reset}`);
|
|
924
|
+
}
|
|
925
|
+
else if (perms.action === "failed") {
|
|
926
|
+
console.log(`\n ${c.yellow}!${c.reset} could not update ${perms.file}: ${perms.detail}`);
|
|
927
|
+
}
|
|
928
|
+
console.log(`\n${c.dim}Blocks regenerate in place on re-run; edit outside the markers freely. ` +
|
|
929
|
+
`Mutating kit commands (secrets/fix/hooks) still prompt by design.${c.reset}`);
|
|
781
930
|
return !failed;
|
|
782
931
|
}
|
|
783
932
|
async function cmdGovernance() {
|
|
@@ -901,7 +1050,12 @@ async function generateConfigFile(configPath, nonInteractive) {
|
|
|
901
1050
|
]));
|
|
902
1051
|
console.log();
|
|
903
1052
|
}
|
|
904
|
-
|
|
1053
|
+
// Detect a Dockerfile so generateToml can provision the trivy container/IaC
|
|
1054
|
+
// scanner only where it applies.
|
|
1055
|
+
const hasDockerfile = existsSync(resolve(process.cwd(), "Dockerfile")) ||
|
|
1056
|
+
existsSync(resolve(process.cwd(), "docker-compose.yml")) ||
|
|
1057
|
+
existsSync(resolve(process.cwd(), "compose.yml"));
|
|
1058
|
+
const tomlContent = generateToml(stack, { secretsStore: chosenStore, hasDockerfile });
|
|
905
1059
|
// Show diff preview
|
|
906
1060
|
console.log(`${c.bold}Preview — .kit.toml${c.reset}\n`);
|
|
907
1061
|
for (const line of tomlContent.split("\n")) {
|
|
@@ -923,8 +1077,57 @@ async function generateConfigFile(configPath, nonInteractive) {
|
|
|
923
1077
|
}
|
|
924
1078
|
await writeFile(configPath, tomlContent, "utf-8");
|
|
925
1079
|
console.log(` ${c.green}✓${c.reset} Generated ${c.bold}.kit.toml${c.reset}\n`);
|
|
1080
|
+
// Close the loop on the vault choice: tell the user exactly what `kit setup`
|
|
1081
|
+
// will provision and what they still have to do themselves (login is their
|
|
1082
|
+
// account action — kit guides it, never runs it).
|
|
1083
|
+
const meta = vaultMeta(chosenStore);
|
|
1084
|
+
if (meta) {
|
|
1085
|
+
console.log(` ${c.dim}Secret backend: ${c.reset}${c.bold}${meta.label}${c.reset}`);
|
|
1086
|
+
if (meta.miseTool) {
|
|
1087
|
+
console.log(` ${c.green}✓${c.reset} ${c.dim}${c.reset}${c.bold}kit setup${c.reset}${c.dim} will install its CLI via mise${c.reset}`);
|
|
1088
|
+
}
|
|
1089
|
+
if (meta.loginCmd) {
|
|
1090
|
+
const steps = meta.initCmd ? `${meta.loginCmd} && ${meta.initCmd}` : meta.loginCmd;
|
|
1091
|
+
console.log(` ${c.yellow}!${c.reset} ${c.dim}then authenticate (your account): ${c.reset}${c.bold}${steps}${c.reset}`);
|
|
1092
|
+
}
|
|
1093
|
+
console.log();
|
|
1094
|
+
}
|
|
926
1095
|
return "written";
|
|
927
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Brownfield context-lock offer. If the repo already talks to gcloud / vercel /
|
|
1099
|
+
* github but `.kit.toml` declares no `[context]`, surface the detected
|
|
1100
|
+
* account+project and offer to lock it. kit does NOT install or authenticate
|
|
1101
|
+
* these (cloud env's job) — it locks which account+project this repo is bound to,
|
|
1102
|
+
* the exact pairing where cross-account contamination bugs hide. Default is NO:
|
|
1103
|
+
* the values are the currently-active CLI state, which is what the lock exists to
|
|
1104
|
+
* question, so the user must confirm they're right for THIS repo first.
|
|
1105
|
+
*/
|
|
1106
|
+
async function offerContextLock(configPath, nonInteractive) {
|
|
1107
|
+
const live = await gatherLive(process.cwd());
|
|
1108
|
+
if (!hasLockableContext(live))
|
|
1109
|
+
return;
|
|
1110
|
+
const block = suggestContextToml(live);
|
|
1111
|
+
if (!block.trim())
|
|
1112
|
+
return;
|
|
1113
|
+
console.log(`${c.bold}Detected environment${c.reset} ${c.dim}— lock account+project so kit verifies the right one each session:${c.reset}\n`);
|
|
1114
|
+
for (const line of block.split("\n")) {
|
|
1115
|
+
console.log(line.trim() === "" ? "" : ` ${c.dim}${line}${c.reset}`);
|
|
1116
|
+
}
|
|
1117
|
+
console.log(`\n${c.yellow}⚠ These are the currently-active CLI values — verify each is right for THIS repo before locking.${c.reset}`);
|
|
1118
|
+
if (nonInteractive) {
|
|
1119
|
+
console.log(`${c.dim}Non-interactive: not writing. Add the block above to .kit.toml, or run ${c.reset}${c.bold}kit context check${c.reset}${c.dim} to lock it.${c.reset}\n`);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const ok = await promptConfirm("Add this [context] lock to .kit.toml? [y/N] ", 10000, false);
|
|
1123
|
+
if (!ok) {
|
|
1124
|
+
console.log(`${c.dim}Skipped — run ${c.reset}${c.bold}kit context check${c.reset}${c.dim} later to add it.${c.reset}\n`);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const existing = readFileSync(configPath, "utf-8");
|
|
1128
|
+
await writeFile(configPath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
|
|
1129
|
+
console.log(` ${c.green}✓${c.reset} Locked ${c.bold}[context]${c.reset} ${c.dim}→ verify with ${c.reset}${c.bold}kit context check${c.reset}\n`);
|
|
1130
|
+
}
|
|
928
1131
|
async function cmdInit() {
|
|
929
1132
|
console.log(`${c.bold}${c.cyan}kit init${c.reset}`);
|
|
930
1133
|
console.log(`${c.dim}${"─".repeat(50)}${c.reset}\n`);
|
|
@@ -946,6 +1149,11 @@ async function cmdInit() {
|
|
|
946
1149
|
return true; // user declined — exit 0, not an error
|
|
947
1150
|
}
|
|
948
1151
|
const config = await loadConfig(configPath);
|
|
1152
|
+
// Brownfield: offer to lock the already-active cloud/repo context (no install,
|
|
1153
|
+
// no login — just pin account+project) when none is declared yet.
|
|
1154
|
+
if (!config.context) {
|
|
1155
|
+
await offerContextLock(configPath, nonInteractive);
|
|
1156
|
+
}
|
|
949
1157
|
// Check if lock files exist
|
|
950
1158
|
const kitMeta = await readkitMeta();
|
|
951
1159
|
const skillsLock = await readSkillsLock();
|
|
@@ -3015,7 +3223,8 @@ const COMMAND_HELP = {
|
|
|
3015
3223
|
"auth revoke": "Drop the elevation marker",
|
|
3016
3224
|
"auth setup-totp": "Enroll TOTP secret (writes ~/.kit/totp-secret 0600)",
|
|
3017
3225
|
"hooks add": "Install a built-in hook (e.g. secret-scan)",
|
|
3018
|
-
setup: "Full pipeline: install → login → secrets →
|
|
3226
|
+
setup: "Full pipeline: install → login → secrets → agent config → verify",
|
|
3227
|
+
"setup --recommended": "Opinionated profile: setup + memory hooks + git secret-scan/context-check gates",
|
|
3019
3228
|
fix: "Auto-fix what is possible",
|
|
3020
3229
|
escalate: "List what needs human action",
|
|
3021
3230
|
governance: "View governance status and agent access controls",
|