wholestack 0.6.1 → 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,24 +1234,28 @@ 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 : [];
830
1248
  const verify = result.verify ?? {};
831
1249
  const proofVerdict = result.proof?.verdict;
832
1250
  const shipped = proofVerdict ? proofVerdict === "SHIP" : !!verify.ok;
1251
+ const pending = result.proof?.proofStatus === "pending" && shipped;
1252
+ const unverified = result.proof?.proofStatus === "unverified";
833
1253
  return {
834
1254
  ok: true,
835
1255
  buildId: result.buildId ?? null,
836
1256
  verdict: shipped ? "SHIP" : "NO_SHIP",
1257
+ proofPending: pending,
1258
+ unverified,
837
1259
  themeId: result.themeId ?? null,
838
1260
  fileCount: files.length,
839
1261
  spec: typeof result.spec === "string" ? result.spec : null,
@@ -869,17 +1291,48 @@ function makeConsumer(onPhase) {
869
1291
  }
870
1292
  };
871
1293
  }
1294
+ async function pollProofStatus(zetaApiUrl, buildId, authHeaders8, opts = {}) {
1295
+ const interval = opts.intervalMs ?? 5e3;
1296
+ const timeout = opts.timeoutMs ?? 10 * 60 * 1e3;
1297
+ const started = Date.now();
1298
+ let last = { proofStatus: "pending", verdict: null, dirty: false };
1299
+ while (Date.now() - started < timeout) {
1300
+ try {
1301
+ const resp = await fetch(
1302
+ `${zetaApiUrl}/api/zeta/build/${encodeURIComponent(buildId)}/proof-status`,
1303
+ { headers: { ...authHeaders8 } }
1304
+ );
1305
+ if (resp.ok) {
1306
+ const j = await resp.json();
1307
+ last = {
1308
+ proofStatus: j.proofStatus ?? null,
1309
+ verdict: j.verdict ?? null,
1310
+ dirty: j.dirty === true
1311
+ };
1312
+ if (last.proofStatus && last.proofStatus !== "pending") return last;
1313
+ }
1314
+ } catch {
1315
+ }
1316
+ opts.onTick?.(Date.now() - started);
1317
+ await new Promise((r) => setTimeout(r, interval));
1318
+ }
1319
+ return last;
1320
+ }
872
1321
  async function httpBuild(zetaApiUrl, body, onPhase) {
873
1322
  let resp;
874
1323
  try {
875
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();
876
1327
  resp = await fetch(`${zetaApiUrl}/api/zeta/build`, {
877
1328
  method: "POST",
878
1329
  headers: {
879
1330
  "content-type": "application/json",
880
1331
  // Membership: lets the engine verify the subscription and return the code
881
1332
  // (and charge one credit per app). Without it, only the preview comes back.
882
- ...apiKey ? { authorization: `Bearer ${apiKey}` } : {}
1333
+ ...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
1334
+ ...byokKey ? { "x-zeta-byok-key": byokKey } : {},
1335
+ ...byokKey && byokProvider ? { "x-zeta-byok-provider": byokProvider } : {}
883
1336
  },
884
1337
  body: JSON.stringify({ ...body, mode: "idea" })
885
1338
  });
@@ -1038,8 +1491,8 @@ async function deliverBuild(zetaApiUrl, buildId, destDir, onPhase) {
1038
1491
  const rel = pathNormalize2(f.path).replace(/^(\.\.(\/|\\|$))+/, "");
1039
1492
  const full = resolve2(root, rel);
1040
1493
  if (full !== root && !full.startsWith(root + sep2)) continue;
1041
- mkdirSync2(dirname2(full), { recursive: true });
1042
- writeFileSync2(full, f.content);
1494
+ mkdirSync3(dirname2(full), { recursive: true });
1495
+ writeFileSync3(full, f.content);
1043
1496
  written += 1;
1044
1497
  onPhase?.(`wrote ${rel}`);
1045
1498
  }
@@ -1049,7 +1502,7 @@ async function deliverBuild(zetaApiUrl, buildId, destDir, onPhase) {
1049
1502
  function findRepoRoot(start) {
1050
1503
  let dir = start ?? dirname2(fileURLToPath(import.meta.url));
1051
1504
  for (let i = 0; i < 12; i++) {
1052
- if (existsSync2(join2(dir, "scripts", "zeta-build-worker.mts"))) return dir;
1505
+ if (existsSync3(join3(dir, "scripts", "zeta-build-worker.mts"))) return dir;
1053
1506
  const up = dirname2(dir);
1054
1507
  if (up === dir) break;
1055
1508
  dir = up;
@@ -1070,9 +1523,9 @@ async function localBuild(body, onPhase, repoRoot) {
1070
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)."
1071
1524
  };
1072
1525
  }
1073
- const worker = join2(root, "scripts", "zeta-build-worker.mts");
1526
+ const worker = join3(root, "scripts", "zeta-build-worker.mts");
1074
1527
  const buildId = `zeta-build-${Date.now().toString(36)}-${Math.floor(performance.now())}`;
1075
- const projectDir = join2(tmpdir(), buildId);
1528
+ const projectDir = join3(tmpdir(), buildId);
1076
1529
  const ideaB64 = Buffer.from(body.idea, "utf8").toString("base64");
1077
1530
  const args = [
1078
1531
  "--import",
@@ -1097,8 +1550,17 @@ async function localBuild(body, onPhase, repoRoot) {
1097
1550
  ...process.env,
1098
1551
  ...body.styleId ? { ZETA_COMPOSE_STYLE: body.styleId } : {},
1099
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) } : {},
1100
1555
  // SNIPER / lean → worker skips the Launch Kit + Brand Studio campaign.
1101
- ...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 } : {}
1102
1564
  },
1103
1565
  stdio: ["ignore", "pipe", "pipe"]
1104
1566
  });
