wholestack 0.6.2 → 0.7.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.
@@ -590,6 +590,424 @@ async function collectLocalFiles(dir) {
590
590
  return { files, skipped };
591
591
  }
592
592
 
593
+ // src/config.ts
594
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, chmodSync, renameSync } from "fs";
595
+ import { homedir } from "os";
596
+ import { join as join2 } from "path";
597
+ var LEGACY_DIR = join2(homedir(), ".zeta-g");
598
+ var STATE_DIR = join2(homedir(), ".wholestack");
599
+ try {
600
+ if (existsSync2(LEGACY_DIR) && !existsSync2(STATE_DIR)) renameSync(LEGACY_DIR, STATE_DIR);
601
+ } catch {
602
+ }
603
+ function stateDir() {
604
+ return STATE_DIR;
605
+ }
606
+ function webBaseUrl() {
607
+ return process.env.ZETA_WEB_URL?.trim() || process.env.ZETA_API_URL?.trim() || "https://wholestack.ai";
608
+ }
609
+ function projectDirs(cwd2) {
610
+ return [join2(cwd2, ".wholestack"), join2(cwd2, ".zeta-g")];
611
+ }
612
+ var CONFIG_DIR = STATE_DIR;
613
+ var CONFIG_PATH = join2(CONFIG_DIR, "config.json");
614
+ function parseDotenv(text) {
615
+ const out = {};
616
+ for (const raw of text.split("\n")) {
617
+ const l = raw.trim();
618
+ if (!l || l.startsWith("#")) continue;
619
+ const eq = l.indexOf("=");
620
+ if (eq < 0) continue;
621
+ const k = l.slice(0, eq).trim();
622
+ let v = l.slice(eq + 1).trim();
623
+ if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
624
+ v = v.slice(1, -1);
625
+ }
626
+ if (k) out[k] = v;
627
+ }
628
+ return out;
629
+ }
630
+ function fill(src) {
631
+ for (const [k, v] of Object.entries(src)) {
632
+ if (v && process.env[k] === void 0) process.env[k] = v;
633
+ }
634
+ }
635
+ function loadStoredEnv() {
636
+ const localEnv = join2(process.cwd(), ".env");
637
+ if (existsSync2(localEnv)) {
638
+ try {
639
+ fill(parseDotenv(readFileSync2(localEnv, "utf8")));
640
+ } catch {
641
+ }
642
+ }
643
+ if (existsSync2(CONFIG_PATH)) {
644
+ try {
645
+ const json = JSON.parse(readFileSync2(CONFIG_PATH, "utf8"));
646
+ const flat = {};
647
+ for (const [k, v] of Object.entries(json)) if (typeof v === "string") flat[k] = v;
648
+ fill(flat);
649
+ } catch {
650
+ }
651
+ }
652
+ }
653
+ function saveKey(name, value) {
654
+ mkdirSync2(CONFIG_DIR, { recursive: true });
655
+ let current = {};
656
+ if (existsSync2(CONFIG_PATH)) {
657
+ try {
658
+ current = JSON.parse(readFileSync2(CONFIG_PATH, "utf8"));
659
+ } catch {
660
+ current = {};
661
+ }
662
+ }
663
+ current[name] = value;
664
+ writeFileSync2(CONFIG_PATH, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
665
+ chmodSync(CONFIG_PATH, 384);
666
+ return CONFIG_PATH;
667
+ }
668
+
669
+ // src/volition.ts
670
+ import { cwd, stdout } from "process";
671
+ function apiBase() {
672
+ return webBaseUrl().replace(/\/$/, "");
673
+ }
674
+ function jsonOut(obj) {
675
+ stdout.write(JSON.stringify(obj) + "\n");
676
+ }
677
+ async function resolveVolitionProject(nameOrId) {
678
+ const r = await apiJson(`${apiBase()}/api/projects`);
679
+ if (!r.ok) return { ok: false, error: r.error };
680
+ const projects = r.data.projects ?? [];
681
+ const byId = projects.find((p) => p.id === nameOrId);
682
+ if (byId) return { ok: true, project: byId };
683
+ const needle = nameOrId.trim().toLowerCase();
684
+ const byName = projects.find((p) => p.name?.trim().toLowerCase() === needle);
685
+ if (byName) return { ok: true, project: byName };
686
+ return { ok: false, error: `no project matches "${nameOrId}"` };
687
+ }
688
+ async function createVolitionProject(name, description) {
689
+ const r = await apiJson(
690
+ `${apiBase()}/api/projects`,
691
+ {
692
+ method: "POST",
693
+ body: JSON.stringify({
694
+ name,
695
+ description: description?.trim() || "Business handed to the Volition crew from the terminal"
696
+ })
697
+ }
698
+ );
699
+ if (!r.ok) return { ok: false, error: r.error };
700
+ const id = r.data.project?.id;
701
+ if (!id) return { ok: false, error: r.data.error ?? "project create returned no id" };
702
+ return { ok: true, id };
703
+ }
704
+ async function activateVolition(projectId, opts = {}) {
705
+ return apiJson(
706
+ `${apiBase()}/api/projects/${encodeURIComponent(projectId)}/volition/activate`,
707
+ { method: "POST", body: JSON.stringify({ live: opts.live === true }) }
708
+ );
709
+ }
710
+ async function volitionStatus(projectId) {
711
+ return apiJson(
712
+ `${apiBase()}/api/projects/${encodeURIComponent(projectId)}/volition`
713
+ );
714
+ }
715
+ async function volitionActions(projectId, limit = 20) {
716
+ const r = await apiJson(
717
+ `${apiBase()}/api/projects/${encodeURIComponent(projectId)}/volition/actions?limit=${limit}`
718
+ );
719
+ if (!r.ok) return { ok: false, error: r.error, status: r.status };
720
+ return { ok: true, actions: r.data.actions ?? [] };
721
+ }
722
+ async function volitionRunNow(projectId) {
723
+ return apiJson(
724
+ `${apiBase()}/api/projects/${encodeURIComponent(projectId)}/volition/run`,
725
+ { method: "POST", body: JSON.stringify({}) }
726
+ );
727
+ }
728
+ async function launchVolition(input2) {
729
+ const step = input2.onStep ?? (() => {
730
+ });
731
+ let projectId;
732
+ let created = false;
733
+ const target = input2.target?.trim();
734
+ if (target) {
735
+ const found = await resolveVolitionProject(target);
736
+ if (found.ok) {
737
+ projectId = found.project.id;
738
+ step(`project: ${found.project.name ?? projectId}`);
739
+ } else {
740
+ step(`creating project "${target}"`);
741
+ const made = await createVolitionProject(target, input2.description);
742
+ if (!made.ok) return { ok: false, error: made.error };
743
+ projectId = made.id;
744
+ created = true;
745
+ }
746
+ } else {
747
+ projectId = readProjectLink(input2.cwd ?? cwd())?.projectId;
748
+ if (!projectId) {
749
+ return {
750
+ ok: false,
751
+ error: 'no target \u2014 pass a business name or project id (wholestack volition "my business"), or run inside a linked project directory.'
752
+ };
753
+ }
754
+ step(`linked project: ${projectId}`);
755
+ }
756
+ step(input2.live ? "activating crew (LIVE \u2014 real external actions)" : "activating crew (sandbox)");
757
+ const r = await activateVolition(projectId, { live: input2.live });
758
+ const consoleUrl = `${apiBase()}/volition/${projectId}`;
759
+ if (!r.ok) {
760
+ return { ok: false, projectId, consoleUrl, status: r.status, activation: r.data, error: r.error };
761
+ }
762
+ if (created && input2.cwd) {
763
+ try {
764
+ writeProjectLink(input2.cwd, { projectId });
765
+ } catch {
766
+ }
767
+ }
768
+ return { ok: true, projectId, consoleUrl, activation: r.data };
769
+ }
770
+ function renderVerdictHelp(status, a) {
771
+ const out = [];
772
+ if (status === 401) {
773
+ out.push(c.red(" \u2717 sign in first \u2014 run `wholestack login`."));
774
+ } else if (status === 402) {
775
+ const price = a?.priceUsdCents ? `$${(a.priceUsdCents / 100).toFixed(0)}/mo` : "$99/mo";
776
+ out.push(c.red(` \u2717 Operation Volition is a ${price} add-on.`));
777
+ out.push(c.dim(` subscribe: ${apiBase()}${a?.checkoutPath ?? "/api/billing/addons/volition/checkout"}`));
778
+ } else if (status === 503) {
779
+ out.push(c.red(" \u2717 Volition inference is not configured."));
780
+ out.push(c.dim(" add an OpenRouter key in account secrets (web \u2192 Account \u2192 Secrets)."));
781
+ }
782
+ return out;
783
+ }
784
+ function renderActivation(projectId, consoleUrl, a) {
785
+ const out = [];
786
+ out.push(c.green(" \u2713 Volition crew activated") + (a.live ? c.red(" LIVE") : c.dim(" sandbox")));
787
+ if (a.firstRun?.runId) {
788
+ out.push(c.dim(` first cycle: ${a.firstRun.runId}`));
789
+ if (a.firstRun.summary) out.push(c.dim(` ${a.firstRun.summary}`));
790
+ } else {
791
+ out.push(c.dim(" first cycle queued \u2014 the crew runs on the hourly heartbeat."));
792
+ }
793
+ out.push(c.cyan(` console: ${consoleUrl}`));
794
+ if (!a.live) {
795
+ out.push(c.dim(" sandbox = external actions are simulated. Re-run with --live to go real."));
796
+ }
797
+ return out;
798
+ }
799
+ var VERBS = /* @__PURE__ */ new Set(["status", "actions", "run", "launch", "activate", "start"]);
800
+ async function runVolitionSubcommand(raw) {
801
+ if (raw[0] !== "volition") return null;
802
+ const rest = raw.slice(1);
803
+ const wantsJson = rest.includes("--json");
804
+ const live = rest.includes("--live");
805
+ const descIdx = rest.indexOf("--desc");
806
+ const description = descIdx >= 0 ? rest[descIdx + 1] : void 0;
807
+ const limitIdx = rest.indexOf("--limit");
808
+ const limit = limitIdx >= 0 ? Math.max(1, Number(rest[limitIdx + 1]) || 20) : 20;
809
+ const flagValueIdx = new Set(
810
+ [descIdx >= 0 ? descIdx + 1 : -1, limitIdx >= 0 ? limitIdx + 1 : -1].filter((i) => i >= 0)
811
+ );
812
+ const positional = rest.filter((a, i) => !a.startsWith("-") && !flagValueIdx.has(i));
813
+ if (!process.env.ZETA_API_KEY?.trim()) {
814
+ line(c.red(" login required \u2014 run `wholestack login` first."));
815
+ return 1;
816
+ }
817
+ const verb = VERBS.has(positional[0] ?? "") ? positional[0] : "launch";
818
+ const target = verb === "launch" && !VERBS.has(positional[0] ?? "") ? positional.join(" ") || void 0 : positional.slice(1).join(" ") || void 0;
819
+ const projectIdOr = async () => {
820
+ if (target) {
821
+ const found = await resolveVolitionProject(target);
822
+ if (found.ok) return found.project.id;
823
+ line(c.red(` \u2717 ${found.error}`));
824
+ return null;
825
+ }
826
+ const linked = readProjectLink(cwd())?.projectId;
827
+ if (!linked) {
828
+ line(c.red(" no project \u2014 pass an id/name or run inside a linked project."));
829
+ return null;
830
+ }
831
+ return linked;
832
+ };
833
+ if (verb === "status") {
834
+ const id = await projectIdOr();
835
+ if (!id) return 1;
836
+ const r = await volitionStatus(id);
837
+ if (wantsJson) {
838
+ jsonOut(r.ok ? { ok: true, ...r.data } : { ok: false, error: r.error });
839
+ return r.ok ? 0 : 1;
840
+ }
841
+ line("");
842
+ if (!r.ok) {
843
+ line(c.red(` \u2717 ${r.error}`));
844
+ return 1;
845
+ }
846
+ const op = r.data.operator ?? {};
847
+ const on = op.enabled === true;
848
+ line(
849
+ " " + (on ? c.green("\u25CF operating") : c.dim("\u25CB idle")) + (op.sandbox === false ? c.red(" LIVE") : c.dim(" sandbox"))
850
+ );
851
+ const counts = r.data.actionCounts ?? {};
852
+ const countStr = Object.entries(counts).map(([k, v]) => `${k} ${v}`).join(" \xB7 ");
853
+ if (countStr) line(c.dim(` actions: ${countStr}`));
854
+ for (const run of (r.data.recentRuns ?? []).slice(0, 5)) {
855
+ line(
856
+ c.dim(
857
+ ` ${run.status === "completed" ? "\u2713" : "\u2022"} ${run.runId ?? "run"} ${run.trigger ?? ""} ${run.summary ?? ""}`.trimEnd()
858
+ )
859
+ );
860
+ }
861
+ line(c.cyan(` console: ${apiBase()}/volition/${id}`));
862
+ return 0;
863
+ }
864
+ if (verb === "actions") {
865
+ const id = await projectIdOr();
866
+ if (!id) return 1;
867
+ const r = await volitionActions(id, limit);
868
+ if (wantsJson) {
869
+ jsonOut(r.ok ? { ok: true, actions: r.actions } : { ok: false, error: r.error });
870
+ return r.ok ? 0 : 1;
871
+ }
872
+ line("");
873
+ if (!r.ok) {
874
+ line(c.red(` \u2717 ${r.error}`));
875
+ return 1;
876
+ }
877
+ if (r.actions.length === 0) {
878
+ line(c.dim(" no actions yet \u2014 run a cycle (`wholestack volition run`)."));
879
+ return 0;
880
+ }
881
+ for (const a of r.actions) {
882
+ const mark = a.status === "executed" ? c.green("\u2713") : a.status === "blocked" ? c.red("\u2717") : c.cyan("\u2022");
883
+ line(
884
+ ` ${mark} ${c.bold(a.agent ?? "agent")} ${c.dim(a.kind ?? "")} ${a.summary ?? ""}`.trimEnd()
885
+ );
886
+ const proof = a.proofHash ? ` proof ${a.proofHash.slice(0, 12)}\u2026` : "";
887
+ line(c.dim(` ${a.status ?? ""}${a.verdict ? ` \xB7 ${a.verdict}` : ""}${proof}`));
888
+ }
889
+ return 0;
890
+ }
891
+ if (verb === "run") {
892
+ const id = await projectIdOr();
893
+ if (!id) return 1;
894
+ line(c.dim(" running one cycle\u2026"));
895
+ const r = await volitionRunNow(id);
896
+ if (wantsJson) {
897
+ jsonOut(r.ok ? { ok: true, ...r.data } : { ok: false, error: r.error });
898
+ return r.ok ? 0 : 1;
899
+ }
900
+ if (!r.ok) {
901
+ line(c.red(` \u2717 ${r.error}`));
902
+ return 1;
903
+ }
904
+ line(c.green(` \u2713 cycle ${r.data.runId ?? "done"}`));
905
+ if (r.data.summary) line(c.dim(` ${r.data.summary}`));
906
+ return 0;
907
+ }
908
+ const result = await launchVolition({
909
+ target,
910
+ live,
911
+ description,
912
+ cwd: cwd(),
913
+ onStep: (s) => line(c.dim(` ${s}`))
914
+ });
915
+ if (wantsJson) {
916
+ jsonOut(
917
+ result.ok ? { ok: true, projectId: result.projectId, consoleUrl: result.consoleUrl, ...result.activation } : { ok: false, error: result.error, status: result.status, ...result.activation }
918
+ );
919
+ return result.ok ? 0 : 1;
920
+ }
921
+ line("");
922
+ if (!result.ok) {
923
+ const help = renderVerdictHelp(result.status, result.activation);
924
+ if (help.length > 0) for (const h of help) line(h);
925
+ else line(c.red(` \u2717 ${result.error}`));
926
+ return 1;
927
+ }
928
+ for (const l of renderActivation(result.projectId, result.consoleUrl, result.activation ?? {}))
929
+ line(l);
930
+ return 0;
931
+ }
932
+ var volitionCommands = [
933
+ {
934
+ name: "volition",
935
+ aliases: ["crew"],
936
+ summary: "hand this business to the autonomous Volition crew",
937
+ source: "builtin",
938
+ run: async (ctx) => {
939
+ const arg = ctx.args.trim();
940
+ const live = /(^|\s)--live(\s|$)/.test(arg);
941
+ const target = arg.replace(/(^|\s)--live(\s|$)/g, " ").trim() || void 0;
942
+ const result = await launchVolition({
943
+ target,
944
+ live,
945
+ cwd: ctx.cwd,
946
+ onStep: (s) => ctx.print(" " + c.dim(s))
947
+ });
948
+ ctx.print();
949
+ if (!result.ok) {
950
+ const help = renderVerdictHelp(result.status, result.activation);
951
+ if (help.length > 0) for (const h of help) ctx.print(h);
952
+ else ctx.print(" " + c.red(`\u2717 ${result.error}`));
953
+ return { type: "handled" };
954
+ }
955
+ for (const l of renderActivation(
956
+ result.projectId,
957
+ result.consoleUrl,
958
+ result.activation ?? {}
959
+ ))
960
+ ctx.print(l);
961
+ return { type: "handled" };
962
+ }
963
+ },
964
+ {
965
+ name: "volition-status",
966
+ aliases: ["crew-status"],
967
+ summary: "operator state + recent runs of the Volition crew",
968
+ source: "builtin",
969
+ run: async (ctx) => {
970
+ const target = ctx.args.trim() || void 0;
971
+ let id = target;
972
+ if (id) {
973
+ const found = await resolveVolitionProject(id);
974
+ if (!found.ok) {
975
+ ctx.print(" " + c.red(`\u2717 ${found.error}`));
976
+ return { type: "handled" };
977
+ }
978
+ id = found.project.id;
979
+ } else {
980
+ id = readProjectLink(ctx.cwd)?.projectId;
981
+ }
982
+ if (!id) {
983
+ ctx.print(" " + c.red("no project \u2014 pass an id/name or run inside a linked project."));
984
+ return { type: "handled" };
985
+ }
986
+ const r = await volitionStatus(id);
987
+ ctx.print();
988
+ if (!r.ok) {
989
+ ctx.print(" " + c.red(`\u2717 ${r.error}`));
990
+ return { type: "handled" };
991
+ }
992
+ const op = r.data.operator ?? {};
993
+ ctx.print(
994
+ " " + (op.enabled === true ? c.green("\u25CF operating") : c.dim("\u25CB idle")) + (op.sandbox === false ? c.red(" LIVE") : c.dim(" sandbox"))
995
+ );
996
+ const counts = Object.entries(r.data.actionCounts ?? {}).map(([k, v]) => `${k} ${v}`).join(" \xB7 ");
997
+ if (counts) ctx.print(" " + c.dim(`actions: ${counts}`));
998
+ for (const run of (r.data.recentRuns ?? []).slice(0, 5)) {
999
+ ctx.print(
1000
+ " " + c.dim(
1001
+ `${run.status === "completed" ? "\u2713" : "\u2022"} ${run.runId ?? "run"} ${run.summary ?? ""}`.trimEnd()
1002
+ )
1003
+ );
1004
+ }
1005
+ ctx.print(" " + c.cyan(`console: ${apiBase()}/volition/${id}`));
1006
+ return { type: "handled" };
1007
+ }
1008
+ }
1009
+ ];
1010
+
593
1011
  // src/launch.ts
594
1012
  function deployError(d, fallback) {
595
1013
  return {
@@ -816,14 +1234,14 @@ async function patchBusiness(apiUrl, body) {
816
1234
 
817
1235
  // src/prover.ts
818
1236
  import { spawn as spawn2 } from "child_process";
819
- import { join as join3 } from "path";
820
- import { existsSync as existsSync3 } from "fs";
1237
+ import { join as join4 } from "path";
1238
+ import { existsSync as existsSync4 } from "fs";
821
1239
 
822
1240
  // src/zeta-engine.ts
823
1241
  import { spawn } from "child_process";
824
1242
  import { tmpdir } from "os";
825
- import { join as join2, dirname as dirname2, normalize as pathNormalize2, resolve as resolve2, sep as sep2 } from "path";
826
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1243
+ import { join as join3, dirname as dirname2, normalize as pathNormalize2, resolve as resolve2, sep as sep2 } from "path";
1244
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
827
1245
  import { fileURLToPath } from "url";
828
1246
  function normalize(result) {
829
1247
  const files = Array.isArray(result.files) ? result.files : [];
@@ -831,11 +1249,13 @@ function normalize(result) {
831
1249
  const proofVerdict = result.proof?.verdict;
832
1250
  const shipped = proofVerdict ? proofVerdict === "SHIP" : !!verify.ok;
833
1251
  const pending = result.proof?.proofStatus === "pending" && shipped;
1252
+ const unverified = result.proof?.proofStatus === "unverified";
834
1253
  return {
835
1254
  ok: true,
836
1255
  buildId: result.buildId ?? null,
837
1256
  verdict: shipped ? "SHIP" : "NO_SHIP",
838
1257
  proofPending: pending,
1258
+ unverified,
839
1259
  themeId: result.themeId ?? null,
840
1260
  fileCount: files.length,
841
1261
  spec: typeof result.spec === "string" ? result.spec : null,
@@ -902,13 +1322,17 @@ async function httpBuild(zetaApiUrl, body, onPhase) {
902
1322
  let resp;
903
1323
  try {
904
1324
  const apiKey = process.env.ZETA_API_KEY?.trim();
1325
+ const byokKey = process.env.ZETA_BYOK_KEY?.trim();
1326
+ const byokProvider = process.env.ZETA_BYOK_PROVIDER?.trim();
905
1327
  resp = await fetch(`${zetaApiUrl}/api/zeta/build`, {
906
1328
  method: "POST",
907
1329
  headers: {
908
1330
  "content-type": "application/json",
909
1331
  // Membership: lets the engine verify the subscription and return the code
910
1332
  // (and charge one credit per app). Without it, only the preview comes back.
911
- ...apiKey ? { authorization: `Bearer ${apiKey}` } : {}
1333
+ ...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
1334
+ ...byokKey ? { "x-zeta-byok-key": byokKey } : {},
1335
+ ...byokKey && byokProvider ? { "x-zeta-byok-provider": byokProvider } : {}
912
1336
  },
913
1337
  body: JSON.stringify({ ...body, mode: "idea" })
914
1338
  });
@@ -1067,8 +1491,8 @@ async function deliverBuild(zetaApiUrl, buildId, destDir, onPhase) {
1067
1491
  const rel = pathNormalize2(f.path).replace(/^(\.\.(\/|\\|$))+/, "");
1068
1492
  const full = resolve2(root, rel);
1069
1493
  if (full !== root && !full.startsWith(root + sep2)) continue;
1070
- mkdirSync2(dirname2(full), { recursive: true });
1071
- writeFileSync2(full, f.content);
1494
+ mkdirSync3(dirname2(full), { recursive: true });
1495
+ writeFileSync3(full, f.content);
1072
1496
  written += 1;
1073
1497
  onPhase?.(`wrote ${rel}`);
1074
1498
  }
@@ -1078,7 +1502,7 @@ async function deliverBuild(zetaApiUrl, buildId, destDir, onPhase) {
1078
1502
  function findRepoRoot(start) {
1079
1503
  let dir = start ?? dirname2(fileURLToPath(import.meta.url));
1080
1504
  for (let i = 0; i < 12; i++) {
1081
- if (existsSync2(join2(dir, "scripts", "zeta-build-worker.mts"))) return dir;
1505
+ if (existsSync3(join3(dir, "scripts", "zeta-build-worker.mts"))) return dir;
1082
1506
  const up = dirname2(dir);
1083
1507
  if (up === dir) break;
1084
1508
  dir = up;
@@ -1099,9 +1523,9 @@ async function localBuild(body, onPhase, repoRoot) {
1099
1523
  error: "local build needs the ZETA monorepo (scripts/zeta-build-worker.mts not found). Run from inside the repo, or use the http engine (--zeta-url)."
1100
1524
  };
1101
1525
  }
1102
- const worker = join2(root, "scripts", "zeta-build-worker.mts");
1526
+ const worker = join3(root, "scripts", "zeta-build-worker.mts");
1103
1527
  const buildId = `zeta-build-${Date.now().toString(36)}-${Math.floor(performance.now())}`;
1104
- const projectDir = join2(tmpdir(), buildId);
1528
+ const projectDir = join3(tmpdir(), buildId);
1105
1529
  const ideaB64 = Buffer.from(body.idea, "utf8").toString("base64");
1106
1530
  const args = [
1107
1531
  "--import",
@@ -1126,8 +1550,17 @@ async function localBuild(body, onPhase, repoRoot) {
1126
1550
  ...process.env,
1127
1551
  ...body.styleId ? { ZETA_COMPOSE_STYLE: body.styleId } : {},
1128
1552
  ...body.colorScheme ? { ZETA_BUILD_COLOR_SCHEME: body.colorScheme } : {},
1553
+ // App-sizing override → the worker parses it (parseScopeOverride) into opts.appScope.
1554
+ ...body.appScope ? { ZETA_BUILD_SCOPE: JSON.stringify(body.appScope) } : {},
1129
1555
  // SNIPER / lean → worker skips the Launch Kit + Brand Studio campaign.
1130
- ...body.lean ? { ZETA_LEAN: "1" } : {}
1556
+ ...body.lean ? { ZETA_LEAN: "1" } : {},
1557
+ // UNHINGED → worker skips ALL proofs/gates (fastest bootable, UNVERIFIED ship).
1558
+ // BYOK keys ride the inherited ...process.env above (set by the /build command).
1559
+ ...body.unhinged ? { ZETA_UNHINGED: "1" } : {},
1560
+ // Studio parity pins — same worker envs the web route forwards.
1561
+ ...body.platform === "mobile" ? { ZETA_BUILD_PLATFORM: "mobile" } : {},
1562
+ ...body.archetype ? { ZETA_BUILD_ARCHETYPE: body.archetype } : {},
1563
+ ...body.styleVibe ? { ZETA_STYLE_VIBE: body.styleVibe } : {}
1131
1564
  },
1132
1565
  stdio: ["ignore", "pipe", "pipe"]
1133
1566
  });
@@ -1172,11 +1605,11 @@ var PROPERTY_KINDS = [
1172
1605
  function resolveProver(repoRoot) {
1173
1606
  const root = repoRoot ?? findRepoRoot();
1174
1607
  if (!root) return null;
1175
- const base = join3(root, "packages", "shipgate-contract-prover");
1176
- const dist = join3(base, "dist", "cli.js");
1177
- if (existsSync3(dist)) return { nodeArgs: [dist] };
1178
- const src = join3(base, "src", "cli.ts");
1179
- if (existsSync3(src)) return { nodeArgs: ["--import", "tsx", src] };
1608
+ const base = join4(root, "packages", "shipgate-contract-prover");
1609
+ const dist = join4(base, "dist", "cli.js");
1610
+ if (existsSync4(dist)) return { nodeArgs: [dist] };
1611
+ const src = join4(base, "src", "cli.ts");
1612
+ if (existsSync4(src)) return { nodeArgs: ["--import", "tsx", src] };
1180
1613
  return null;
1181
1614
  }
1182
1615
  function runProver(proverArgs, opts) {
@@ -1221,8 +1654,8 @@ ${e.message}` });
1221
1654
  // src/app-runner.ts
1222
1655
  import { spawn as spawn3, spawnSync } from "child_process";
1223
1656
  import { readFile } from "fs/promises";
1224
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1225
- import { join as join4, dirname as dirname3 } from "path";
1657
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
1658
+ import { join as join5, dirname as dirname3 } from "path";
1226
1659
  var running = [];
1227
1660
  function killRunningApps() {
1228
1661
  for (const a of running.splice(0)) {
@@ -1238,8 +1671,8 @@ function listRunningApps() {
1238
1671
  var URL_RE = /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?[^\s)]*)/i;
1239
1672
  var FATAL_RE = /(Error:|error TS\d+|Cannot find module|EADDRINUSE|Failed to compile|SyntaxError|Module not found|exited with)/i;
1240
1673
  async function detectRun(dir) {
1241
- const pkgPath = join4(dir, "package.json");
1242
- if (!existsSync4(pkgPath)) {
1674
+ const pkgPath = join5(dir, "package.json");
1675
+ if (!existsSync5(pkgPath)) {
1243
1676
  return { error: `no package.json in ${dir} \u2014 not a runnable app directory` };
1244
1677
  }
1245
1678
  let scripts = {};
@@ -1249,7 +1682,7 @@ async function detectRun(dir) {
1249
1682
  } catch (e) {
1250
1683
  return { error: `unreadable package.json: ${e.message}` };
1251
1684
  }
1252
- const pm = existsSync4(join4(dir, "pnpm-lock.yaml")) ? "pnpm" : existsSync4(join4(dir, "yarn.lock")) ? "yarn" : "npm";
1685
+ const pm = existsSync5(join5(dir, "pnpm-lock.yaml")) ? "pnpm" : existsSync5(join5(dir, "yarn.lock")) ? "yarn" : "npm";
1253
1686
  for (const s of ["dev", "start", "serve"]) {
1254
1687
  if (scripts[s]) return { command: pm, args: ["run", s], reason: `${pm} run ${s}` };
1255
1688
  }
@@ -1266,27 +1699,27 @@ function pnpmAvailable() {
1266
1699
  return _pnpmOnPath;
1267
1700
  }
1268
1701
  function installPmFor(dir) {
1269
- if (existsSync4(join4(dir, "yarn.lock"))) return "yarn";
1270
- if (existsSync4(join4(dir, "pnpm-lock.yaml"))) return "pnpm";
1702
+ if (existsSync5(join5(dir, "yarn.lock"))) return "yarn";
1703
+ if (existsSync5(join5(dir, "pnpm-lock.yaml"))) return "pnpm";
1271
1704
  return pnpmAvailable() ? "pnpm" : "npm";
1272
1705
  }
1273
1706
  function ensureNpmrc(dir, lines) {
1274
1707
  try {
1275
- const npmrc = join4(dir, ".npmrc");
1276
- const prev = existsSync4(npmrc) ? readFileSync2(npmrc, "utf8") : "";
1708
+ const npmrc = join5(dir, ".npmrc");
1709
+ const prev = existsSync5(npmrc) ? readFileSync3(npmrc, "utf8") : "";
1277
1710
  const missing = lines.filter((l) => !prev.includes(l.split("=")[0]));
1278
1711
  if (missing.length === 0) return;
1279
1712
  const sep5 = prev && !prev.endsWith("\n") ? "\n" : "";
1280
- writeFileSync3(npmrc, prev + sep5 + missing.join("\n") + "\n");
1713
+ writeFileSync4(npmrc, prev + sep5 + missing.join("\n") + "\n");
1281
1714
  } catch {
1282
1715
  }
1283
1716
  }
1284
1717
  function isolateFromWorkspace(dir) {
1285
- if (existsSync4(join4(dir, "pnpm-workspace.yaml"))) return;
1718
+ if (existsSync5(join5(dir, "pnpm-workspace.yaml"))) return;
1286
1719
  let cur = dirname3(dir);
1287
1720
  let nested = false;
1288
1721
  for (let i = 0; i < 12; i++) {
1289
- if (existsSync4(join4(cur, "pnpm-workspace.yaml"))) {
1722
+ if (existsSync5(join5(cur, "pnpm-workspace.yaml"))) {
1290
1723
  nested = true;
1291
1724
  break;
1292
1725
  }
@@ -1296,14 +1729,14 @@ function isolateFromWorkspace(dir) {
1296
1729
  }
1297
1730
  if (!nested) return;
1298
1731
  try {
1299
- writeFileSync3(join4(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
1732
+ writeFileSync4(join5(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
1300
1733
  ensureNpmrc(dir, ["ignore-workspace-root-check=true"]);
1301
1734
  } catch {
1302
1735
  }
1303
1736
  }
1304
1737
  function installDeps(dir, onLog, signal) {
1305
1738
  const pm = installPmFor(dir);
1306
- if (pm === "pnpm" && !existsSync4(join4(dir, "pnpm-lock.yaml"))) {
1739
+ if (pm === "pnpm" && !existsSync5(join5(dir, "pnpm-lock.yaml"))) {
1307
1740
  ensureNpmrc(dir, ["shamefully-hoist=true", "prefer-offline=true"]);
1308
1741
  }
1309
1742
  const args = pm === "pnpm" ? (
@@ -1343,7 +1776,7 @@ ${e.message}` });
1343
1776
  });
1344
1777
  }
1345
1778
  async function runApp(dir, opts = {}) {
1346
- if (!existsSync4(join4(dir, "node_modules"))) {
1779
+ if (!existsSync5(join5(dir, "node_modules"))) {
1347
1780
  isolateFromWorkspace(dir);
1348
1781
  const inst = await installDeps(dir, void 0, opts.signal);
1349
1782
  if (!inst.ok) {
@@ -1357,7 +1790,7 @@ async function runApp(dir, opts = {}) {
1357
1790
  let command;
1358
1791
  let cmdArgs;
1359
1792
  if (opts.script) {
1360
- const pm = existsSync4(join4(dir, "pnpm-lock.yaml")) ? "pnpm" : "npm";
1793
+ const pm = existsSync5(join5(dir, "pnpm-lock.yaml")) ? "pnpm" : "npm";
1361
1794
  command = pm;
1362
1795
  cmdArgs = ["run", opts.script];
1363
1796
  } else {
@@ -1461,7 +1894,7 @@ async function fetchGithubRepos(webBase, installationId) {
1461
1894
  }
1462
1895
 
1463
1896
  // src/repo-audit.ts
1464
- import { readFileSync as readFileSync3 } from "fs";
1897
+ import { readFileSync as readFileSync4 } from "fs";
1465
1898
  import { createInterface } from "readline/promises";
1466
1899
  import { stdin as input, stdout as output } from "process";
1467
1900
 
@@ -1627,7 +2060,7 @@ async function pickGithubRepoInteractive(webBase) {
1627
2060
  }
1628
2061
  function readSourceFile(path) {
1629
2062
  try {
1630
- const source = readFileSync3(path, "utf8");
2063
+ const source = readFileSync4(path, "utf8");
1631
2064
  if (source.trim().length < 20) return { ok: false, error: "source file is too short" };
1632
2065
  return { ok: true, source };
1633
2066
  } catch (e) {
@@ -1642,11 +2075,11 @@ var TaskManager = class {
1642
2075
  seq = 0;
1643
2076
  map = /* @__PURE__ */ new Map();
1644
2077
  /** Start a detached-from-the-REPL background command. Returns the task id. */
1645
- start(command, args, cwd) {
2078
+ start(command, args, cwd2) {
1646
2079
  const id = `t${++this.seq}`;
1647
2080
  const display = [command, ...args].join(" ");
1648
2081
  const child = spawn4(command, args, {
1649
- cwd,
2082
+ cwd: cwd2,
1650
2083
  stdio: ["ignore", "pipe", "pipe"],
1651
2084
  // Own process group so we can signal the whole tree on kill.
1652
2085
  detached: false
@@ -1741,7 +2174,7 @@ import { tool as tool6 } from "ai";
1741
2174
  import { z as z6 } from "zod";
1742
2175
  import { spawn as spawn5 } from "child_process";
1743
2176
  import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
1744
- import { resolve as resolve3, dirname as dirname4, relative, join as join5, sep as sep3 } from "path";
2177
+ import { resolve as resolve3, dirname as dirname4, relative, join as join6, sep as sep3 } from "path";
1745
2178
  import fg2 from "fast-glob";
1746
2179
 
1747
2180
  // src/crypto-tools.ts
@@ -2429,8 +2862,11 @@ function buildTools(ctx) {
2429
2862
  const { permissions } = ctx;
2430
2863
  async function runGenerate(opts) {
2431
2864
  const { idea, scope, buildModel, install, keep, themeId, styleId, colorScheme } = opts;
2865
+ const { platform, archetype, styleVibe } = opts;
2432
2866
  const lean = opts.lean ?? true;
2433
- const preferBoot = opts.preferBoot ?? true;
2867
+ const unhinged = opts.unhinged ?? false;
2868
+ const byokActive = Boolean(process.env.ZETA_BYOK_KEY?.trim());
2869
+ const preferBoot = (opts.preferBoot ?? true) && !unhinged && !byokActive;
2434
2870
  if (permissions.guardMutation() === "deny") {
2435
2871
  return { ok: false, error: permissions.planRefusal() };
2436
2872
  }
@@ -2456,7 +2892,13 @@ function buildTools(ctx) {
2456
2892
  ...styleId ? { styleId } : {},
2457
2893
  ...colorScheme ? { colorScheme } : {},
2458
2894
  // SNIPER: app surfaces only, no marketing campaign. On by default for the CLI.
2459
- ...lean ? { lean: true } : {}
2895
+ ...lean ? { lean: true } : {},
2896
+ // UNHINGED: skip ALL proofs/gates → fastest bootable app, shipped UNVERIFIED.
2897
+ ...unhinged ? { unhinged: true } : {},
2898
+ // Studio parity pins — mobile shell, interior archetype, look vibe.
2899
+ ...platform === "mobile" ? { platform } : {},
2900
+ ...archetype ? { archetype } : {},
2901
+ ...styleVibe ? { styleVibe } : {}
2460
2902
  };
2461
2903
  const onPhase = (msg) => line(c.dim(` \u21B3 ${scrubVendor(msg)}`));
2462
2904
  try {
@@ -2517,8 +2959,20 @@ function buildTools(ctx) {
2517
2959
  line(c.dim(` still proving \u2014 check later: zeta proof ${bid}`));
2518
2960
  }
2519
2961
  }
2962
+ if (result.ok && result.unverified) {
2963
+ line(
2964
+ c.yellow(
2965
+ " \u26A0 UNHINGED build \u2014 ALL ShipGate proofs/gates were skipped. It boots, but auth / tenant isolation / data rules are UNVERIFIED. Verify before any real use."
2966
+ )
2967
+ );
2968
+ }
2969
+ if (viaHosted && result.ok && result.buildId) {
2970
+ const passportUrl = `${ctx.zetaApiUrl.replace(/\/$/, "")}/proof/build/${encodeURIComponent(String(result.buildId))}`;
2971
+ line(c.dim(` \u21B3 build passport: ${passportUrl}`));
2972
+ result = { ...result, passportUrl };
2973
+ }
2520
2974
  if (keep !== false && viaHosted && result.ok && result.buildId && !result.paywalled) {
2521
- const dest = opts.destDir ? join5(ctx.cwd, opts.destDir) : join5(ctx.cwd, `zeta-${String(result.buildId).slice(-8)}`);
2975
+ const dest = opts.destDir ? join6(ctx.cwd, opts.destDir) : join6(ctx.cwd, `zeta-${String(result.buildId).slice(-8)}`);
2522
2976
  line(c.dim(" \u21B3 saving your code\u2026"));
2523
2977
  const delivered = await deliverBuild(
2524
2978
  ctx.zetaApiUrl,
@@ -2604,9 +3058,65 @@ function buildTools(ctx) {
2604
3058
  ),
2605
3059
  lean: z6.boolean().default(true).describe(
2606
3060
  "SNIPER mode (default true). Build ONLY the product surfaces \u2014 landing \xB7 login \xB7 app \xB7 dashboard \u2014 and SKIP the build-time go-to-market campaign (Launch Kit: pricing/social/email/pitch/checklist, and Brand Studio: DNA + visual campaigns). Faster and focused on the app. Set false ONLY when the user explicitly asks for the full marketing/launch kit."
3061
+ ),
3062
+ unhinged: z6.boolean().default(false).describe(
3063
+ "UNHINGED mode \u2014 build as fast as possible: SKIP ALL ShipGate proofs and gates (RLS/RBAC/authz/client-contract/truth-gate + the ~30 runtime proofs). Keeps the compile check so the app still boots, but ships UNVERIFIED \u2014 no security proofs run. Set true ONLY when the user explicitly asks for a fast / raw / unhinged / no-checks build. Default false (the normal proven-before-ship path)."
3064
+ ),
3065
+ platform: z6.enum(["web", "mobile"]).optional().describe(
3066
+ '"mobile" adds a Capacitor store-shippable iOS/Android shell after the web app is emitted. Use ONLY when the user asks for a mobile/phone app. OMIT for normal web apps.'
3067
+ ),
3068
+ archetype: z6.enum([
3069
+ "analytics",
3070
+ "document-editor",
3071
+ "collaboration",
3072
+ "kanban",
3073
+ "calendar",
3074
+ "marketplace",
3075
+ "studio"
3076
+ ]).optional().describe(
3077
+ "Pin the app's interior layout archetype. 'studio' = the two-plane generator/maker product class. OMIT to let the engine detect it from the idea (the default)."
3078
+ ),
3079
+ styleVibe: z6.enum([
3080
+ "cinematic-dark",
3081
+ "editorial-luxe",
3082
+ "warm-minimal",
3083
+ "bold-brutalist",
3084
+ "glass-futuristic",
3085
+ "vibrant-playful",
3086
+ "corporate-clean"
3087
+ ]).optional().describe(
3088
+ "Restrict the look to one vibe bucket when the user names a mood but not an exact style. A styleId pin still wins. OMIT for full-auto."
2607
3089
  )
2608
3090
  }),
2609
- execute: async ({ idea, scope, buildModel, install, keep, themeId, styleId, colorScheme, lean }) => runGenerate({ idea, scope, buildModel, install, keep, themeId, styleId, colorScheme, lean })
3091
+ execute: async ({
3092
+ idea,
3093
+ scope,
3094
+ buildModel,
3095
+ install,
3096
+ keep,
3097
+ themeId,
3098
+ styleId,
3099
+ colorScheme,
3100
+ lean,
3101
+ unhinged,
3102
+ platform,
3103
+ archetype,
3104
+ styleVibe
3105
+ }) => runGenerate({
3106
+ idea,
3107
+ scope,
3108
+ buildModel,
3109
+ install,
3110
+ keep,
3111
+ themeId,
3112
+ styleId,
3113
+ colorScheme,
3114
+ lean,
3115
+ unhinged,
3116
+ platform,
3117
+ archetype,
3118
+ styleVibe
3119
+ })
2610
3120
  }),
2611
3121
  deploy_app: tool6({
2612
3122
  description: "Deploy a BUILT app to a live preview URL (the studio's one-click Deploy \u2192 Vercel). Use AFTER generate_app has produced a build. Charges one build credit, paid once per build (re-deploying the same build is free); provisions a database + applies the schema when the app needs one. Polls until the deployment is live and returns its URL. This is an ephemeral PREVIEW \u2014 use promote_app to ship a durable production deployment.",
@@ -2694,6 +3204,79 @@ function buildTools(ctx) {
2694
3204
  };
2695
3205
  }
2696
3206
  }),
3207
+ launch_volition: tool6({
3208
+ description: "Hand a business to OPERATION VOLITION \u2014 the autonomous operate-plane crew (strategy, social, outreach, support, ads, finance agents) that RUNS the business 24/7 on the platform. Use when the user wants the business operated/marketed for them, not just built. Pass a project id or name for an existing business, or a new business name to create one; omit target to use the project linked in this directory. Starts in SANDBOX (external actions simulated, every action proof-gated + certificate-hashed); live:true goes real. The $99/mo add-on is enforced server-side \u2014 a 402 returns the checkout path, never fake an activation.",
3209
+ inputSchema: z6.object({
3210
+ target: z6.string().max(200).optional().describe(
3211
+ "Project id, existing project name, or a NEW business name to create. Omit to use the linked project."
3212
+ ),
3213
+ live: z6.boolean().optional().describe(
3214
+ "true = real external actions (posts, emails). Default false = sandbox simulation. Only set true when the user explicitly asks to go live."
3215
+ ),
3216
+ description: z6.string().max(500).optional().describe("One-line description of the business (used when creating a new project).")
3217
+ }),
3218
+ execute: async ({ target, live, description }) => {
3219
+ if (permissions.guardMutation() === "deny")
3220
+ return { ok: false, error: permissions.planRefusal() };
3221
+ if (!process.env.ZETA_API_KEY?.trim()) {
3222
+ return {
3223
+ ok: false,
3224
+ error: "Login required \u2014 run `wholestack login` (Volition runs on your account)."
3225
+ };
3226
+ }
3227
+ const result = await launchVolition({
3228
+ target,
3229
+ live,
3230
+ description,
3231
+ cwd: ctx.cwd,
3232
+ onStep: (s) => toolLine("launch_volition", c.dim(s))
3233
+ });
3234
+ if (!result.ok) {
3235
+ return {
3236
+ ok: false,
3237
+ error: result.error,
3238
+ status: result.status,
3239
+ // 402 carries the checkout path; 503 carries readiness — surface them
3240
+ // so the agent can tell the user exactly what to do next.
3241
+ ...result.activation
3242
+ };
3243
+ }
3244
+ return {
3245
+ ok: true,
3246
+ projectId: result.projectId,
3247
+ consoleUrl: result.consoleUrl,
3248
+ ...result.activation
3249
+ };
3250
+ }
3251
+ }),
3252
+ volition_status: tool6({
3253
+ description: "Read the Volition crew's state for a project: operator on/off, sandbox vs live, action counts by status, recent shift summaries. Use to answer 'what has the crew done?' or before launching (to avoid double-activation). Pass a project id or name; omit to use the linked project.",
3254
+ inputSchema: z6.object({
3255
+ target: z6.string().max(200).optional().describe("Project id or name. Omit to use the project linked in this directory.")
3256
+ }),
3257
+ execute: async ({ target }) => {
3258
+ if (!process.env.ZETA_API_KEY?.trim()) {
3259
+ return { ok: false, error: "Login required \u2014 run `wholestack login` first." };
3260
+ }
3261
+ let id = target?.trim();
3262
+ if (id) {
3263
+ const found = await resolveVolitionProject(id);
3264
+ if (!found.ok) return { ok: false, error: found.error };
3265
+ id = found.project.id;
3266
+ } else {
3267
+ id = readProjectLink(ctx.cwd)?.projectId;
3268
+ }
3269
+ if (!id) {
3270
+ return {
3271
+ ok: false,
3272
+ error: "No project \u2014 pass a target or run inside a linked project directory."
3273
+ };
3274
+ }
3275
+ const r = await volitionStatus(id);
3276
+ if (!r.ok) return { ok: false, error: r.error, status: r.status };
3277
+ return { ok: true, projectId: id, ...r.data };
3278
+ }
3279
+ }),
2697
3280
  promote_app: tool6({
2698
3281
  description: "Promote a BUILT app to a DURABLE PRODUCTION deployment \u2014 a real (non-ephemeral) database and production env with dev-auth OFF, optionally bound to a custom domain. This is the 'make it a real product' step (stronger than deploy_app's ephemeral preview). Requires the platform to have a database provider configured. Charges/credits follow the same rules as deploy.",
2699
3282
  inputSchema: z6.object({
@@ -3124,7 +3707,7 @@ function buildTools(ctx) {
3124
3707
  const themeId = themes[i % themes.length];
3125
3708
  line(c.bold(`
3126
3709
  [${i + 1}/${picks.length}] ${p.name} \xB7 ${themeId}`));
3127
- const destDir = join5("zeta-batch", `${String(i + 1).padStart(2, "0")}-${slugify(p.name)}`);
3710
+ const destDir = join6("zeta-batch", `${String(i + 1).padStart(2, "0")}-${slugify(p.name)}`);
3128
3711
  let r;
3129
3712
  try {
3130
3713
  r = await runGenerate({
@@ -3404,7 +3987,7 @@ function buildTools(ctx) {
3404
3987
  const names = await readdir(abs);
3405
3988
  const entries = await Promise.all(
3406
3989
  names.map(async (n) => {
3407
- const s = await stat(join5(abs, n)).catch(() => null);
3990
+ const s = await stat(join6(abs, n)).catch(() => null);
3408
3991
  return { name: n, dir: s?.isDirectory() ?? false };
3409
3992
  })
3410
3993
  );
@@ -3595,102 +4178,26 @@ function buildTools(ctx) {
3595
4178
  };
3596
4179
  }
3597
4180
 
3598
- // src/config.ts
3599
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5, chmodSync, renameSync } from "fs";
3600
- import { homedir } from "os";
3601
- import { join as join6 } from "path";
3602
- var LEGACY_DIR = join6(homedir(), ".zeta-g");
3603
- var STATE_DIR = join6(homedir(), ".wholestack");
3604
- try {
3605
- if (existsSync5(LEGACY_DIR) && !existsSync5(STATE_DIR)) renameSync(LEGACY_DIR, STATE_DIR);
3606
- } catch {
3607
- }
3608
- function stateDir() {
3609
- return STATE_DIR;
3610
- }
3611
- function webBaseUrl() {
3612
- return process.env.ZETA_WEB_URL?.trim() || process.env.ZETA_API_URL?.trim() || "https://wholestack.ai";
3613
- }
3614
- function projectDirs(cwd) {
3615
- return [join6(cwd, ".wholestack"), join6(cwd, ".zeta-g")];
3616
- }
3617
- var CONFIG_DIR = STATE_DIR;
3618
- var CONFIG_PATH = join6(CONFIG_DIR, "config.json");
3619
- function parseDotenv(text) {
3620
- const out = {};
3621
- for (const raw of text.split("\n")) {
3622
- const l = raw.trim();
3623
- if (!l || l.startsWith("#")) continue;
3624
- const eq = l.indexOf("=");
3625
- if (eq < 0) continue;
3626
- const k = l.slice(0, eq).trim();
3627
- let v = l.slice(eq + 1).trim();
3628
- if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
3629
- v = v.slice(1, -1);
3630
- }
3631
- if (k) out[k] = v;
3632
- }
3633
- return out;
3634
- }
3635
- function fill(src) {
3636
- for (const [k, v] of Object.entries(src)) {
3637
- if (v && process.env[k] === void 0) process.env[k] = v;
3638
- }
3639
- }
3640
- function loadStoredEnv() {
3641
- const localEnv = join6(process.cwd(), ".env");
3642
- if (existsSync5(localEnv)) {
3643
- try {
3644
- fill(parseDotenv(readFileSync4(localEnv, "utf8")));
3645
- } catch {
3646
- }
3647
- }
3648
- if (existsSync5(CONFIG_PATH)) {
3649
- try {
3650
- const json = JSON.parse(readFileSync4(CONFIG_PATH, "utf8"));
3651
- const flat = {};
3652
- for (const [k, v] of Object.entries(json)) if (typeof v === "string") flat[k] = v;
3653
- fill(flat);
3654
- } catch {
3655
- }
3656
- }
3657
- }
3658
- function saveKey(name, value) {
3659
- mkdirSync3(CONFIG_DIR, { recursive: true });
3660
- let current = {};
3661
- if (existsSync5(CONFIG_PATH)) {
3662
- try {
3663
- current = JSON.parse(readFileSync4(CONFIG_PATH, "utf8"));
3664
- } catch {
3665
- current = {};
3666
- }
3667
- }
3668
- current[name] = value;
3669
- writeFileSync4(CONFIG_PATH, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
3670
- chmodSync(CONFIG_PATH, 384);
3671
- return CONFIG_PATH;
3672
- }
3673
-
3674
4181
  // src/hooks.ts
3675
4182
  import { spawn as spawn6 } from "child_process";
3676
4183
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3677
4184
  import { join as join7 } from "path";
3678
4185
  var HOOK_TIMEOUT_MS = 15e3;
3679
- function runOne(def, event, payload, cwd) {
4186
+ function runOne(def, event, payload, cwd2) {
3680
4187
  return new Promise((res) => {
3681
- const child = spawn6("sh", ["-c", def.command], { cwd });
3682
- let stdout2 = "";
4188
+ const child = spawn6("sh", ["-c", def.command], { cwd: cwd2 });
4189
+ let stdout3 = "";
3683
4190
  let stderr = "";
3684
4191
  const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_TIMEOUT_MS);
3685
- child.stdout.on("data", (b) => stdout2 += b.toString());
4192
+ child.stdout.on("data", (b) => stdout3 += b.toString());
3686
4193
  child.stderr.on("data", (b) => stderr += b.toString());
3687
4194
  child.on("error", (e) => {
3688
4195
  clearTimeout(timer);
3689
- res({ code: 1, stdout: stdout2, stderr: stderr + e.message });
4196
+ res({ code: 1, stdout: stdout3, stderr: stderr + e.message });
3690
4197
  });
3691
4198
  child.on("close", (code) => {
3692
4199
  clearTimeout(timer);
3693
- res({ code: code ?? 0, stdout: stdout2, stderr });
4200
+ res({ code: code ?? 0, stdout: stdout3, stderr });
3694
4201
  });
3695
4202
  try {
3696
4203
  child.stdin.write(JSON.stringify({ event, ...payload }));
@@ -3700,9 +4207,9 @@ function runOne(def, event, payload, cwd) {
3700
4207
  });
3701
4208
  }
3702
4209
  var HookRunner = class {
3703
- constructor(hooks, cwd) {
4210
+ constructor(hooks, cwd2) {
3704
4211
  this.hooks = hooks;
3705
- this.cwd = cwd;
4212
+ this.cwd = cwd2;
3706
4213
  }
3707
4214
  hooks;
3708
4215
  cwd;
@@ -3719,22 +4226,22 @@ var HookRunner = class {
3719
4226
  for (const def of defs) {
3720
4227
  if (def.matcher && payload.toolName && !new RegExp(def.matcher).test(payload.toolName))
3721
4228
  continue;
3722
- const { code, stdout: stdout2, stderr } = await runOne(def, event, payload, this.cwd);
3723
- const parsed = tryJson(stdout2);
4229
+ const { code, stdout: stdout3, stderr } = await runOne(def, event, payload, this.cwd);
4230
+ const parsed = tryJson(stdout3);
3724
4231
  if (parsed && (parsed.decision === "block" || parsed.block)) {
3725
4232
  outcome.block = String(parsed.reason ?? parsed.message ?? "blocked by hook");
3726
4233
  return outcome;
3727
4234
  }
3728
4235
  if (canBlock && code !== 0) {
3729
4236
  const reason = parsed ? String(parsed.reason ?? parsed.message ?? "") : "";
3730
- outcome.block = (stderr || reason || stdout2 || `hook exited ${code}`).trim();
4237
+ outcome.block = (stderr || reason || stdout3 || `hook exited ${code}`).trim();
3731
4238
  return outcome;
3732
4239
  }
3733
4240
  if (parsed) {
3734
4241
  const extra = parsed.additionalContext ?? parsed.hookSpecificOutput?.additionalContext;
3735
4242
  if (extra) outcome.context.push(String(extra));
3736
4243
  } else {
3737
- const text = stdout2.trim();
4244
+ const text = stdout3.trim();
3738
4245
  if (text) outcome.context.push(text);
3739
4246
  }
3740
4247
  }
@@ -3760,10 +4267,10 @@ function mergeHookSets(...sets) {
3760
4267
  }
3761
4268
  return out;
3762
4269
  }
3763
- function loadHookFiles(cwd) {
4270
+ function loadHookFiles(cwd2) {
3764
4271
  const files = [
3765
4272
  join7(stateDir(), "hooks.json"),
3766
- ...projectDirs(cwd).map((d) => join7(d, "hooks.json"))
4273
+ ...projectDirs(cwd2).map((d) => join7(d, "hooks.json"))
3767
4274
  ];
3768
4275
  const sets = [];
3769
4276
  for (const f of files) {
@@ -3925,7 +4432,9 @@ var MODEL_IDS = {
3925
4432
  /** BRAIN lane — frontend swarm, decisions, debugging, editing, planning. Cerebras. The 20%. */
3926
4433
  glm: readEnv("ZETA_GLM_MODEL") || "zai-glm-4.7",
3927
4434
  /** VISION lane — accepts image input. OpenRouter (Cerebras text lanes can't see). */
3928
- vision: readEnv("ZETA_VISION_MODEL") || "anthropic/claude-sonnet-4.6"
4435
+ vision: readEnv("ZETA_VISION_MODEL") || "anthropic/claude-sonnet-4.6",
4436
+ /** SONNET lane — Claude Sonnet 5 via OpenRouter. Premium reasoning + design brain (CLI + studio design pass). */
4437
+ sonnet: readEnv("ZETA_SONNET_MODEL") || "anthropic/claude-sonnet-5"
3929
4438
  };
3930
4439
  var ZETA_AGENT_PIPELINE = Object.freeze({
3931
4440
  /** THINK lane — ISL spec authoring BASE. gpt-oss-120b ($0.35/$0.75 per 1M) so a build
@@ -3950,6 +4459,7 @@ var CEREBRAS_KEY = PROVIDERS.cerebras.keyEnv;
3950
4459
  var KEY_ENV = PROVIDERS.openrouter.keyEnv;
3951
4460
  var DEFAULT_CUSTOM_MODEL = MODEL_IDS.vision;
3952
4461
  var VISION_MODEL = MODEL_IDS.vision;
4462
+ var SONNET_MODEL = MODEL_IDS.sonnet;
3953
4463
  var MODELS = /* @__PURE__ */ new Map([
3954
4464
  [
3955
4465
  "zeta-g1-lite",
@@ -3996,6 +4506,19 @@ var MODELS = /* @__PURE__ */ new Map([
3996
4506
  contextWindow: 2e5,
3997
4507
  thinking: null
3998
4508
  }
4509
+ ],
4510
+ // The Sonnet tier — Claude Sonnet 5 via OpenRouter: the premium reasoning + design
4511
+ // brain, and vision-capable. BYOK OpenRouter like the vision tier (set OPENROUTER_API_KEY).
4512
+ [
4513
+ "zeta-g1-sonnet",
4514
+ {
4515
+ modelId: SONNET_MODEL,
4516
+ label: "Zeta-G1.0 Sonnet",
4517
+ keyEnv: KEY_ENV,
4518
+ baseURL: OPENROUTER_URL,
4519
+ contextWindow: 2e5,
4520
+ thinking: null
4521
+ }
3999
4522
  ]
4000
4523
  ]);
4001
4524
  var MODEL_KEYS = [...MODELS.keys()];
@@ -4037,9 +4560,9 @@ function resolveModelKey(raw) {
4037
4560
  image: "zeta-g1-vision",
4038
4561
  // legacy convenience — resolve silently, but only the Zeta label is shown
4039
4562
  opus: "zeta-g1-max",
4040
- sonnet: "zeta-g1",
4563
+ sonnet: "zeta-g1-sonnet",
4041
4564
  haiku: "zeta-g1-lite",
4042
- claude: "zeta-g1"
4565
+ claude: "zeta-g1-sonnet"
4043
4566
  };
4044
4567
  if (ALIASES[lower]) return ALIASES[lower];
4045
4568
  throw new Error(`Unknown model "${raw}". Try: ${MODEL_KEYS.join(", ")}.`);
@@ -4057,7 +4580,7 @@ function modelId(key) {
4057
4580
  return spec(key).modelId;
4058
4581
  }
4059
4582
  function visionCapable(key) {
4060
- return key === "zeta-g1-vision" || key.startsWith("custom:");
4583
+ return key === "zeta-g1-vision" || key === "zeta-g1-sonnet" || key.startsWith("custom:");
4061
4584
  }
4062
4585
  function modelContextWindow(key) {
4063
4586
  return spec(key).contextWindow;
@@ -5094,11 +5617,11 @@ function readScripts(dir) {
5094
5617
  return {};
5095
5618
  }
5096
5619
  }
5097
- function runScript(pm, script, cwd, timeoutMs) {
5620
+ function runScript(pm, script, cwd2, timeoutMs) {
5098
5621
  const shell = process.env.SHELL || "/bin/bash";
5099
5622
  const line2 = `${pm} run ${script}`;
5100
5623
  return new Promise((res) => {
5101
- const child = spawn7(shell, ["-lc", line2], { cwd, shell: false });
5624
+ const child = spawn7(shell, ["-lc", line2], { cwd: cwd2, shell: false });
5102
5625
  let out = "";
5103
5626
  const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
5104
5627
  const sink = (b) => {
@@ -6792,13 +7315,13 @@ async function readBounded(path, limit) {
6792
7315
  return null;
6793
7316
  }
6794
7317
  }
6795
- async function loadProjectMemory(cwd) {
7318
+ async function loadProjectMemory(cwd2) {
6796
7319
  const candidates = [];
6797
7320
  const globalFile = join9(stateDir(), "ZETA.md");
6798
7321
  if (existsSync9(globalFile)) candidates.push(globalFile);
6799
- const root = repoRootFrom(cwd);
7322
+ const root = repoRootFrom(cwd2);
6800
7323
  const chain = [];
6801
- let dir = cwd;
7324
+ let dir = cwd2;
6802
7325
  for (let i = 0; i < 24; i++) {
6803
7326
  chain.unshift(dir);
6804
7327
  if (dir === root) break;
@@ -6990,10 +7513,10 @@ var Session = class _Session {
6990
7513
  meta;
6991
7514
  path;
6992
7515
  /** Start a fresh session and write its meta header. */
6993
- static create(cwd, model) {
7516
+ static create(cwd2, model) {
6994
7517
  ensureDir();
6995
7518
  const id = `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
6996
- const meta = { id, cwd, model, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
7519
+ const meta = { id, cwd: cwd2, model, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
6997
7520
  const path = fileFor(id);
6998
7521
  appendFileSync(path, JSON.stringify({ kind: "meta", ...meta }) + "\n", "utf8");
6999
7522
  return new _Session(meta, path);
@@ -7020,12 +7543,12 @@ var Session = class _Session {
7020
7543
  return { session: new _Session(meta, path), messages };
7021
7544
  }
7022
7545
  /** The most recent session (optionally scoped to a working directory). */
7023
- static latestId(cwd) {
7024
- const all = _Session.list(1, cwd);
7546
+ static latestId(cwd2) {
7547
+ const all = _Session.list(1, cwd2);
7025
7548
  return all.length ? all[0].id : null;
7026
7549
  }
7027
7550
  /** List sessions newest-first, with a preview of the first user message. */
7028
- static list(limit = 30, cwd) {
7551
+ static list(limit = 30, cwd2) {
7029
7552
  if (!existsSync10(SESSIONS_DIR)) return [];
7030
7553
  const out = [];
7031
7554
  for (const name of readdirSync2(SESSIONS_DIR)) {
@@ -7051,7 +7574,7 @@ var Session = class _Session {
7051
7574
  continue;
7052
7575
  }
7053
7576
  if (!meta) continue;
7054
- if (cwd && meta.cwd !== cwd) continue;
7577
+ if (cwd2 && meta.cwd !== cwd2) continue;
7055
7578
  const updatedAt = statSync2(path).mtimeMs;
7056
7579
  out.push({ ...meta, preview: preview.slice(0, 70), turns, updatedAt });
7057
7580
  }
@@ -7082,9 +7605,9 @@ async function loadChromium() {
7082
7605
  return null;
7083
7606
  }
7084
7607
  }
7085
- function insideCwd(cwd, p) {
7086
- const abs = resolve5(cwd, p);
7087
- const rel = relative3(cwd, abs);
7608
+ function insideCwd(cwd2, p) {
7609
+ const abs = resolve5(cwd2, p);
7610
+ const rel = relative3(cwd2, abs);
7088
7611
  if (rel === ".." || rel.startsWith(".." + sep4)) {
7089
7612
  throw new Error(`path "${p}" escapes the workspace root`);
7090
7613
  }
@@ -7835,7 +8358,7 @@ function shippedSkillsDir() {
7835
8358
  for (const dir of candidates) if (existsSync12(dir)) return dir;
7836
8359
  return "";
7837
8360
  }
7838
- function loadAllSkills(cwd) {
8361
+ function loadAllSkills(cwd2) {
7839
8362
  const byName = /* @__PURE__ */ new Map();
7840
8363
  const add = (skills) => {
7841
8364
  for (const s of skills) byName.set(s.name, s);
@@ -7843,7 +8366,7 @@ function loadAllSkills(cwd) {
7843
8366
  const shipped = shippedSkillsDir();
7844
8367
  if (shipped) add(loadSkillsFromDir(shipped, "shipped"));
7845
8368
  add(loadSkillsFromDir(join13(stateDir(), "skills"), "user"));
7846
- for (const d of projectDirs(cwd)) add(loadSkillsFromDir(join13(d, "skills"), "project"));
8369
+ for (const d of projectDirs(cwd2)) add(loadSkillsFromDir(join13(d, "skills"), "project"));
7847
8370
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
7848
8371
  }
7849
8372
  var SkillRegistry = class _SkillRegistry {
@@ -7853,8 +8376,8 @@ var SkillRegistry = class _SkillRegistry {
7853
8376
  this.skills = skills;
7854
8377
  this.index = new Map(skills.map((s) => [s.name, s]));
7855
8378
  }
7856
- static load(cwd) {
7857
- return new _SkillRegistry(loadAllSkills(cwd));
8379
+ static load(cwd2) {
8380
+ return new _SkillRegistry(loadAllSkills(cwd2));
7858
8381
  }
7859
8382
  list() {
7860
8383
  return this.skills;
@@ -7940,23 +8463,23 @@ import { join as join14 } from "path";
7940
8463
 
7941
8464
  // src/git.ts
7942
8465
  import { execFileSync } from "child_process";
7943
- function git(args, cwd) {
8466
+ function git(args, cwd2) {
7944
8467
  return execFileSync("git", args, {
7945
- cwd,
8468
+ cwd: cwd2,
7946
8469
  encoding: "utf8",
7947
8470
  maxBuffer: 32 * 1024 * 1024,
7948
8471
  stdio: ["ignore", "pipe", "pipe"]
7949
8472
  }).trim();
7950
8473
  }
7951
- function gitSafe(args, cwd) {
8474
+ function gitSafe(args, cwd2) {
7952
8475
  try {
7953
- return git(args, cwd);
8476
+ return git(args, cwd2);
7954
8477
  } catch {
7955
8478
  return null;
7956
8479
  }
7957
8480
  }
7958
- function isRepo(cwd) {
7959
- return gitSafe(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
8481
+ function isRepo(cwd2) {
8482
+ return gitSafe(["rev-parse", "--is-inside-work-tree"], cwd2) === "true";
7960
8483
  }
7961
8484
  var SECRET_HINTS = [
7962
8485
  { re: /\bAKIA[0-9A-Z]{16}\b/, label: "AWS access key id" },
@@ -8119,7 +8642,7 @@ var eyesCommands = [
8119
8642
  ];
8120
8643
 
8121
8644
  // src/ops-commands.ts
8122
- function apiBase() {
8645
+ function apiBase2() {
8123
8646
  return (process.env.ZETA_WEB_URL?.trim() || process.env.ZETA_API_URL?.trim() || "https://wholestack.ai").replace(/\/$/, "");
8124
8647
  }
8125
8648
  async function probe2(url, timeoutMs) {
@@ -8191,7 +8714,7 @@ var opsCommands = [
8191
8714
  return { type: "handled" };
8192
8715
  }
8193
8716
  const r = await apiJson(
8194
- `${apiBase()}/api/zeta/build/${encodeURIComponent(buildId)}/deploy?deploymentId=${encodeURIComponent(deploymentId)}`
8717
+ `${apiBase2()}/api/zeta/build/${encodeURIComponent(buildId)}/deploy?deploymentId=${encodeURIComponent(deploymentId)}`
8195
8718
  );
8196
8719
  ctx.print();
8197
8720
  if (!r.ok) {
@@ -8210,7 +8733,7 @@ var opsCommands = [
8210
8733
  summary: "read live traffic + revenue (PostHog/Stripe)",
8211
8734
  source: "builtin",
8212
8735
  run: async (ctx) => {
8213
- const r = await readMetrics(apiBase());
8736
+ const r = await readMetrics(apiBase2());
8214
8737
  ctx.print();
8215
8738
  if (!r.ok || !r.metrics) {
8216
8739
  ctx.print(" " + c.red(`\u2717 ${r.error ?? "no metrics"}`));
@@ -8243,7 +8766,7 @@ var opsCommands = [
8243
8766
  summary: "read recent production errors (Sentry/PostHog)",
8244
8767
  source: "builtin",
8245
8768
  run: async (ctx) => {
8246
- const r = await readMetrics(apiBase());
8769
+ const r = await readMetrics(apiBase2());
8247
8770
  ctx.print();
8248
8771
  if (!r.ok || !r.metrics) {
8249
8772
  ctx.print(" " + c.red(`\u2717 ${r.error ?? "no metrics"}`));
@@ -8514,7 +9037,7 @@ var BUILTINS = [
8514
9037
  },
8515
9038
  {
8516
9039
  name: "build",
8517
- summary: "scaffold a full-stack app \u2014 sniper by default: landing \xB7 login \xB7 app \xB7 dashboard, no marketing campaign (/build <idea> [--style <id>] [--color dark|light] [--campaign] [--deploy])",
9040
+ summary: "scaffold a full-stack app \u2014 sniper by default: landing \xB7 login \xB7 app \xB7 dashboard, no marketing campaign (/build <idea> [--style <id>] [--color dark|light] [--campaign] [--deploy] [--unhinged] [--key <k> --provider <p>])",
8518
9041
  source: "builtin",
8519
9042
  run: (ctx) => {
8520
9043
  if (!ctx.args) {
@@ -8552,6 +9075,29 @@ var BUILTINS = [
8552
9075
  campaign = true;
8553
9076
  rest = rest.replace(/(^|\s)--(campaign|full-kit)(\s|$)/, " ");
8554
9077
  }
9078
+ let unhinged = false;
9079
+ if (/(^|\s)--unhinged(\s|$)/.test(rest)) {
9080
+ unhinged = true;
9081
+ rest = rest.replace(/(^|\s)--unhinged(\s|$)/, " ");
9082
+ }
9083
+ const provM = rest.match(/--provider[=\s]+(openrouter|cerebras|anthropic|openai|google)/i);
9084
+ if (provM) {
9085
+ process.env.ZETA_BYOK_PROVIDER = provM[1].toLowerCase();
9086
+ rest = rest.replace(provM[0], " ");
9087
+ }
9088
+ const keyM = rest.match(/--key[=\s]+(\S+)/i);
9089
+ if (keyM) {
9090
+ process.env.ZETA_BYOK_KEY = keyM[1];
9091
+ if (!process.env.ZETA_BYOK_PROVIDER) process.env.ZETA_BYOK_PROVIDER = "openrouter";
9092
+ rest = rest.replace(keyM[0], " ");
9093
+ const k = keyM[1];
9094
+ const masked = k.length > 8 ? `${k.slice(0, 4)}\u2026${k.slice(-2)}` : "\u2026";
9095
+ ctx.print(
9096
+ " " + c.dim(
9097
+ `BYOK key set for this session (provider: ${process.env.ZETA_BYOK_PROVIDER}, key: ${masked}). It is never logged or sent to the model.`
9098
+ )
9099
+ );
9100
+ }
8555
9101
  const idea = rest.trim();
8556
9102
  if (!idea) {
8557
9103
  ctx.print(
@@ -8563,6 +9109,8 @@ var BUILTINS = [
8563
9109
  'scope: "full"',
8564
9110
  // Sniper by default (lean:true) — skip the marketing campaign. --campaign opts in.
8565
9111
  `lean: ${campaign ? "false" : "true"}`,
9112
+ // UNHINGED (--unhinged): skip ALL proofs/gates → fastest bootable app, UNVERIFIED ship.
9113
+ unhinged ? "unhinged: true" : null,
8566
9114
  styleId ? `styleId: "${styleId}"` : null,
8567
9115
  colorScheme ? `colorScheme: "${colorScheme}"` : null
8568
9116
  ].filter(Boolean).join(", ");
@@ -9094,6 +9642,7 @@ var BUILTINS = [
9094
9642
  ...gitCommands,
9095
9643
  ...eyesCommands,
9096
9644
  ...opsCommands,
9645
+ ...volitionCommands,
9097
9646
  ...learnedCommands,
9098
9647
  ...autoRouteCommands,
9099
9648
  ...skillsCommands,
@@ -9196,8 +9745,8 @@ var CommandRegistry = class {
9196
9745
  return this.ordered.flatMap((c2) => [c2.name, ...c2.aliases ?? []]);
9197
9746
  }
9198
9747
  /** Load *.md commands from ~/.wholestack/commands and <cwd>/.wholestack/commands (or legacy .zeta-g/). */
9199
- loadCustom(cwd) {
9200
- const dirs = [join14(stateDir(), "commands"), ...projectDirs(cwd).map((d) => join14(d, "commands"))];
9748
+ loadCustom(cwd2) {
9749
+ const dirs = [join14(stateDir(), "commands"), ...projectDirs(cwd2).map((d) => join14(d, "commands"))];
9201
9750
  for (const dir of dirs) {
9202
9751
  for (const cmd of loadMdCommands(dir, "custom")) this.register(cmd);
9203
9752
  }
@@ -9219,8 +9768,8 @@ var CommandRegistry = class {
9219
9768
  // src/plugins.ts
9220
9769
  import { readFileSync as readFileSync12, existsSync as existsSync14, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
9221
9770
  import { join as join15 } from "path";
9222
- function pluginRoots(cwd) {
9223
- return [join15(stateDir(), "plugins"), ...projectDirs(cwd).map((d) => join15(d, "plugins"))];
9771
+ function pluginRoots(cwd2) {
9772
+ return [join15(stateDir(), "plugins"), ...projectDirs(cwd2).map((d) => join15(d, "plugins"))];
9224
9773
  }
9225
9774
  function resolveSystemPrompt(dir, value) {
9226
9775
  const asFile = join15(dir, value);
@@ -9233,7 +9782,7 @@ function resolveSystemPrompt(dir, value) {
9233
9782
  }
9234
9783
  return value.trim();
9235
9784
  }
9236
- function loadPlugins(cwd) {
9785
+ function loadPlugins(cwd2) {
9237
9786
  const result = {
9238
9787
  plugins: [],
9239
9788
  systemText: "",
@@ -9244,7 +9793,7 @@ function loadPlugins(cwd) {
9244
9793
  };
9245
9794
  const systemBlocks = [];
9246
9795
  const hookSets = [];
9247
- for (const root of pluginRoots(cwd)) {
9796
+ for (const root of pluginRoots(cwd2)) {
9248
9797
  if (!existsSync14(root)) continue;
9249
9798
  for (const entry of readdirSync6(root)) {
9250
9799
  const dir = join15(root, entry);
@@ -9312,11 +9861,11 @@ function readConfigFile(path) {
9312
9861
  return {};
9313
9862
  }
9314
9863
  }
9315
- function discoverConfigs(cwd) {
9864
+ function discoverConfigs(cwd2) {
9316
9865
  const merged = {};
9317
9866
  const global = join16(stateDir(), "mcp.json");
9318
9867
  if (existsSync15(global)) Object.assign(merged, readConfigFile(global));
9319
- let dir = cwd;
9868
+ let dir = cwd2;
9320
9869
  for (let i = 0; i < 24; i++) {
9321
9870
  const p = join16(dir, ".mcp.json");
9322
9871
  if (existsSync15(p)) {
@@ -9584,7 +10133,7 @@ function buildWebTools() {
9584
10133
  // src/input.ts
9585
10134
  import { createInterface as createInterface2 } from "readline/promises";
9586
10135
  import { emitKeypressEvents } from "readline";
9587
- import { stdin, stdout } from "process";
10136
+ import { stdin, stdout as stdout2 } from "process";
9588
10137
  import { readFileSync as readFileSync14, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, existsSync as existsSync16, readdirSync as readdirSync7 } from "fs";
9589
10138
  import { join as join17, dirname as dirname10, basename as basename2 } from "path";
9590
10139
  var HISTORY_FILE = join17(stateDir(), "history");
@@ -9601,7 +10150,7 @@ var InputController = class {
9601
10150
  this.opts = opts;
9602
10151
  this.rl = createInterface2({
9603
10152
  input: stdin,
9604
- output: stdout,
10153
+ output: stdout2,
9605
10154
  terminal: stdin.isTTY ?? false,
9606
10155
  historySize: HISTORY_MAX,
9607
10156
  completer: (line2) => this.complete(line2)
@@ -9732,7 +10281,7 @@ var InputController = class {
9732
10281
  });
9733
10282
  out += "\r " + c.dim("\u2570" + "\u2500".repeat(W) + "\u256F");
9734
10283
  out += up(contentRows - caret.r) + "\r" + right(4 + caret.c);
9735
- stdout.write(out);
10284
+ stdout2.write(out);
9736
10285
  prevRows = contentRows;
9737
10286
  prevCaretR = caret.r;
9738
10287
  };
@@ -9744,7 +10293,7 @@ var InputController = class {
9744
10293
  this.rl.pause();
9745
10294
  if (stdin.isTTY) stdin.setRawMode(true);
9746
10295
  stdin.resume();
9747
- stdout.write("\x1B[?2004h");
10296
+ stdout2.write("\x1B[?2004h");
9748
10297
  const onResize = () => {
9749
10298
  F = Math.max(8, boxInnerWidth() - 2);
9750
10299
  W = F + 2;
@@ -9755,10 +10304,10 @@ var InputController = class {
9755
10304
  const finish = (val) => {
9756
10305
  stdin.off("keypress", onKey);
9757
10306
  process.stdout.off("resize", onResize);
9758
- stdout.write("\x1B[?2004l");
10307
+ stdout2.write("\x1B[?2004l");
9759
10308
  if (stdin.isTTY) stdin.setRawMode(false);
9760
10309
  const down = prevRows - prevCaretR;
9761
- stdout.write("\x1B[" + Math.max(1, down) + "B\r\n");
10310
+ stdout2.write("\x1B[" + Math.max(1, down) + "B\r\n");
9762
10311
  this.rl.resume();
9763
10312
  if (watching) this.startWatch(watching);
9764
10313
  resolve6(val);
@@ -9770,7 +10319,7 @@ var InputController = class {
9770
10319
  const onKey = (s, key) => {
9771
10320
  const name = key?.name;
9772
10321
  if (key?.ctrl && name === "c") {
9773
- stdout.write("\n");
10322
+ stdout2.write("\n");
9774
10323
  this.opts.onExit();
9775
10324
  return;
9776
10325
  }
@@ -9886,7 +10435,7 @@ var InputController = class {
9886
10435
  }
9887
10436
  };
9888
10437
  const bar = this.opts.contextBar?.();
9889
- if (bar) stdout.write(" " + bar + "\n");
10438
+ if (bar) stdout2.write(" " + bar + "\n");
9890
10439
  stdin.on("keypress", onKey);
9891
10440
  render();
9892
10441
  });
@@ -9996,6 +10545,11 @@ export {
9996
10545
  httpBuild,
9997
10546
  demoBootBuild,
9998
10547
  deliverBuild,
10548
+ stateDir,
10549
+ webBaseUrl,
10550
+ loadStoredEnv,
10551
+ saveKey,
10552
+ runVolitionSubcommand,
9999
10553
  deployBuild,
10000
10554
  promoteBuild,
10001
10555
  rollbackDeployment,
@@ -10018,10 +10572,6 @@ export {
10018
10572
  readSourceFile,
10019
10573
  tasks,
10020
10574
  buildTools,
10021
- stateDir,
10022
- webBaseUrl,
10023
- loadStoredEnv,
10024
- saveKey,
10025
10575
  HookRunner,
10026
10576
  mergeHookSets,
10027
10577
  loadHookFiles,