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.
package/dist/cli.js CHANGED
@@ -20,6 +20,8 @@ import {
20
20
  buildWebTools,
21
21
  c,
22
22
  collectLocalFiles,
23
+ deliverBuild,
24
+ demoBootBuild,
23
25
  deployBuild,
24
26
  drawCanvas,
25
27
  escalationBus,
@@ -28,8 +30,11 @@ import {
28
30
  generateLegal,
29
31
  getBusiness,
30
32
  getLaunchKit,
33
+ httpBuild,
31
34
  inspectUrl,
35
+ installDeps,
32
36
  isPermissionMode,
37
+ isolateFromWorkspace,
33
38
  killRunningApps,
34
39
  line,
35
40
  listBusinesses,
@@ -62,6 +67,7 @@ import {
62
67
  rollbackDeployment,
63
68
  runProver,
64
69
  runRepoAuditStream,
70
+ runVolitionSubcommand,
65
71
  saveKey,
66
72
  searchLessons,
67
73
  stateDir,
@@ -75,11 +81,11 @@ import {
75
81
  webBaseUrl,
76
82
  writeProjectLink,
77
83
  writeWorkingTree
78
- } from "./chunk-KJ5INI3M.js";
84
+ } from "./chunk-NHABZZTE.js";
79
85
 
80
86
  // src/cli.ts
81
87
  import { createInterface } from "readline/promises";
82
- import { stdin, stdout as stdout3, argv, exit, cwd as cwd2 } from "process";
88
+ import { stdin, stdout as stdout4, argv, exit, cwd as cwd3 } from "process";
83
89
 
84
90
  // src/cli-login.ts
85
91
  import { createServer } from "http";
@@ -107,7 +113,7 @@ async function loginBrowser(opts) {
107
113
  const state = randomBytes(16).toString("hex");
108
114
  const device = hostname().slice(0, 60);
109
115
  const timeoutMs = (opts.timeoutSec ?? 240) * 1e3;
110
- return new Promise((resolve4) => {
116
+ return new Promise((resolve5) => {
111
117
  let settled = false;
112
118
  const finish = (token) => {
113
119
  if (settled) return;
@@ -117,7 +123,7 @@ async function loginBrowser(opts) {
117
123
  server.close();
118
124
  } catch {
119
125
  }
120
- resolve4(token);
126
+ resolve5(token);
121
127
  };
122
128
  const server = createServer((req, res) => {
123
129
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -334,7 +340,7 @@ async function renderTree(absDir, relDir) {
334
340
  return `${relDir || "."}/ (directory tree)
335
341
  ${lines.join("\n")}`;
336
342
  }
337
- async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET) {
343
+ async function expandMentions(message, cwd4, tokenBudget = DEFAULT_TOKEN_BUDGET) {
338
344
  const mentions = extractMentions(message);
339
345
  if (mentions.length === 0) return { context: "", notice: null };
340
346
  const blocks = [];
@@ -344,7 +350,7 @@ async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET)
344
350
  for (const mention of mentions) {
345
351
  if (spent >= tokenBudget) break;
346
352
  const raw = mention.startsWith("~/") ? join(process.env.HOME ?? "", mention.slice(2)) : mention;
347
- const direct = isAbsolute(raw) ? raw : resolve(cwd3, raw);
353
+ const direct = isAbsolute(raw) ? raw : resolve(cwd4, raw);
348
354
  let isDir = false;
349
355
  try {
350
356
  isDir = (await stat(direct)).isDirectory();
@@ -352,7 +358,7 @@ async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET)
352
358
  isDir = false;
353
359
  }
354
360
  if (isDir) {
355
- const rel = relative(cwd3, direct);
361
+ const rel = relative(cwd4, direct);
356
362
  const tree = await renderTree(direct, rel);
357
363
  const cost = estTokens(tree);
358
364
  if (spent + cost <= tokenBudget) {
@@ -365,7 +371,7 @@ async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET)
365
371
  let files = [];
366
372
  try {
367
373
  files = await fg(raw, {
368
- cwd: cwd3,
374
+ cwd: cwd4,
369
375
  ignore: IGNORE,
370
376
  onlyFiles: true,
371
377
  dot: true,
@@ -377,7 +383,7 @@ async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET)
377
383
  }
378
384
  if (files.length === 0) {
379
385
  try {
380
- if ((await stat(direct)).isFile()) files = [relative(cwd3, direct)];
386
+ if ((await stat(direct)).isFile()) files = [relative(cwd4, direct)];
381
387
  } catch {
382
388
  }
383
389
  }
@@ -388,7 +394,7 @@ async function expandMentions(message, cwd3, tokenBudget = DEFAULT_TOKEN_BUDGET)
388
394
  seen.add(rel);
389
395
  let buf;
390
396
  try {
391
- buf = await readFile2(resolve(cwd3, rel), "utf8");
397
+ buf = await readFile2(resolve(cwd4, rel), "utf8");
392
398
  } catch {
393
399
  continue;
394
400
  }
@@ -563,9 +569,9 @@ function workerToolset(worktreeCtx) {
563
569
  return out;
564
570
  }
565
571
  var WORKER_SYSTEM = "You are an autonomous engineer spawned by a lead engineer to complete ONE focused, self-contained slice of a larger task. You have a full coding toolset (read_file, write_file, edit_file, multi_edit, list_dir, glob, grep, run_command) and you are working in your OWN isolated git worktree \u2014 your edits cannot affect anyone else, so edit freely. Approach: (1) explore the relevant files, (2) make the change, (3) verify it (build/test/typecheck where it makes sense), (4) report. Stay strictly within your assigned slice \u2014 do not refactor unrelated code. When done, return a tight summary: what you changed, which files, and how you verified it.";
566
- function git(args, cwd3, timeoutMs = 3e4) {
572
+ function git(args, cwd4, timeoutMs = 3e4) {
567
573
  return new Promise((res) => {
568
- const child = spawn2("git", args, { cwd: cwd3, shell: false });
574
+ const child = spawn2("git", args, { cwd: cwd4, shell: false });
569
575
  let out = "";
570
576
  const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
571
577
  const sink = (b) => {
@@ -846,6 +852,365 @@ async function runOpsSubcommand(raw) {
846
852
  return 1;
847
853
  }
848
854
 
855
+ // src/factory.ts
856
+ import { existsSync, mkdirSync, readFileSync } from "fs";
857
+ import { join as join3, resolve as resolve2 } from "path";
858
+ import { cwd as cwd2, stderr, stdout as stdout2 } from "process";
859
+ var HELP = `${c.bold("wholestack make")} \u2014 the app factory. One idea in, a full-stack app out.
860
+
861
+ ${c.bold("USAGE")}
862
+ wholestack make ${c.dim('"<idea>"')} make one app from an idea
863
+ wholestack make --batch ${c.dim("<file>")} make one app per non-blank line of a file
864
+
865
+ ${c.bold("OPTIONS")}
866
+ --out ${c.dim("<dir>")} parent dir for delivered apps ${c.dim("(default: ./out)")}
867
+ --batch ${c.dim("<file>")} a file of ideas, one per line (# comments allowed)
868
+ --concurrency ${c.dim("<n>")} build N apps at once ${c.dim("(default: 1; engine may rate-limit)")}
869
+ --json emit NDJSON results to stdout, human chrome to stderr
870
+ --boot boot a free live URL instead of delivering code ${c.dim("(no paywall)")}
871
+ --install run pnpm install in each delivered app
872
+ --max use the zeta-g1-max build model ${c.dim("(default: zeta-g1)")}
873
+ --theme ${c.dim("<id>")} force a theme id
874
+ --style ${c.dim("<id>")} force a Launch Style id (e.g. mono-noir, voltage)
875
+ --color ${c.dim("<d|l>")} force dark or light
876
+ --tier ${c.dim("<t>")} app size: solo\xB7team\xB7enterprise ${c.dim("(default: inferred from the idea)")}
877
+ --feature ${c.dim("<f>")} force a feature on (repeatable); prefix ${c.dim("no-")} to force off
878
+ --mobile add the Capacitor store-shippable iOS/Android shell
879
+ --archetype ${c.dim("<a>")} pin the interior layout: analytics\xB7document-editor\xB7collaboration\xB7kanban\xB7calendar\xB7marketplace\xB7studio
880
+ --vibe ${c.dim("<v>")} look filter: cinematic-dark\xB7editorial-luxe\xB7warm-minimal\xB7bold-brutalist\xB7glass-futuristic\xB7vibrant-playful\xB7corporate-clean
881
+ --verbose stream the engine's build phases
882
+ -h, --help show this help
883
+
884
+ ${c.bold("FEATURES")} ${c.dim("(for --feature)")}
885
+ ${c.dim("socialAuth \xB7 multiTenant \xB7 billing \xB7 search \xB7 files \xB7 admin \xB7 compliance \xB7 infra \xB7 mobile \xB7 analytics")}
886
+ ${c.dim("e.g. --tier solo --feature billing --feature no-infra (a lean app + payments, no terraform)")}
887
+
888
+ ${c.bold("NOTES")}
889
+ \u2022 Delivering code requires a Wholestack membership and spends one build credit per
890
+ app. Run ${c.cyan("wholestack login")} first. ${c.dim("--boot is free but keeps no code.")}
891
+ \u2022 Exit code is 0 only when every requested app shipped.`;
892
+ var FEATURE_KEYS = [
893
+ "socialAuth",
894
+ "multiTenant",
895
+ "billing",
896
+ "search",
897
+ "files",
898
+ "admin",
899
+ "compliance",
900
+ "infra",
901
+ "mobile",
902
+ "analytics"
903
+ ];
904
+ var KNOWN = /* @__PURE__ */ new Set([
905
+ "--out",
906
+ "--batch",
907
+ "--concurrency",
908
+ "--json",
909
+ "--boot",
910
+ "--install",
911
+ "--max",
912
+ "--theme",
913
+ "--style",
914
+ "--color",
915
+ "--tier",
916
+ "--feature",
917
+ "--mobile",
918
+ "--archetype",
919
+ "--vibe",
920
+ "--verbose",
921
+ "-h",
922
+ "--help"
923
+ ]);
924
+ function parseFlags(rest) {
925
+ const f = {
926
+ out: "out",
927
+ concurrency: 1,
928
+ json: false,
929
+ boot: false,
930
+ install: false,
931
+ buildModel: "zeta-g1",
932
+ verbose: false,
933
+ help: false,
934
+ ideaParts: []
935
+ };
936
+ for (let i = 0; i < rest.length; i++) {
937
+ const a = rest[i];
938
+ const need = (label) => {
939
+ const v = rest[++i];
940
+ if (v == null || v.startsWith("-")) throw new Error(`${label} needs a value`);
941
+ return v;
942
+ };
943
+ switch (a) {
944
+ case "-h":
945
+ case "--help":
946
+ f.help = true;
947
+ break;
948
+ case "--json":
949
+ f.json = true;
950
+ break;
951
+ case "--boot":
952
+ f.boot = true;
953
+ break;
954
+ case "--install":
955
+ f.install = true;
956
+ break;
957
+ case "--max":
958
+ f.buildModel = "zeta-g1-max";
959
+ break;
960
+ case "--verbose":
961
+ f.verbose = true;
962
+ break;
963
+ case "--out":
964
+ f.out = need("--out");
965
+ break;
966
+ case "--batch":
967
+ f.batch = need("--batch");
968
+ break;
969
+ case "--concurrency": {
970
+ const n = Number(need("--concurrency"));
971
+ if (!Number.isInteger(n) || n < 1 || n > 16)
972
+ throw new Error("--concurrency must be an integer 1\u201316");
973
+ f.concurrency = n;
974
+ break;
975
+ }
976
+ case "--theme":
977
+ f.themeId = need("--theme");
978
+ break;
979
+ case "--style":
980
+ f.styleId = need("--style");
981
+ break;
982
+ case "--color": {
983
+ const v = need("--color").toLowerCase();
984
+ if (v === "d" || v === "dark") f.colorScheme = "dark";
985
+ else if (v === "l" || v === "light") f.colorScheme = "light";
986
+ else throw new Error("--color must be dark or light");
987
+ break;
988
+ }
989
+ case "--tier": {
990
+ const v = need("--tier").toLowerCase();
991
+ if (v !== "solo" && v !== "team" && v !== "enterprise")
992
+ throw new Error("--tier must be solo, team, or enterprise");
993
+ f.tier = v;
994
+ break;
995
+ }
996
+ case "--feature": {
997
+ const raw = need("--feature");
998
+ const off = raw.startsWith("no-");
999
+ const key = off ? raw.slice(3) : raw;
1000
+ if (!FEATURE_KEYS.includes(key))
1001
+ throw new Error(`unknown --feature "${raw}" (one of: ${FEATURE_KEYS.join(", ")}, optionally no-<feature>)`);
1002
+ (f.features ??= {})[key] = !off;
1003
+ break;
1004
+ }
1005
+ case "--mobile":
1006
+ f.mobile = true;
1007
+ break;
1008
+ case "--archetype":
1009
+ f.archetype = need("--archetype");
1010
+ break;
1011
+ case "--vibe":
1012
+ f.styleVibe = need("--vibe");
1013
+ break;
1014
+ default:
1015
+ if (a.startsWith("-") && !KNOWN.has(a)) throw new Error(`unknown flag: ${a}`);
1016
+ f.ideaParts.push(a);
1017
+ }
1018
+ }
1019
+ return f;
1020
+ }
1021
+ function slugify(idea) {
1022
+ const s = idea.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").split("-").filter(Boolean).slice(0, 5).join("-");
1023
+ return s || "app";
1024
+ }
1025
+ function readBatch(file) {
1026
+ const path = resolve2(cwd2(), file);
1027
+ if (!existsSync(path)) throw new Error(`batch file not found: ${file}`);
1028
+ return readFileSync(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1029
+ }
1030
+ function destFor(outRoot, idea, buildId) {
1031
+ const base2 = join3(outRoot, slugify(idea));
1032
+ if (!existsSync(base2)) return base2;
1033
+ return `${base2}-${buildId.slice(-6)}`;
1034
+ }
1035
+ async function buildOne(engine, idea, flags, outRoot, onPhase) {
1036
+ const slug2 = slugify(idea);
1037
+ try {
1038
+ if (flags.boot) {
1039
+ const booted = await demoBootBuild(
1040
+ engine,
1041
+ { idea, buildModel: flags.buildModel, ...flags.themeId ? { themeId: flags.themeId } : {}, lean: true },
1042
+ onPhase
1043
+ );
1044
+ if (booted.ok && booted.liveUrl) {
1045
+ return {
1046
+ idea,
1047
+ slug: slug2,
1048
+ ok: true,
1049
+ verdict: booted.verdict,
1050
+ files: booted.files,
1051
+ liveUrl: booted.liveUrl,
1052
+ buildId: booted.buildId
1053
+ };
1054
+ }
1055
+ return { idea, slug: slug2, ok: false, error: booted.error ?? "boot failed" };
1056
+ }
1057
+ const appScope = flags.tier || flags.features ? { ...flags.tier ? { tier: flags.tier } : {}, ...flags.features ? { features: flags.features } : {} } : void 0;
1058
+ const result = await httpBuild(
1059
+ engine,
1060
+ {
1061
+ idea,
1062
+ buildModel: flags.buildModel,
1063
+ install: false,
1064
+ scope: "full",
1065
+ ...flags.themeId ? { themeId: flags.themeId } : {},
1066
+ ...flags.styleId ? { styleId: flags.styleId } : {},
1067
+ ...flags.colorScheme ? { colorScheme: flags.colorScheme } : {},
1068
+ ...appScope ? { appScope } : {},
1069
+ ...flags.mobile ? { platform: "mobile" } : {},
1070
+ ...flags.archetype ? { archetype: flags.archetype } : {},
1071
+ ...flags.styleVibe ? { styleVibe: flags.styleVibe } : {},
1072
+ lean: true
1073
+ },
1074
+ onPhase
1075
+ );
1076
+ if (!result.ok) return { idea, slug: slug2, ok: false, error: result.error ?? "build failed" };
1077
+ if (result.paywalled || !result.buildId) {
1078
+ return {
1079
+ idea,
1080
+ slug: slug2,
1081
+ ok: false,
1082
+ error: `preview only \u2014 subscribe to keep code: ${result.upgradeUrl ?? `${engine.replace(/\/$/, "")}/pricing`}`
1083
+ };
1084
+ }
1085
+ const dest = destFor(outRoot, idea, String(result.buildId));
1086
+ mkdirSync(dest, { recursive: true });
1087
+ const delivered = await deliverBuild(engine, String(result.buildId), dest, onPhase);
1088
+ if (!delivered.ok) {
1089
+ const why = delivered.paywalled ? `subscribe to keep code: ${delivered.upgradeUrl ?? "/pricing"}` : delivered.error ?? "delivery failed";
1090
+ return { idea, slug: slug2, ok: false, buildId: String(result.buildId), error: why };
1091
+ }
1092
+ isolateFromWorkspace(dest);
1093
+ if (flags.install) {
1094
+ const inst = await installDeps(dest, onPhase);
1095
+ if (!inst.ok) onPhase(`install failed \u2014 fix package.json then run pnpm install`);
1096
+ }
1097
+ return {
1098
+ idea,
1099
+ slug: slug2,
1100
+ ok: true,
1101
+ verdict: result.verdict,
1102
+ files: result.fileCount,
1103
+ dir: dest,
1104
+ buildId: String(result.buildId)
1105
+ };
1106
+ } catch (e) {
1107
+ return { idea, slug: slug2, ok: false, error: e.message };
1108
+ }
1109
+ }
1110
+ async function runPool(items, n, worker, onDone) {
1111
+ const results = new Array(items.length);
1112
+ let next = 0;
1113
+ async function lane() {
1114
+ for (; ; ) {
1115
+ const i = next++;
1116
+ if (i >= items.length) return;
1117
+ const r = await worker(items[i], i);
1118
+ results[i] = r;
1119
+ onDone(r, i);
1120
+ }
1121
+ }
1122
+ await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => lane()));
1123
+ return results;
1124
+ }
1125
+ async function runMakeSubcommand(rawArgs) {
1126
+ const verb = rawArgs[0];
1127
+ if (verb !== "make" && verb !== "factory") return null;
1128
+ let flags;
1129
+ try {
1130
+ flags = parseFlags(rawArgs.slice(1));
1131
+ } catch (e) {
1132
+ line(c.red(` ${e.message}`));
1133
+ line(c.dim(" wholestack make --help"));
1134
+ return 1;
1135
+ }
1136
+ if (flags.help) {
1137
+ line(HELP);
1138
+ return 0;
1139
+ }
1140
+ const chrome = flags.json ? (s = "") => stderr.write(s + "\n") : line;
1141
+ let ideas;
1142
+ try {
1143
+ if (flags.batch) {
1144
+ if (flags.ideaParts.length) throw new Error("pass an idea OR --batch, not both");
1145
+ ideas = readBatch(flags.batch);
1146
+ } else {
1147
+ const inline = flags.ideaParts.join(" ").trim();
1148
+ ideas = inline ? [inline] : [];
1149
+ }
1150
+ } catch (e) {
1151
+ chrome(c.red(` ${e.message}`));
1152
+ return 1;
1153
+ }
1154
+ if (!ideas.length) {
1155
+ chrome(c.red(" no idea given."));
1156
+ chrome(c.dim(' wholestack make "a CRM for dog groomers" \xB7 wholestack make --batch ideas.txt'));
1157
+ return 1;
1158
+ }
1159
+ if (!process.env.ZETA_API_KEY?.trim()) {
1160
+ chrome(c.red(" login required."));
1161
+ chrome(c.dim(" run `wholestack login` \u2014 the factory uses your membership and build credits."));
1162
+ return 1;
1163
+ }
1164
+ const engine = webBaseUrl();
1165
+ const outRoot = resolve2(cwd2(), flags.out);
1166
+ const conc = Math.min(flags.concurrency, ideas.length);
1167
+ chrome(
1168
+ c.dim(
1169
+ ` factory \xB7 ${ideas.length} app${ideas.length > 1 ? "s" : ""} \xB7 ${flags.boot ? "boot (live URL)" : `deliver \u2192 ${flags.out}/`} \xB7 ${flags.buildModel}${conc > 1 ? ` \xB7 ${conc}\xD7 parallel` : ""}`
1170
+ )
1171
+ );
1172
+ const render = (r, idx) => {
1173
+ if (flags.json) {
1174
+ stdout2.write(JSON.stringify(r) + "\n");
1175
+ return;
1176
+ }
1177
+ const tag = ideas.length > 1 ? c.dim(`[${idx + 1}/${ideas.length}] `) : "";
1178
+ if (r.ok) {
1179
+ const verdict = r.verdict === "SHIP" ? c.green("SHIP") : c.yellow(r.verdict ?? "?");
1180
+ const where = r.liveUrl ? c.cyan(r.liveUrl) : r.dir ? c.cyan(relOut(flags.out, outRoot, r.dir)) : "";
1181
+ const bits = [`${r.files ?? 0} files`, verdict, where].filter(Boolean);
1182
+ chrome(` ${tag}${c.green("\u2713")} ${r.slug} \xB7 ${bits.join(" \xB7 ")}`);
1183
+ } else {
1184
+ chrome(` ${tag}${c.red("\u2717")} ${r.slug} \xB7 ${r.error ?? "failed"}`);
1185
+ }
1186
+ };
1187
+ const results = await runPool(
1188
+ ideas,
1189
+ conc,
1190
+ (idea, i) => {
1191
+ if (conc === 1 && !flags.json) {
1192
+ const short = idea.length > 56 ? idea.slice(0, 55) + "\u2026" : idea;
1193
+ chrome(`${c.bold("\u2192")} ${short}`);
1194
+ }
1195
+ const tagged = (m) => flags.verbose ? chrome(c.dim(` \u21B3 ${conc > 1 ? `${slugify(idea)}: ` : ""}${m}`)) : void 0;
1196
+ return buildOne(engine, idea, flags, outRoot, tagged);
1197
+ },
1198
+ render
1199
+ );
1200
+ const failures = results.filter((r) => !r.ok).length;
1201
+ if (ideas.length > 1) {
1202
+ const made = ideas.length - failures;
1203
+ chrome(
1204
+ ` ${c.bold("factory done")} \xB7 ${made}/${ideas.length} shipped` + (failures ? c.red(` \xB7 ${failures} failed`) : "")
1205
+ );
1206
+ }
1207
+ return failures ? 1 : 0;
1208
+ }
1209
+ function relOut(outFlag, outRoot, dest) {
1210
+ const tail = dest.startsWith(outRoot) ? dest.slice(outRoot.length).replace(/^[\\/]/, "") : dest;
1211
+ return join3(outFlag, tail);
1212
+ }
1213
+
849
1214
  // src/learned-tools.ts
850
1215
  import { tool as tool3 } from "ai";
851
1216
  import { z as z3 } from "zod";
@@ -931,7 +1296,7 @@ function buildLearnedMemoryTools(deps) {
931
1296
  }
932
1297
 
933
1298
  // src/skills-cli.ts
934
- import { stdout as stdout2 } from "process";
1299
+ import { stdout as stdout3 } from "process";
935
1300
  async function runSkillsSubcommand(raw) {
936
1301
  if (raw[0] !== "skills") return null;
937
1302
  const rest = raw.slice(1);
@@ -951,7 +1316,7 @@ async function runSkillsSubcommand(raw) {
951
1316
  return 1;
952
1317
  }
953
1318
  if (rest.includes("--json")) {
954
- stdout2.write(
1319
+ stdout3.write(
955
1320
  JSON.stringify(
956
1321
  {
957
1322
  name: skill.name,
@@ -974,7 +1339,7 @@ async function runSkillsSubcommand(raw) {
974
1339
  }
975
1340
  const skills = registry.list();
976
1341
  if (rest.includes("--json")) {
977
- stdout2.write(
1342
+ stdout3.write(
978
1343
  JSON.stringify(
979
1344
  skills.map((s) => ({
980
1345
  name: s.name,
@@ -1005,7 +1370,7 @@ async function runSkillsSubcommand(raw) {
1005
1370
 
1006
1371
  // src/images.ts
1007
1372
  import { readFile as readFile3, stat as stat2 } from "fs/promises";
1008
- import { resolve as resolve2, extname, relative as relative2, isAbsolute as isAbsolute2, join as join3 } from "path";
1373
+ import { resolve as resolve3, extname, relative as relative2, isAbsolute as isAbsolute2, join as join4 } from "path";
1009
1374
  import { execFile } from "child_process";
1010
1375
  import { tmpdir as tmpdir2, platform as platform2 } from "os";
1011
1376
  var EXT_TO_MEDIA = {
@@ -1026,7 +1391,7 @@ function run(cmd, args) {
1026
1391
  });
1027
1392
  }
1028
1393
  async function grabClipboardImage() {
1029
- const out = join3(tmpdir2(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
1394
+ const out = join4(tmpdir2(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
1030
1395
  const os = platform2();
1031
1396
  if (os === "darwin") {
1032
1397
  const script = `set thePath to "${out}"
@@ -1060,7 +1425,7 @@ function imageTokens(message) {
1060
1425
  for (let m = re.exec(message); m; m = re.exec(message)) out.add(m[1]);
1061
1426
  return [...out];
1062
1427
  }
1063
- async function scanImages(message, cwd3) {
1428
+ async function scanImages(message, cwd4) {
1064
1429
  const tokens = imageTokens(message);
1065
1430
  const images = [];
1066
1431
  const skipped = [];
@@ -1089,8 +1454,8 @@ async function scanImages(message, cwd3) {
1089
1454
  }
1090
1455
  }
1091
1456
  for (const tok of tokens) {
1092
- const raw = tok.startsWith("~/") ? join3(process.env.HOME ?? "", tok.slice(2)) : tok;
1093
- const abs = isAbsolute2(raw) ? raw : resolve2(cwd3, raw);
1457
+ const raw = tok.startsWith("~/") ? join4(process.env.HOME ?? "", tok.slice(2)) : tok;
1458
+ const abs = isAbsolute2(raw) ? raw : resolve3(cwd4, raw);
1094
1459
  const mediaType = EXT_TO_MEDIA[extname(abs).toLowerCase()];
1095
1460
  if (!mediaType) continue;
1096
1461
  let bytes;
@@ -1101,7 +1466,7 @@ async function scanImages(message, cwd3) {
1101
1466
  } catch {
1102
1467
  continue;
1103
1468
  }
1104
- const label = isAbsolute2(raw) ? raw : relative2(cwd3, abs);
1469
+ const label = isAbsolute2(raw) ? raw : relative2(cwd4, abs);
1105
1470
  if (bytes > PER_IMAGE_BYTES || total + bytes > TOTAL_BYTES) {
1106
1471
  skipped.push(`${label} (${Math.round(bytes / 1024)}KB \u2014 over the size cap)`);
1107
1472
  continue;
@@ -1197,7 +1562,7 @@ ${body}`);
1197
1562
 
1198
1563
  // src/export.ts
1199
1564
  import { writeFileSync } from "fs";
1200
- import { resolve as resolve3 } from "path";
1565
+ import { resolve as resolve4 } from "path";
1201
1566
  function renderContent(content) {
1202
1567
  if (typeof content === "string") return content.trim();
1203
1568
  if (!Array.isArray(content)) return "";
@@ -1235,32 +1600,32 @@ ${text}`;
1235
1600
  ${body}
1236
1601
  `;
1237
1602
  }
1238
- function writeTranscript(messages, meta, file, cwd3) {
1239
- const abs = resolve3(cwd3, file);
1603
+ function writeTranscript(messages, meta, file, cwd4) {
1604
+ const abs = resolve4(cwd4, file);
1240
1605
  writeFileSync(abs, renderTranscript(messages, meta), "utf8");
1241
1606
  return abs;
1242
1607
  }
1243
1608
 
1244
1609
  // src/cli.ts
1245
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
1246
- import { join as join6 } from "path";
1610
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1611
+ import { join as join7 } from "path";
1247
1612
 
1248
1613
  // src/update-check.ts
1249
- import { existsSync, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1250
- import { join as join4 } from "path";
1614
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1615
+ import { join as join5 } from "path";
1251
1616
  var PKG = "wholestack";
1252
- var CACHE = join4(stateDir(), "update-check.json");
1617
+ var CACHE = join5(stateDir(), "update-check.json");
1253
1618
  var DAY_MS = 24 * 60 * 60 * 1e3;
1254
1619
  function readCache() {
1255
1620
  try {
1256
- return JSON.parse(readFileSync(CACHE, "utf8"));
1621
+ return JSON.parse(readFileSync2(CACHE, "utf8"));
1257
1622
  } catch {
1258
1623
  return { lastCheck: 0, latest: null };
1259
1624
  }
1260
1625
  }
1261
1626
  function writeCache(c2) {
1262
1627
  try {
1263
- mkdirSync(stateDir(), { recursive: true });
1628
+ mkdirSync2(stateDir(), { recursive: true });
1264
1629
  writeFileSync2(CACHE, JSON.stringify(c2));
1265
1630
  } catch {
1266
1631
  }
@@ -1276,7 +1641,7 @@ function isNewer(a, b) {
1276
1641
  return false;
1277
1642
  }
1278
1643
  async function checkForUpdate(current) {
1279
- if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync) return null;
1644
+ if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync2) return null;
1280
1645
  const cache = readCache();
1281
1646
  const fresh = Date.now() - cache.lastCheck < DAY_MS;
1282
1647
  let latest = cache.latest;
@@ -1302,20 +1667,20 @@ async function checkForUpdate(current) {
1302
1667
  }
1303
1668
 
1304
1669
  // src/access.ts
1305
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
1306
- import { join as join5 } from "path";
1307
- var CACHE2 = join5(stateDir(), "access.json");
1670
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
1671
+ import { join as join6 } from "path";
1672
+ var CACHE2 = join6(stateDir(), "access.json");
1308
1673
  var TTL_MS = 60 * 60 * 1e3;
1309
1674
  function readCache2() {
1310
1675
  try {
1311
- return JSON.parse(readFileSync2(CACHE2, "utf8"));
1676
+ return JSON.parse(readFileSync3(CACHE2, "utf8"));
1312
1677
  } catch {
1313
1678
  return null;
1314
1679
  }
1315
1680
  }
1316
1681
  function writeCache2(c2) {
1317
1682
  try {
1318
- mkdirSync2(stateDir(), { recursive: true });
1683
+ mkdirSync3(stateDir(), { recursive: true });
1319
1684
  writeFileSync3(CACHE2, JSON.stringify(c2));
1320
1685
  } catch {
1321
1686
  }
@@ -1405,27 +1770,27 @@ function showPaywall(loggedIn, webUrl) {
1405
1770
  }
1406
1771
  row("");
1407
1772
  row(c.dim("Free: make \xB7 edit \xB7 preview (watermarked)"));
1408
- row(c.dim("Pro $29/mo: ship unlimited \u2014 own the code"));
1773
+ row(c.dim("Starter $49/mo \xB7 Pro $99/mo \u2014 ship & own apps"));
1409
1774
  row(c.cyan(`${webUrl.replace(/\/$/, "")}/pricing`));
1410
1775
  line(" " + c.cyan("\u2570" + "\u2500".repeat(w) + "\u256F"));
1411
1776
  line();
1412
1777
  }
1413
1778
 
1414
1779
  // src/cli.ts
1415
- import { readFileSync as readFileSync3 } from "fs";
1780
+ import { readFileSync as readFileSync4 } from "fs";
1416
1781
  import { basename as basename2, dirname as dirname2 } from "path";
1417
1782
  import { fileURLToPath } from "url";
1418
1783
  var VERSION = (() => {
1419
1784
  try {
1420
1785
  const here = dirname2(fileURLToPath(import.meta.url));
1421
- return JSON.parse(readFileSync3(join6(here, "..", "package.json"), "utf8")).version;
1786
+ return JSON.parse(readFileSync4(join7(here, "..", "package.json"), "utf8")).version;
1422
1787
  } catch {
1423
1788
  return "0.0.0";
1424
1789
  }
1425
1790
  })();
1426
1791
  function gitBranch(dir) {
1427
1792
  try {
1428
- const head = readFileSync3(join6(dir, ".git", "HEAD"), "utf8").trim();
1793
+ const head = readFileSync4(join7(dir, ".git", "HEAD"), "utf8").trim();
1429
1794
  const m = head.match(/ref:\s*refs\/heads\/(.+)$/);
1430
1795
  return m ? m[1] : head.slice(0, 7);
1431
1796
  } catch {
@@ -1480,13 +1845,19 @@ function parse(raw) {
1480
1845
  a.prompt = rest.join(" ").trim();
1481
1846
  return a;
1482
1847
  }
1483
- var HELP = `${c.cyan("wholestack")} \u2014 conversational coding agent for the Wholestack engine ${c.dim("(alias: zeta)")}
1848
+ var HELP2 = `${c.cyan("wholestack")} \u2014 conversational coding agent for the Wholestack engine ${c.dim("(alias: zeta)")}
1484
1849
 
1485
1850
  ${c.bold("Usage")}
1486
1851
  wholestack start an interactive session
1487
1852
  wholestack "build me a todo app" run one prompt and exit
1488
1853
  wholestack -m zeta-g1-max "..." drive it with the most capable brain
1489
1854
 
1855
+ ${c.bold("Factory")} ${c.dim("(strict \xB7 no chat \xB7 ~0 client tokens \xB7 one idea in, app out)")}
1856
+ wholestack make "a barbershop booking app" make one app \u2192 ./out/<slug>
1857
+ wholestack make --batch ideas.txt one app per line of a file
1858
+ wholestack make "..." --boot free live URL instead of code
1859
+ ${c.dim("wholestack make --help for all flags (--out, --install, --style, --max, \u2026)")}
1860
+
1490
1861
  ${c.bold("Projects")} ${c.dim("(one spine across terminal, dashboard and the web IDE)")}
1491
1862
  wholestack projects list your platform projects
1492
1863
  wholestack pull <id> [dir] download a project's working tree
@@ -1510,6 +1881,13 @@ ${c.bold("Launch")} ${c.dim("(the studio's Launch HQ \u2014 go-to-market, in the
1510
1881
  wholestack business [buildId] one workspace + its readiness scorecard
1511
1882
  ${c.dim("add --json for machine-readable output")}
1512
1883
 
1884
+ ${c.bold("Operation Volition")} ${c.dim("(the autonomous operate-plane \u2014 a proof-gated crew RUNS the business)")}
1885
+ wholestack volition <name|projectId> [--live] hand a business to the crew (sandbox default)
1886
+ wholestack volition status [projectId] operator state + recent shifts
1887
+ wholestack volition actions [projectId] the action ledger (every move has a proof hash)
1888
+ wholestack volition run [projectId] run one crew cycle right now
1889
+ ${c.dim("sandbox simulates external actions; --live goes real ($99/mo add-on, gated server-side)")}
1890
+
1513
1891
  ${c.bold("Prove gate (web2)")} ${c.dim("(deterministic \xB7 fail-closed \xB7 blocking exit code)")}
1514
1892
  wholestack prove <dir> [--json] gate a checked-out repo (local lockstep mirror)
1515
1893
  wholestack prove --remote --github o/r [--ref b] hosted, HMAC-signed verdict via /api/v1/prove
@@ -1728,7 +2106,7 @@ async function runProveSubcommand(raw) {
1728
2106
  }
1729
2107
  const data = await resp.json().catch(() => null);
1730
2108
  if (wantsJson) {
1731
- stdout3.write(JSON.stringify(data) + "\n");
2109
+ stdout4.write(JSON.stringify(data) + "\n");
1732
2110
  return data?.verdict === "SHIP" ? 0 : 1;
1733
2111
  }
1734
2112
  if (resp.status === 401 || resp.status === 402 || resp.status === 503 || !data) {
@@ -1744,14 +2122,14 @@ async function runProveSubcommand(raw) {
1744
2122
  );
1745
2123
  return data.verdict === "SHIP" ? 0 : 1;
1746
2124
  }
1747
- const path = rest.find((a) => !a.startsWith("-")) ?? cwd2();
1748
- if (!existsSync2(path)) {
2125
+ const path = rest.find((a) => !a.startsWith("-")) ?? cwd3();
2126
+ if (!existsSync3(path)) {
1749
2127
  line(c.red(` path not found: ${path}`));
1750
2128
  return 1;
1751
2129
  }
1752
2130
  const result = proveLocalRepo(path);
1753
2131
  if (wantsJson) {
1754
- stdout3.write(JSON.stringify(result) + "\n");
2132
+ stdout4.write(JSON.stringify(result) + "\n");
1755
2133
  return result.verdict === "SHIP" ? 0 : 1;
1756
2134
  }
1757
2135
  line();
@@ -1796,7 +2174,7 @@ async function runSecuritySubcommand(raw) {
1796
2174
  line();
1797
2175
  line(c.cyan(` \u259F ShipGate ${verb}`) + c.dim(" \xB7 forge \xB7 slither \xB7 firewall \xB7 halmos"));
1798
2176
  line();
1799
- const r = await runProver(proverArgs, { cwd: cwd2(), inherit: true });
2177
+ const r = await runProver(proverArgs, { cwd: cwd3(), inherit: true });
1800
2178
  if (r.missing) {
1801
2179
  line(c.red(` ${r.out}`));
1802
2180
  return 1;
@@ -1808,12 +2186,12 @@ async function runSecuritySubcommand(raw) {
1808
2186
  function maybeYoloNotice() {
1809
2187
  try {
1810
2188
  const dir = stateDir();
1811
- const marker = join6(dir, "yolo-notice-seen");
1812
- if (existsSync2(marker)) return;
2189
+ const marker = join7(dir, "yolo-notice-seen");
2190
+ if (existsSync3(marker)) return;
1813
2191
  line(
1814
2192
  " " + c.yellow("\u26A1 yolo mode") + c.dim(" \u2014 actions run without confirmation. ") + c.cyan("/mode default") + c.dim(" to require approvals \xB7 /undo reverts.") + "\n"
1815
2193
  );
1816
- mkdirSync3(dir, { recursive: true });
2194
+ mkdirSync4(dir, { recursive: true });
1817
2195
  writeFileSync4(marker, (/* @__PURE__ */ new Date()).toISOString());
1818
2196
  } catch {
1819
2197
  }
@@ -1869,7 +2247,7 @@ async function runLogin(raw) {
1869
2247
  line(c.red(" usage: wholestack login --member <key> | --brain <key> | --build <key>"));
1870
2248
  return 1;
1871
2249
  }
1872
- const rl = createInterface({ input: stdin, output: stdout3 });
2250
+ const rl = createInterface({ input: stdin, output: stdout4 });
1873
2251
  if (!name) {
1874
2252
  const which = (await rl.question(c.cyan(" key for [member/brain/engine]: "))).trim().toLowerCase();
1875
2253
  name = which.startsWith("m") ? "ZETA_API_KEY" : "CEREBRAS_API_KEY";
@@ -2051,7 +2429,7 @@ async function runLaunchSubcommand(raw) {
2051
2429
  return 1;
2052
2430
  }
2053
2431
  if (wantsJson) {
2054
- stdout3.write(JSON.stringify(r2.plan, null, 2) + "\n");
2432
+ stdout4.write(JSON.stringify(r2.plan, null, 2) + "\n");
2055
2433
  return 0;
2056
2434
  }
2057
2435
  for (const ln of renderGtmPlanLines(r2.plan)) line(ln);
@@ -2067,7 +2445,7 @@ async function runLaunchSubcommand(raw) {
2067
2445
  return 1;
2068
2446
  }
2069
2447
  if (wantsJson) {
2070
- stdout3.write(JSON.stringify(r.kit, null, 2) + "\n");
2448
+ stdout4.write(JSON.stringify(r.kit, null, 2) + "\n");
2071
2449
  return 0;
2072
2450
  }
2073
2451
  for (const ln of renderLaunchKitLines(r.kit)) line(ln);
@@ -2093,7 +2471,7 @@ async function runHqSubcommand(raw) {
2093
2471
  line(c.red(` \u2717 ${r2.error ?? "no document produced"}`));
2094
2472
  return 1;
2095
2473
  }
2096
- stdout3.write(r2.markdown.trimEnd() + "\n");
2474
+ stdout4.write(r2.markdown.trimEnd() + "\n");
2097
2475
  return 0;
2098
2476
  }
2099
2477
  if (verb === "styles") {
@@ -2110,7 +2488,7 @@ async function runHqSubcommand(raw) {
2110
2488
  return 1;
2111
2489
  }
2112
2490
  if (wantsJson) {
2113
- stdout3.write(JSON.stringify(data, null, 2) + "\n");
2491
+ stdout4.write(JSON.stringify(data, null, 2) + "\n");
2114
2492
  return 0;
2115
2493
  }
2116
2494
  if (data.recommended) {
@@ -2138,7 +2516,7 @@ async function runHqSubcommand(raw) {
2138
2516
  return 1;
2139
2517
  }
2140
2518
  if (wantsJson) {
2141
- stdout3.write(JSON.stringify(r2.metrics, null, 2) + "\n");
2519
+ stdout4.write(JSON.stringify(r2.metrics, null, 2) + "\n");
2142
2520
  return 0;
2143
2521
  }
2144
2522
  for (const ln of renderMetricsLines(r2.metrics)) line(ln);
@@ -2166,7 +2544,7 @@ async function runHqSubcommand(raw) {
2166
2544
  return 1;
2167
2545
  }
2168
2546
  if (wantsJson) {
2169
- stdout3.write(JSON.stringify(r2.businesses ?? [], null, 2) + "\n");
2547
+ stdout4.write(JSON.stringify(r2.businesses ?? [], null, 2) + "\n");
2170
2548
  return 0;
2171
2549
  }
2172
2550
  const rows = r2.businesses ?? [];
@@ -2189,7 +2567,7 @@ async function runHqSubcommand(raw) {
2189
2567
  line(c.red(` \u2717 ${r.error}`));
2190
2568
  return 1;
2191
2569
  }
2192
- stdout3.write(JSON.stringify({ business: r.business, readiness: r.readiness }, null, 2) + "\n");
2570
+ stdout4.write(JSON.stringify({ business: r.business, readiness: r.readiness }, null, 2) + "\n");
2193
2571
  return 0;
2194
2572
  }
2195
2573
  var EMPTY_PLUGINS = {
@@ -2220,6 +2598,8 @@ async function main() {
2220
2598
  const rawArgs = argv.slice(2);
2221
2599
  const loggedIn = await runLogin(rawArgs);
2222
2600
  if (loggedIn !== null) exit(loggedIn);
2601
+ const made = await runMakeSubcommand(rawArgs);
2602
+ if (made !== null) exit(made);
2223
2603
  const gh = await runGithubSubcommand(rawArgs);
2224
2604
  if (gh !== null) exit(gh);
2225
2605
  const provePath = await runProveSubcommand(rawArgs);
@@ -2238,6 +2618,8 @@ async function main() {
2238
2618
  if (eyes !== null) exit(eyes);
2239
2619
  const ops = await runOpsSubcommand(rawArgs);
2240
2620
  if (ops !== null) exit(ops);
2621
+ const volition = await runVolitionSubcommand(rawArgs);
2622
+ if (volition !== null) exit(volition);
2241
2623
  const skillsSub = await runSkillsSubcommand(rawArgs);
2242
2624
  if (skillsSub !== null) exit(skillsSub);
2243
2625
  const args = parse(rawArgs);
@@ -2246,7 +2628,7 @@ async function main() {
2246
2628
  return;
2247
2629
  }
2248
2630
  if (args.help) {
2249
- line(HELP);
2631
+ line(HELP2);
2250
2632
  return;
2251
2633
  }
2252
2634
  {
@@ -2260,7 +2642,7 @@ async function main() {
2260
2642
  token ? "Your session expired." : "The agent is free \u2014 make, edit & preview unlimited. Sign in to start."
2261
2643
  )
2262
2644
  );
2263
- const rl = createInterface({ input: stdin, output: stdout3 });
2645
+ const rl = createInterface({ input: stdin, output: stdout4 });
2264
2646
  const ans = (await rl.question(
2265
2647
  " " + c.dim("Open the browser to sign in with Google? ") + c.dim("[Y/n] ")
2266
2648
  )).trim().toLowerCase();
@@ -2312,7 +2694,7 @@ async function main() {
2312
2694
  }
2313
2695
  const isTty = !!stdin.isTTY;
2314
2696
  const oneShot = args.prompt.length > 0;
2315
- const workdir = cwd2();
2697
+ const workdir = cwd3();
2316
2698
  const persistedMode = isPermissionMode(
2317
2699
  process.env.ZETA_PERMISSION_MODE
2318
2700
  ) ? process.env.ZETA_PERMISSION_MODE : void 0;