@@ -1143,11 +1605,11 @@ var PROPERTY_KINDS = [
1143
1605
  function resolveProver(repoRoot) {
1144
1606
  const root = repoRoot ?? findRepoRoot();
1145
1607
  if (!root) return null;
1146
- const base = join3(root, "packages", "shipgate-contract-prover");
1147
- const dist = join3(base, "dist", "cli.js");
1148
- if (existsSync3(dist)) return { nodeArgs: [dist] };
1149
- const src = join3(base, "src", "cli.ts");
1150
- 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] };
1151
1613
  return null;
1152
1614
  }
1153
1615
  function runProver(proverArgs, opts) {
@@ -1192,8 +1654,8 @@ ${e.message}` });
1192
1654
  // src/app-runner.ts
1193
1655
  import { spawn as spawn3, spawnSync } from "child_process";
1194
1656
  import { readFile } from "fs/promises";
1195
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1196
- 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";
1197
1659
  var running = [];
1198
1660
  function killRunningApps() {
1199
1661
  for (const a of running.splice(0)) {
@@ -1209,8 +1671,8 @@ function listRunningApps() {
1209
1671
  var URL_RE = /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?[^\s)]*)/i;
1210
1672
  var FATAL_RE = /(Error:|error TS\d+|Cannot find module|EADDRINUSE|Failed to compile|SyntaxError|Module not found|exited with)/i;
1211
1673
  async function detectRun(dir) {
1212
- const pkgPath = join4(dir, "package.json");
1213
- if (!existsSync4(pkgPath)) {
1674
+ const pkgPath = join5(dir, "package.json");
1675
+ if (!existsSync5(pkgPath)) {
1214
1676
  return { error: `no package.json in ${dir} \u2014 not a runnable app directory` };
1215
1677
  }
1216
1678
  let scripts = {};
@@ -1220,7 +1682,7 @@ async function detectRun(dir) {
1220
1682
  } catch (e) {
1221
1683
  return { error: `unreadable package.json: ${e.message}` };
1222
1684
  }
1223
- 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";
1224
1686
  for (const s of ["dev", "start", "serve"]) {
1225
1687
  if (scripts[s]) return { command: pm, args: ["run", s], reason: `${pm} run ${s}` };
1226
1688
  }
@@ -1237,27 +1699,27 @@ function pnpmAvailable() {
1237
1699
  return _pnpmOnPath;
1238
1700
  }
1239
1701
  function installPmFor(dir) {
1240
- if (existsSync4(join4(dir, "yarn.lock"))) return "yarn";
1241
- 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";
1242
1704
  return pnpmAvailable() ? "pnpm" : "npm";
1243
1705
  }
1244
1706
  function ensureNpmrc(dir, lines) {
1245
1707
  try {
1246
- const npmrc = join4(dir, ".npmrc");
1247
- const prev = existsSync4(npmrc) ? readFileSync2(npmrc, "utf8") : "";
1708
+ const npmrc = join5(dir, ".npmrc");
1709
+ const prev = existsSync5(npmrc) ? readFileSync3(npmrc, "utf8") : "";
1248
1710
  const missing = lines.filter((l) => !prev.includes(l.split("=")[0]));
1249
1711
  if (missing.length === 0) return;
1250
1712
  const sep5 = prev && !prev.endsWith("\n") ? "\n" : "";
1251
- writeFileSync3(npmrc, prev + sep5 + missing.join("\n") + "\n");
1713
+ writeFileSync4(npmrc, prev + sep5 + missing.join("\n") + "\n");
1252
1714
  } catch {
1253
1715
  }
1254
1716
  }
1255
1717
  function isolateFromWorkspace(dir) {
1256
- if (existsSync4(join4(dir, "pnpm-workspace.yaml"))) return;
1718
+ if (existsSync5(join5(dir, "pnpm-workspace.yaml"))) return;
1257
1719
  let cur = dirname3(dir);
1258
1720
  let nested = false;
1259
1721
  for (let i = 0; i < 12; i++) {
1260
- if (existsSync4(join4(cur, "pnpm-workspace.yaml"))) {
1722
+ if (existsSync5(join5(cur, "pnpm-workspace.yaml"))) {
1261
1723
  nested = true;
1262
1724
  break;
1263
1725
  }
@@ -1267,14 +1729,14 @@ function isolateFromWorkspace(dir) {
1267
1729
  }
1268
1730
  if (!nested) return;
1269
1731
  try {
1270
- writeFileSync3(join4(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
1732
+ writeFileSync4(join5(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
1271
1733
  ensureNpmrc(dir, ["ignore-workspace-root-check=true"]);
1272
1734
  } catch {
1273
1735
  }
1274
1736
  }
1275
1737
  function installDeps(dir, onLog, signal) {
1276
1738
  const pm = installPmFor(dir);
1277
- if (pm === "pnpm" && !existsSync4(join4(dir, "pnpm-lock.yaml"))) {
1739
+ if (pm === "pnpm" && !existsSync5(join5(dir, "pnpm-lock.yaml"))) {
1278
1740
  ensureNpmrc(dir, ["shamefully-hoist=true", "prefer-offline=true"]);
1279
1741
  }
1280
1742
  const args = pm === "pnpm" ? (
@@ -1314,7 +1776,7 @@ ${e.message}` });
1314
1776
  });
1315
1777
  }
