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.
- package/dist/{chunk-UBDW5N5G.js → chunk-NHABZZTE.js} +736 -186
- package/dist/cli.js +68 -2
- package/dist/index.d.ts +181 -13
- package/dist/index.js +1 -1
- package/package.json +3 -3
|
@@ -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
|
|
820
|
-
import { existsSync as
|
|
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
|
|
826
|
-
import { existsSync as
|
|
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
|
-
|
|
1071
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1176
|
-
const dist =
|
|
1177
|
-
if (
|
|
1178
|
-
const src =
|
|
1179
|
-
if (
|
|
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
|
|
1225
|
-
import { join as
|
|
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 =
|
|
1242
|
-
if (!
|
|
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 =
|
|
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 (
|
|
1270
|
-
if (
|
|
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 =
|
|
1276
|
-
const prev =
|
|
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
|
-
|
|
1713
|
+
writeFileSync4(npmrc, prev + sep5 + missing.join("\n") + "\n");
|
|
1281
1714
|
} catch {
|
|
1282
1715
|
}
|
|
1283
1716
|
}
|
|
1284
1717
|
function isolateFromWorkspace(dir) {
|
|
1285
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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" && !
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
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 ?
|
|
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 ({
|
|
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 =
|
|
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(
|
|
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,
|
|
4186
|
+
function runOne(def, event, payload, cwd2) {
|
|
3680
4187
|
return new Promise((res) => {
|
|
3681
|
-
const child = spawn6("sh", ["-c", def.command], { cwd });
|
|
3682
|
-
let
|
|
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) =>
|
|
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:
|
|
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:
|
|
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,
|
|
4210
|
+
constructor(hooks, cwd2) {
|
|
3704
4211
|
this.hooks = hooks;
|
|
3705
|
-
this.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:
|
|
3723
|
-
const parsed = tryJson(
|
|
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 ||
|
|
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 =
|
|
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(
|
|
4270
|
+
function loadHookFiles(cwd2) {
|
|
3764
4271
|
const files = [
|
|
3765
4272
|
join7(stateDir(), "hooks.json"),
|
|
3766
|
-
...projectDirs(
|
|
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,
|
|
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(
|
|
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(
|
|
7322
|
+
const root = repoRootFrom(cwd2);
|
|
6800
7323
|
const chain = [];
|
|
6801
|
-
let dir =
|
|
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(
|
|
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(
|
|
7024
|
-
const all = _Session.list(1,
|
|
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,
|
|
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 (
|
|
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(
|
|
7086
|
-
const abs = resolve5(
|
|
7087
|
-
const rel = relative3(
|
|
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(
|
|
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(
|
|
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(
|
|
7857
|
-
return new _SkillRegistry(loadAllSkills(
|
|
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,
|
|
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,
|
|
8474
|
+
function gitSafe(args, cwd2) {
|
|
7952
8475
|
try {
|
|
7953
|
-
return git(args,
|
|
8476
|
+
return git(args, cwd2);
|
|
7954
8477
|
} catch {
|
|
7955
8478
|
return null;
|
|
7956
8479
|
}
|
|
7957
8480
|
}
|
|
7958
|
-
function isRepo(
|
|
7959
|
-
return gitSafe(["rev-parse", "--is-inside-work-tree"],
|
|
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
|
|
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
|
-
`${
|
|
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(
|
|
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(
|
|
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(
|
|
9200
|
-
const dirs = [join14(stateDir(), "commands"), ...projectDirs(
|
|
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(
|
|
9223
|
-
return [join15(stateDir(), "plugins"), ...projectDirs(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10307
|
+
stdout2.write("\x1B[?2004l");
|
|
9759
10308
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
9760
10309
|
const down = prevRows - prevCaretR;
|
|
9761
|
-
|
|
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
|
-
|
|
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)
|
|
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,
|