1316
1778
  async function runApp(dir, opts = {}) {
1317
- if (!existsSync4(join4(dir, "node_modules"))) {
1779
+ if (!existsSync5(join5(dir, "node_modules"))) {
1318
1780
  isolateFromWorkspace(dir);
1319
1781
  const inst = await installDeps(dir, void 0, opts.signal);
1320
1782
  if (!inst.ok) {
@@ -1328,7 +1790,7 @@ async function runApp(dir, opts = {}) {
1328
1790
  let command;
1329
1791
  let cmdArgs;
1330
1792
  if (opts.script) {
1331
- const pm = existsSync4(join4(dir, "pnpm-lock.yaml")) ? "pnpm" : "npm";
1793
+ const pm = existsSync5(join5(dir, "pnpm-lock.yaml")) ? "pnpm" : "npm";
1332
1794
  command = pm;
1333
1795
  cmdArgs = ["run", opts.script];
1334
1796
  } else {
@@ -1432,7 +1894,7 @@ async function fetchGithubRepos(webBase, installationId) {
1432
1894
  }
1433
1895
 
1434
1896
  // src/repo-audit.ts
1435
- import { readFileSync as readFileSync3 } from "fs";
1897
+ import { readFileSync as readFileSync4 } from "fs";
1436
1898
  import { createInterface } from "readline/promises";
1437
1899
  import { stdin as input, stdout as output } from "process";
1438
1900
 
@@ -1598,7 +2060,7 @@ async function pickGithubRepoInteractive(webBase) {
1598
2060
  }
1599
2061
  function readSourceFile(path) {
1600
2062
  try {
1601
- const source = readFileSync3(path, "utf8");
2063
+ const source = readFileSync4(path, "utf8");
1602
2064
  if (source.trim().length < 20) return { ok: false, error: "source file is too short" };
1603
2065
  return { ok: true, source };
1604
2066
  } catch (e) {
@@ -1613,11 +2075,11 @@ var TaskManager = class {
1613
2075
  seq = 0;
1614
2076
  map = /* @__PURE__ */ new Map();
1615
2077
  /** Start a detached-from-the-REPL background command. Returns the task id. */
1616
- start(command, args, cwd) {
2078
+ start(command, args, cwd2) {
1617
2079
  const id = `t${++this.seq}`;
1618
2080
  const display = [command, ...args].join(" ");
1619
2081
  const child = spawn4(command, args, {
1620
- cwd,
2082
+ cwd: cwd2,
1621
2083
  stdio: ["ignore", "pipe", "pipe"],
1622
2084
  // Own process group so we can signal the whole tree on kill.
1623
2085
  detached: false
@@ -1712,7 +2174,7 @@ import { tool as tool6 } from "ai";
1712
2174
  import { z as z6 } from "zod";
1713
2175
  import { spawn as spawn5 } from "child_process";
1714
2176
  import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
1715
- 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";
1716
2178
  import fg2 from "fast-glob";
1717
2179
 
1718
2180
  // src/crypto-tools.ts
@@ -2400,8 +2862,11 @@ function buildTools(ctx) {
2400
2862
  const { permissions } = ctx;
2401
2863
  async function runGenerate(opts) {
2402
2864
  const { idea, scope, buildModel, install, keep, themeId, styleId, colorScheme } = opts;
2865
+ const { platform, archetype, styleVibe } = opts;
2403
2866
  const lean = opts.lean ?? true;
2404
- 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;
2405
2870
  if (permissions.guardMutation() === "deny") {
2406
2871
  return { ok: false, error: permissions.planRefusal() };
2407
2872
  }
@@ -2427,7 +2892,13 @@ function buildTools(ctx) {
2427
2892
  ...styleId ? { styleId } : {},
2428
2893
  ...colorScheme ? { colorScheme } : {},
2429
2894
  // SNIPER: app surfaces only, no marketing campaign. On by default for the CLI.
2430
- ...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 } : {}
2431
2902
  };
2432
2903
  const onPhase = (msg) => line(c.dim(` \u21B3 ${scrubVendor(msg)}`));
2433
2904
  try {
@@ -2470,8 +2941,38 @@ function buildTools(ctx) {
2470
2941
  viaHosted = true;
2471
2942
  }
2472
2943
  }
2944
+ if (viaHosted && result.ok && result.proofPending && result.buildId) {
2945
+ const bid = String(result.buildId);
2946
+ line(c.dim(" \u21B3 shipped fast \u2014 runtime proofs running in the background\u2026"));
2947
+ const final = await pollProofStatus(ctx.zetaApiUrl, bid, {}, {
2948
+ timeoutMs: 6 * 60 * 1e3,
2949
+ onTick: (ms) => line(c.dim(` proving\u2026 (${Math.round(ms / 1e3)}s)`))
2950
+ });
2951
+ if (final.proofStatus === "proven") {
2952
+ line(c.green(` \u2713 proven \u2014 ${final.verdict ?? "SHIP"}`));
2953
+ result = { ...result, verdict: final.verdict ?? result.verdict, proofPending: false };
2954
+ } else if (final.proofStatus === "failed") {
2955
+ line(c.red(` \u2717 proof FAILED after ship \u2014 your app is live but UNPROVEN (${final.verdict ?? "NO_SHIP"}).`));
2956
+ line(c.red(` Roll it back: zeta rollback ${bid}`));
2957
+ result = { ...result, verdict: "NO_SHIP", proofPending: false };
2958
+ } else {
2959
+ line(c.dim(` still proving \u2014 check later: zeta proof ${bid}`));
2960
+ }
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
+ }
2473
2974
  if (keep !== false && viaHosted && result.ok && result.buildId && !result.paywalled) {
2474
- 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)}`);
2475
2976
  line(c.dim(" \u21B3 saving your code\u2026"));
2476
2977
  const delivered = await deliverBuild(
2477
2978
  ctx.zetaApiUrl,
@@ -2557,9 +3058,65 @@ function buildTools(ctx) {
2557
3058
  ),
2558
3059
  lean: z6.boolean().default(true).describe(
2559
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."
2560
3089
  )
2561
3090
  }),
2562
- 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
+ })
2563
3120
  }),
2564
3121
  deploy_app: tool6({
2565
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.",
@@ -2647,6 +3204,79 @@ function buildTools(ctx) {
2647
3204
  };
2648
3205
  }
2649
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
+ }),
2650
3280
  promote_app: tool6({
2651
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.",
2652
3282
  inputSchema: z6.object({
@@ -3077,7 +3707,7 @@ function buildTools(ctx) {
3077
3707
  const themeId = themes[i % themes.length];
3078
3708
  line(c.bold(`
3079
3709
  [${i + 1}/${picks.length}] ${p.name} \xB7 ${themeId}`));
3080
- 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)}`);
3081
3711
  let r;
3082
3712
  try {
3083
3713
  r = await runGenerate({
@@ -3357,7 +3987,7 @@ function buildTools(ctx) {
3357
3987
  const names = await readdir(abs);
3358
3988
  const entries = await Promise.all(
3359
3989
  names.map(async (n) => {
3360
- const s = await stat(join5(abs, n)).catch(() => null);
3990
+ const s = await stat(join6(abs, n)).catch(() => null);
3361
3991
  return { name: n, dir: s?.isDirectory() ?? false };
3362
3992
  })
3363
3993
  );
@@ -3548,102 +4178,26 @@ function buildTools(ctx) {
3548
4178
  };
3549
4179
  }
3550
4180
 
3551
- // src/config.ts
3552
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5, chmodSync, renameSync } from "fs";
3553
- import { homedir } from "os";
3554
- import { join as join6 } from "path";
3555
- var LEGACY_DIR = join6(homedir(), ".zeta-g");
3556
- var STATE_DIR = join6(homedir(), ".wholestack");
3557
- try {
3558
- if (existsSync5(LEGACY_DIR) && !existsSync5(STATE_DIR)) renameSync(LEGACY_DIR, STATE_DIR);
3559
- } catch {
3560
- }
3561
- function stateDir() {
3562
- return STATE_DIR;
3563
- }
3564
- function webBaseUrl() {
3565
- return process.env.ZETA_WEB_URL?.trim() || process.env.ZETA_API_URL?.trim() || "https://wholestack.ai";
3566
- }
3567
- function projectDirs(cwd) {
3568
- return [join6(cwd, ".wholestack"), join6(cwd, ".zeta-g")];
3569
- }
3570
- var CONFIG_DIR = STATE_DIR;
3571
- var CONFIG_PATH = join6(CONFIG_DIR, "config.json");
3572
- function parseDotenv(text) {
3573
- const out = {};
3574
- for (const raw of text.split("\n")) {
3575
- const l = raw.trim();
3576
- if (!l || l.startsWith("#")) continue;
3577
- const eq = l.indexOf("=");
3578
- if (eq < 0) continue;
3579
- const k = l.slice(0, eq).trim();
3580
- let v = l.slice(eq + 1).trim();
3581
- if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
3582
- v = v.slice(1, -1);
3583
- }
3584
- if (k) out[k] = v;
3585
- }
3586
- return out;
3587
- }
3588
- function fill(src) {
3589
- for (const [k, v] of Object.entries(src)) {
3590
- if (v && process.env[k] === void 0) process.env[k] = v;
3591
- }
3592
- }
3593
- function loadStoredEnv() {
3594
- const localEnv = join6(process.cwd(), ".env");
3595
- if (existsSync5(localEnv)) {
3596
- try {
3597
- fill(parseDotenv(readFileSync4(localEnv, "utf8")));
3598
- } catch {
3599
- }
3600
- }
3601
- if (existsSync5(CONFIG_PATH)) {
3602
- try {
3603
- const json = JSON.parse(readFileSync4(CONFIG_PATH, "utf8"));
3604
- const flat = {};
3605
- for (const [k, v] of Object.entries(json)) if (typeof v === "string") flat[k] = v;
3606
- fill(flat);
3607
- } catch {
3608
- }
3609
- }
3610
- }
3611
- function saveKey(name, value) {
3612
- mkdirSync3(CONFIG_DIR, { recursive: true });
3613
- let current = {};
3614
- if (existsSync5(CONFIG_PATH)) {
3615
- try {
3616
- current = JSON.parse(readFileSync4(CONFIG_PATH, "utf8"));
3617
- } catch {
3618
- current = {};
3619
- }
3620
- }
3621
- current[name] = value;
3622
- writeFileSync4(CONFIG_PATH, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
3623
- chmodSync(CONFIG_PATH, 384);
3624
- return CONFIG_PATH;
3625
- }
3626
-
3627
4181
  // src/hooks.ts
3628
4182
  import { spawn as spawn6 } from "child_process";
3629
4183
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3630
4184
  import { join as join7 } from "path";
3631
4185
  var HOOK_TIMEOUT_MS = 15e3;
3632
- function runOne(def, event, payload, cwd) {
4186
+ function runOne(def, event, payload, cwd2) {
3633
4187
  return new Promise((res) => {
3634
- const child = spawn6("sh", ["-c", def.command], { cwd });
3635
- let stdout2 = "";
4188
+ const child = spawn6("sh", ["-c", def.command], { cwd: cwd2 });
4189
+ let stdout3 = "";
3636
4190
  let stderr = "";
3637
4191
  const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_TIMEOUT_MS);
3638
- child.stdout.on("data", (b) => stdout2 += b.toString());
4192
+ child.stdout.on("data", (b) => stdout3 += b.toString());
3639
4193
  child.stderr.on("data", (b) => stderr += b.toString());
3640
4194
  child.on("error", (e) => {
3641
4195
  clearTimeout(timer);
3642
- res({ code: 1, stdout: stdout2, stderr: stderr + e.message });
4196
+ res({ code: 1, stdout: stdout3, stderr: stderr + e.message });
3643
4197
  });
3644
4198
  child.on("close", (code) => {
3645
4199
  clearTimeout(timer);
3646
- res({ code: code ?? 0, stdout: stdout2, stderr });
4200
+ res({ code: code ?? 0, stdout: stdout3, stderr });
3647
4201
  });
3648
4202
  try {
3649
4203
  child.stdin.write(JSON.stringify({ event, ...payload }));
@@ -3653,9 +4207,9 @@ function runOne(def, event, payload, cwd) {
3653
4207
  });
3654
4208
  }
3655
4209
  var HookRunner = class {
3656
- constructor(hooks, cwd) {
4210
+ constructor(hooks, cwd2) {
3657
4211
  this.hooks = hooks;
3658
- this.cwd = cwd;
4212
+ this.cwd = cwd2;
3659
4213
  }
3660
4214
  hooks;
3661
4215
  cwd;
@@ -3672,22 +4226,22 @@ var HookRunner = class {
3672
4226
  for (const def of defs) {
3673
4227
  if (def.matcher && payload.toolName && !new RegExp(def.matcher).test(payload.toolName))
3674
4228
  continue;
3675
- const { code, stdout: stdout2, stderr } = await runOne(def, event, payload, this.cwd);
3676
- const parsed = tryJson(stdout2);
4229
+ const { code, stdout: stdout3, stderr } = await runOne(def, event, payload, this.cwd);
4230
+ const parsed = tryJson(stdout3);
3677
4231
  if (parsed && (parsed.decision === "block" || parsed.block)) {
3678
4232
  outcome.block = String(parsed.reason ?? parsed.message ?? "blocked by hook");
3679
4233
  return outcome;
3680
4234
  }
3681
4235
  if (canBlock && code !== 0) {
3682
4236
  const reason = parsed ? String(parsed.reason ?? parsed.message ?? "") : "";
3683
- outcome.block = (stderr || reason || stdout2 || `hook exited ${code}`).trim();
4237
+ outcome.block = (stderr || reason || stdout3 || `hook exited ${code}`).trim();
3684
4238
  return outcome;
3685
4239
  }
3686
4240
  if (parsed) {
3687
4241
  const extra = parsed.additionalContext ?? parsed.hookSpecificOutput?.additionalContext;
3688
4242
  if (extra) outcome.context.push(String(extra));
3689
4243
  } else {
3690
- const text = stdout2.trim();
4244
+ const text = stdout3.trim();
3691
4245
  if (text) outcome.context.push(text);
3692
4246
  }
3693
4247
  }
@@ -3713,10 +4267,10 @@ function mergeHookSets(...sets) {
3713
4267
  }
3714
4268
  return out;
3715
4269
  }
3716
- function loadHookFiles(cwd) {
4270
+ function loadHookFiles(cwd2) {
3717
4271
  const files = [
3718
4272
  join7(stateDir(), "hooks.json"),
3719
- ...projectDirs(cwd).map((d) => join7(d, "hooks.json"))
4273
+ ...projectDirs(cwd2).map((d) => join7(d, "hooks.json"))
3720
4274
  ];
3721
4275
  const sets = [];
3722
4276
  for (const f of files) {
@@ -3878,7 +4432,9 @@ var MODEL_IDS = {
3878
4432
  /** BRAIN lane — frontend swarm, decisions, debugging, editing, planning. Cerebras. The 20%. */
3879
4433
  glm: readEnv("ZETA_GLM_MODEL") || "zai-glm-4.7",
3880
4434
  /** VISION lane — accepts image input. OpenRouter (Cerebras text lanes can't see). */
3881
- 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"
3882
4438
  };
3883
4439
  var ZETA_AGENT_PIPELINE = Object.freeze({
3884
4440
  /** THINK lane — ISL spec authoring BASE. gpt-oss-120b ($0.35/$0.75 per 1M) so a build
@@ -3903,6 +4459,7 @@ var CEREBRAS_KEY = PROVIDERS.cerebras.keyEnv;
3903
4459
  var KEY_ENV = PROVIDERS.openrouter.keyEnv;
3904
4460
  var DEFAULT_CUSTOM_MODEL = MODEL_IDS.vision;
3905
4461
  var VISION_MODEL = MODEL_IDS.vision;
4462
+ var SONNET_MODEL = MODEL_IDS.sonnet;
3906
4463
  var MODELS = /* @__PURE__ */ new Map([
3907
4464
  [
3908
4465
  "zeta-g1-lite",
@@ -3949,6 +4506,19 @@ var MODELS = /* @__PURE__ */ new Map([
3949
4506
  contextWindow: 2e5,
3950
4507
  thinking: null
3951
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
+ }
3952
4522
  ]
3953
4523
  ]);
3954
4524
  var MODEL_KEYS = [...MODELS.keys()];
@@ -3990,9 +4560,9 @@ function resolveModelKey(raw) {
3990
4560
  image: "zeta-g1-vision",
3991
4561
  // legacy convenience — resolve silently, but only the Zeta label is shown
3992
4562
  opus: "zeta-g1-max",
3993
- sonnet: "zeta-g1",
4563
+ sonnet: "zeta-g1-sonnet",
3994
4564
  haiku: "zeta-g1-lite",
3995
- claude: "zeta-g1"
4565
+ claude: "zeta-g1-sonnet"
3996
4566
  };
3997
4567
  if (ALIASES[lower]) return ALIASES[lower];
3998
4568
  throw new Error(`Unknown model "${raw}". Try: ${MODEL_KEYS.join(", ")}.`);
@@ -4010,7 +4580,7 @@ function modelId(key) {
4010
4580
  return spec(key).modelId;
4011
4581
  }
4012
4582
  function visionCapable(key) {
4013
- return key === "zeta-g1-vision" || key.startsWith("custom:");
4583
+ return key === "zeta-g1-vision" || key === "zeta-g1-sonnet" || key.startsWith("custom:");
4014
4584
  }
4015
4585
  function modelContextWindow(key) {
4016
4586
  return spec(key).contextWindow;
@@ -5047,11 +5617,11 @@ function readScripts(dir) {
5047
5617
  return {};
5048
5618
  }
5049
5619
  }
5050
- function runScript(pm, script, cwd, timeoutMs) {
5620
+ function runScript(pm, script, cwd2, timeoutMs) {
5051
5621
  const shell = process.env.SHELL || "/bin/bash";
5052
5622
  const line2 = `${pm} run ${script}`;
5053
5623
  return new Promise((res) => {
5054
- const child = spawn7(shell, ["-lc", line2], { cwd, shell: false });
5624
+ const child = spawn7(shell, ["-lc", line2], { cwd: cwd2, shell: false });
5055
5625
  let out = "";
5056
5626
  const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
5057
5627
  const sink = (b) => {
@@ -6745,13 +7315,13 @@ async function readBounded(path, limit) {
6745
7315
  return null;
6746
7316
  }
6747
7317
  }
6748
- async function loadProjectMemory(cwd) {
7318
+ async function loadProjectMemory(cwd2) {
6749
7319
  const candidates = [];
6750
7320
  const globalFile = join9(stateDir(), "ZETA.md");
6751
7321
  if (existsSync9(globalFile)) candidates.push(globalFile);
6752
- const root = repoRootFrom(cwd);
7322
+ const root = repoRootFrom(cwd2);
6753
7323
  const chain = [];
6754
- let dir = cwd;
7324
+ let dir = cwd2;
6755
7325
  for (let i = 0; i < 24; i++) {
6756
7326
  chain.unshift(dir);
6757
7327
  if (dir === root) break;
@@ -6943,10 +7513,10 @@ var Session = class _Session {
6943
7513
  meta;
6944
7514
  path;
6945
7515
  /** Start a fresh session and write its meta header. */
6946
- static create(cwd, model) {
7516
+ static create(cwd2, model) {
6947
7517
  ensureDir();
6948
7518
  const id = `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
6949
- const meta = { id, cwd, model, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
7519
+ const meta = { id, cwd: cwd2, model, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
6950
7520
  const path = fileFor(id);
6951
7521
  appendFileSync(path, JSON.stringify({ kind: "meta", ...meta }) + "\n", "utf8");
6952
7522
  return new _Session(meta, path);
@@ -6973,12 +7543,12 @@ var Session = class _Session {
6973
7543
  return { session: new _Session(meta, path), messages };
6974
7544
  }
6975
7545
  /** The most recent session (optionally scoped to a working directory). */
6976
- static latestId(cwd) {
6977
- const all = _Session.list(1, cwd);
7546
+ static latestId(cwd2) {
7547
+ const all = _Session.list(1, cwd2);
6978
7548
  return all.length ? all[0].id : null;
6979
7549
  }
6980
7550
  /** List sessions newest-first, with a preview of the first user message. */
6981
- static list(limit = 30, cwd) {
7551
+ static list(limit = 30, cwd2) {
6982
7552
  if (!existsSync10(SESSIONS_DIR)) return [];
6983
7553
  const out = [];
6984
7554
  for (const name of readdirSync2(SESSIONS_DIR)) {
@@ -7004,7 +7574,7 @@ var Session = class _Session {
7004
7574
  continue;
7005
7575
  }
7006
7576
  if (!meta) continue;
7007
- if (cwd && meta.cwd !== cwd) continue;
7577
+ if (cwd2 && meta.cwd !== cwd2) continue;
7008
7578
  const updatedAt = statSync2(path).mtimeMs;
7009
7579
  out.push({ ...meta, preview: preview.slice(0, 70), turns, updatedAt });
7010
7580
  }
@@ -7035,9 +7605,9 @@ async function loadChromium() {
7035
7605
  return null;
7036
7606
  }
7037
7607
  }
7038
- function insideCwd(cwd, p) {
7039
- const abs = resolve5(cwd, p);
7040
- const rel = relative3(cwd, abs);
7608
+ function insideCwd(cwd2, p) {
7609
+ const abs = resolve5(cwd2, p);
7610
+ const rel = relative3(cwd2, abs);
7041
7611
  if (rel === ".." || rel.startsWith(".." + sep4)) {
7042
7612
  throw new Error(`path "${p}" escapes the workspace root`);
7043
7613
  }
@@ -7788,7 +8358,7 @@ function shippedSkillsDir() {
7788
8358
  for (const dir of candidates) if (existsSync12(dir)) return dir;
7789
8359
  return "";
7790
8360
  }
7791
- function loadAllSkills(cwd) {
8361
+ function loadAllSkills(cwd2) {
7792
8362
  const byName = /* @__PURE__ */ new Map();
7793
8363
  const add = (skills) => {
7794
8364
  for (const s of skills) byName.set(s.name, s);
@@ -7796,7 +8366,7 @@ function loadAllSkills(cwd) {
7796
8366
  const shipped = shippedSkillsDir();
7797
8367
  if (shipped) add(loadSkillsFromDir(shipped, "shipped"));
7798
8368
  add(loadSkillsFromDir(join13(stateDir(), "skills"), "user"));
7799
- 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"));
7800
8370
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
7801
8371
  }
7802
8372
  var SkillRegistry = class _SkillRegistry {
@@ -7806,8 +8376,8 @@ var SkillRegistry = class _SkillRegistry {
7806
8376
  this.skills = skills;
7807
8377
  this.index = new Map(skills.map((s) => [s.name, s]));
7808
8378
  }
7809
- static load(cwd) {
7810
- return new _SkillRegistry(loadAllSkills(cwd));
8379
+ static load(cwd2) {
8380
+ return new _SkillRegistry(loadAllSkills(cwd2));
7811
8381
  }
7812
8382
  list() {
7813
8383
  return this.skills;
@@ -7893,23 +8463,23 @@ import { join as join14 } from "path";
7893
8463
 
7894
8464
  // src/git.ts
7895
8465
  import { execFileSync } from "child_process";
7896
- function git(args, cwd) {
8466
+ function git(args, cwd2) {
7897
8467
  return execFileSync("git", args, {
7898
- cwd,
8468
+ cwd: cwd2,
7899
8469
  encoding: "utf8",
7900
8470
  maxBuffer: 32 * 1024 * 1024,
7901
8471
  stdio: ["ignore", "pipe", "pipe"]
7902
8472
  }).trim();
7903
8473
  }
7904
- function gitSafe(args, cwd) {
8474
+ function gitSafe(args, cwd2) {
7905
8475
  try {
7906
- return git(args, cwd);
8476
+ return git(args, cwd2);
7907
8477
  } catch {
7908
8478
  return null;
7909
8479
  }
7910
8480
  }
7911
- function isRepo(cwd) {
7912
- 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";
7913
8483
  }
7914
8484
  var SECRET_HINTS = [
7915
8485
  { re: /\bAKIA[0-9A-Z]{16}\b/, label: "AWS access key id" },
@@ -8072,7 +8642,7 @@ var eyesCommands = [
8072
8642
  ];
8073
8643
 
8074
8644
  // src/ops-commands.ts
8075
- function apiBase() {
8645
+ function apiBase2() {
8076
8646
  return (process.env.ZETA_WEB_URL?.trim() || process.env.ZETA_API_URL?.trim() || "https://wholestack.ai").replace(/\/$/, "");
8077
8647
  }
8078
8648
  async function probe2(url, timeoutMs) {
@@ -8144,7 +8714,7 @@ var opsCommands = [
8144
8714
  return { type: "handled" };
8145
8715
  }
8146
8716
  const r = await apiJson(
8147
- `${apiBase()}/api/zeta/build/${encodeURIComponent(buildId)}/deploy?deploymentId=${encodeURIComponent(deploymentId)}`
8717
+ `${apiBase2()}/api/zeta/build/${encodeURIComponent(buildId)}/deploy?deploymentId=${encodeURIComponent(deploymentId)}`
8148
8718
  );
8149
8719
  ctx.print();
8150
8720
  if (!r.ok) {
@@ -8163,7 +8733,7 @@ var opsCommands = [
8163
8733
  summary: "read live traffic + revenue (PostHog/Stripe)",
8164
8734
  source: "builtin",
8165
8735
  run: async (ctx) => {
8166
- const r = await readMetrics(apiBase());
8736
+ const r = await readMetrics(apiBase2());
8167
8737
  ctx.print();
8168
8738
  if (!r.ok || !r.metrics) {
8169
8739
  ctx.print(" " + c.red(`\u2717 ${r.error ?? "no metrics"}`));
@@ -8196,7 +8766,7 @@ var opsCommands = [
8196
8766
  summary: "read recent production errors (Sentry/PostHog)",
8197
8767
  source: "builtin",
8198
8768
  run: async (ctx) => {
8199
- const r = await readMetrics(apiBase());
8769
+ const r = await readMetrics(apiBase2());
8200
8770
  ctx.print();
8201
8771
  if (!r.ok || !r.metrics) {
8202
8772
  ctx.print(" " + c.red(`\u2717 ${r.error ?? "no metrics"}`));
@@ -8467,7 +9037,7 @@ var BUILTINS = [
8467
9037
  },
8468
9038
  {
8469
9039
  name: "build",
8470
- 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>])",
8471
9041
  source: "builtin",
8472
9042
  run: (ctx) => {
8473
9043
  if (!ctx.args) {
@@ -8505,6 +9075,29 @@ var BUILTINS = [
8505
9075
  campaign = true;
8506
9076
  rest = rest.replace(/(^|\s)--(campaign|full-kit)(\s|$)/, " ");
8507
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
+ }
8508
9101
  const idea = rest.trim();
8509
9102
  if (!idea) {
8510
9103
  ctx.print(
@@ -8516,6 +9109,8 @@ var BUILTINS = [
8516
9109
  'scope: "full"',
8517
9110
  // Sniper by default (lean:true) — skip the marketing campaign. --campaign opts in.
8518
9111
  `lean: ${campaign ? "false" : "true"}`,
9112
+ // UNHINGED (--unhinged): skip ALL proofs/gates → fastest bootable app, UNVERIFIED ship.
9113
+ unhinged ? "unhinged: true" : null,
8519
9114
  styleId ? `styleId: "${styleId}"` : null,
8520
9115
  colorScheme ? `colorScheme: "${colorScheme}"` : null
8521
9116
  ].filter(Boolean).join(", ");
@@ -9047,6 +9642,7 @@ var BUILTINS = [
9047
9642
  ...gitCommands,
9048
9643
  ...eyesCommands,
9049
9644
  ...opsCommands,
9645
+ ...volitionCommands,
9050
9646
  ...learnedCommands,
9051
9647
  ...autoRouteCommands,
9052
9648
  ...skillsCommands,
@@ -9149,8 +9745,8 @@ var CommandRegistry = class {
9149
9745
  return this.ordered.flatMap((c2) => [c2.name, ...c2.aliases ?? []]);
9150
9746
  }
9151
9747
  /** Load *.md commands from ~/.wholestack/commands and <cwd>/.wholestack/commands (or legacy .zeta-g/). */
9152
- loadCustom(cwd) {
9153
- 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"))];
9154
9750
  for (const dir of dirs) {
9155
9751
  for (const cmd of loadMdCommands(dir, "custom")) this.register(cmd);
9156
9752
  }
@@ -9172,8 +9768,8 @@ var CommandRegistry = class {
9172
9768
  // src/plugins.ts
9173
9769
  import { readFileSync as readFileSync12, existsSync as existsSync14, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
9174
9770
  import { join as join15 } from "path";
9175
- function pluginRoots(cwd) {
9176
- 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"))];
9177
9773
  }
9178
9774
  function resolveSystemPrompt(dir, value) {
9179
9775
  const asFile = join15(dir, value);
@@ -9186,7 +9782,7 @@ function resolveSystemPrompt(dir, value) {
9186
9782
  }
9187
9783
  return value.trim();
9188
9784
  }
9189
- function loadPlugins(cwd) {
9785
+ function loadPlugins(cwd2) {
9190
9786
  const result = {
9191
9787
  plugins: [],
9192
9788
  systemText: "",
@@ -9197,7 +9793,7 @@ function loadPlugins(cwd) {
9197
9793
  };
9198
9794
  const systemBlocks = [];
9199
9795
  const hookSets = [];
9200
- for (const root of pluginRoots(cwd)) {
9796
+ for (const root of pluginRoots(cwd2)) {
9201
9797
  if (!existsSync14(root)) continue;
9202
9798
  for (const entry of readdirSync6(root)) {
9203
9799
  const dir = join15(root, entry);
@@ -9265,11 +9861,11 @@ function readConfigFile(path) {
9265
9861
  return {};
9266
9862
  }
9267
9863
  }
9268
- function discoverConfigs(cwd) {
9864
+ function discoverConfigs(cwd2) {
9269
9865
  const merged = {};
9270
9866
  const global = join16(stateDir(), "mcp.json");
9271
9867
  if (existsSync15(global)) Object.assign(merged, readConfigFile(global));
9272
- let dir = cwd;
9868
+ let dir = cwd2;
9273
9869
  for (let i = 0; i < 24; i++) {
9274
9870
  const p = join16(dir, ".mcp.json");
9275
9871
  if (existsSync15(p)) {
@@ -9537,7 +10133,7 @@ function buildWebTools() {
9537
10133
  // src/input.ts
9538
10134
  import { createInterface as createInterface2 } from "readline/promises";
9539
10135
  import { emitKeypressEvents } from "readline";
9540
- import { stdin, stdout } from "process";
10136
+ import { stdin, stdout as stdout2 } from "process";
9541
10137
  import { readFileSync as readFileSync14, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, existsSync as existsSync16, readdirSync as readdirSync7 } from "fs";
9542
10138
  import { join as join17, dirname as dirname10, basename as basename2 } from "path";
9543
10139
  var HISTORY_FILE = join17(stateDir(), "history");
@@ -9554,7 +10150,7 @@ var InputController = class {
9554
10150
  this.opts = opts;
9555
10151
  this.rl = createInterface2({
9556
10152
  input: stdin,
9557
- output: stdout,
10153
+ output: stdout2,
9558
10154
  terminal: stdin.isTTY ?? false,
9559
10155
  historySize: HISTORY_MAX,
9560
10156
  completer: (line2) => this.complete(line2)
@@ -9685,7 +10281,7 @@ var InputController = class {
9685
10281
  });
9686
10282
  out += "\r " + c.dim("\u2570" + "\u2500".repeat(W) + "\u256F");
9687
10283
  out += up(contentRows - caret.r) + "\r" + right(4 + caret.c);
9688
- stdout.write(out);
10284
+ stdout2.write(out);
9689
10285
  prevRows = contentRows;
9690
10286
  prevCaretR = caret.r;
9691
10287
  };
@@ -9697,7 +10293,7 @@ var InputController = class {
9697
10293
  this.rl.pause();
9698
10294
  if (stdin.isTTY) stdin.setRawMode(true);
9699
10295
  stdin.resume();
9700
- stdout.write("\x1B[?2004h");
10296
+ stdout2.write("\x1B[?2004h");
9701
10297
  const onResize = () => {
9702
10298
  F = Math.max(8, boxInnerWidth() - 2);
9703
10299
  W = F + 2;
@@ -9708,10 +10304,10 @@ var InputController = class {
9708
10304
  const finish = (val) => {
9709
10305
  stdin.off("keypress", onKey);
9710
10306
  process.stdout.off("resize", onResize);
9711
- stdout.write("\x1B[?2004l");
10307
+ stdout2.write("\x1B[?2004l");
9712
10308
  if (stdin.isTTY) stdin.setRawMode(false);
9713
10309
  const down = prevRows - prevCaretR;
9714
- stdout.write("\x1B[" + Math.max(1, down) + "B\r\n");
10310
+ stdout2.write("\x1B[" + Math.max(1, down) + "B\r\n");
9715
10311
  this.rl.resume();
9716
10312
  if (watching) this.startWatch(watching);
9717
10313
  resolve6(val);
@@ -9723,7 +10319,7 @@ var InputController = class {
9723
10319
  const onKey = (s, key) => {
9724
10320
  const name = key?.name;
9725
10321
  if (key?.ctrl && name === "c") {
9726
- stdout.write("\n");
10322
+ stdout2.write("\n");
9727
10323
  this.opts.onExit();
9728
10324
  return;
9729
10325
  }
@@ -9839,7 +10435,7 @@ var InputController = class {
9839
10435
  }
9840
10436
  };
9841
10437
  const bar = this.opts.contextBar?.();
9842
- if (bar) stdout.write(" " + bar + "\n");
10438
+ if (bar) stdout2.write(" " + bar + "\n");
9843
10439
  stdin.on("keypress", onKey);
9844
10440
  render();
9845
10441
  });
@@ -9946,6 +10542,14 @@ export {
9946
10542
  writeProjectLink,
9947
10543
  writeWorkingTree,
9948
10544
  collectLocalFiles,
10545
+ httpBuild,
10546
+ demoBootBuild,
10547
+ deliverBuild,
10548
+ stateDir,
10549
+ webBaseUrl,
10550
+ loadStoredEnv,
10551
+ saveKey,
10552
+ runVolitionSubcommand,
9949
10553
  deployBuild,
9950
10554
  promoteBuild,
9951
10555
  rollbackDeployment,
@@ -9959,6 +10563,8 @@ export {
9959
10563
  PROPERTY_KINDS,
9960
10564
  runProver,
9961
10565
  killRunningApps,
10566
+ isolateFromWorkspace,
10567
+ installDeps,
9962
10568
  fetchGithubInstallations,
9963
10569
  fetchGithubRepos,
9964
10570
  runRepoAuditStream,
@@ -9966,10 +10572,6 @@ export {
9966
10572
  readSourceFile,
9967
10573
  tasks,
9968
10574
  buildTools,
9969
- stateDir,
9970
- webBaseUrl,
9971
- loadStoredEnv,
9972
- saveKey,
9973
10575
  HookRunner,
9974
10576
  mergeHookSets,
9975
10577
  loadHookFiles,