wholestack 0.5.4 → 0.5.6

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
@@ -12,91 +12,41 @@ import {
12
12
  buildTools,
13
13
  buildWebTools,
14
14
  c,
15
+ collectLocalFiles,
15
16
  isPermissionMode,
16
17
  killRunningApps,
17
18
  line,
19
+ listProjects,
18
20
  loadHookFiles,
19
21
  loadMcpTools,
20
22
  loadPlugins,
21
23
  loadProjectMemory,
24
+ loadStoredEnv,
22
25
  mergeHookSets,
23
26
  modelContextWindow,
24
27
  modelLabel,
28
+ openBuildInEditor,
29
+ pullWorkingTree,
30
+ pushWorkingTree,
31
+ readProjectLink,
25
32
  resolveModel,
26
33
  resolveModelKey,
27
34
  runProver,
35
+ saveKey,
36
+ stateDir,
28
37
  statusLine,
29
38
  supportsThinking,
30
39
  tasks,
31
40
  userBox,
32
- visionCapable
33
- } from "./chunk-FI67ER5S.js";
41
+ visionCapable,
42
+ writeProjectLink,
43
+ writeWorkingTree
44
+ } from "./chunk-K6MSDQJT.js";
34
45
 
35
46
  // src/cli.ts
36
47
  import { createInterface } from "readline/promises";
37
48
  import { stdin, stdout, argv, exit, cwd } from "process";
38
49
 
39
- // src/config.ts
40
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
41
- import { homedir } from "os";
42
- import { join } from "path";
43
- var CONFIG_DIR = join(homedir(), ".zeta-g");
44
- var CONFIG_PATH = join(CONFIG_DIR, "config.json");
45
- function parseDotenv(text) {
46
- const out = {};
47
- for (const raw of text.split("\n")) {
48
- const l = raw.trim();
49
- if (!l || l.startsWith("#")) continue;
50
- const eq = l.indexOf("=");
51
- if (eq < 0) continue;
52
- const k = l.slice(0, eq).trim();
53
- let v = l.slice(eq + 1).trim();
54
- if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
55
- v = v.slice(1, -1);
56
- }
57
- if (k) out[k] = v;
58
- }
59
- return out;
60
- }
61
- function fill(src) {
62
- for (const [k, v] of Object.entries(src)) {
63
- if (v && process.env[k] === void 0) process.env[k] = v;
64
- }
65
- }
66
- function loadStoredEnv() {
67
- const localEnv = join(process.cwd(), ".env");
68
- if (existsSync(localEnv)) {
69
- try {
70
- fill(parseDotenv(readFileSync(localEnv, "utf8")));
71
- } catch {
72
- }
73
- }
74
- if (existsSync(CONFIG_PATH)) {
75
- try {
76
- const json = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
77
- const flat = {};
78
- for (const [k, v] of Object.entries(json)) if (typeof v === "string") flat[k] = v;
79
- fill(flat);
80
- } catch {
81
- }
82
- }
83
- }
84
- function saveKey(name, value) {
85
- mkdirSync(CONFIG_DIR, { recursive: true });
86
- let current = {};
87
- if (existsSync(CONFIG_PATH)) {
88
- try {
89
- current = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
90
- } catch {
91
- current = {};
92
- }
93
- }
94
- current[name] = value;
95
- writeFileSync(CONFIG_PATH, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
96
- chmodSync(CONFIG_PATH, 384);
97
- return CONFIG_PATH;
98
- }
99
-
100
50
  // src/cli-login.ts
101
51
  import { createServer } from "http";
102
52
  import { spawn } from "child_process";
@@ -284,7 +234,7 @@ var CheckpointStore = class {
284
234
 
285
235
  // src/mentions.ts
286
236
  import { readFile as readFile2, stat, readdir } from "fs/promises";
287
- import { resolve, relative, join as join2, isAbsolute } from "path";
237
+ import { resolve, relative, join, isAbsolute } from "path";
288
238
  import fg from "fast-glob";
289
239
  var IGNORE = [
290
240
  "**/node_modules/**",
@@ -341,7 +291,7 @@ async function renderTree(absDir, relDir) {
341
291
  }
342
292
  count++;
343
293
  lines.push(`${prefix}${e.name}${e.isDirectory() ? "/" : ""}`);
344
- if (e.isDirectory()) await walk(join2(dir, e.name), prefix + " ", depth + 1);
294
+ if (e.isDirectory()) await walk(join(dir, e.name), prefix + " ", depth + 1);
345
295
  }
346
296
  }
347
297
  await walk(absDir, "", 0);
@@ -357,7 +307,7 @@ async function expandMentions(message, cwd2, tokenBudget = DEFAULT_TOKEN_BUDGET)
357
307
  let spent = 0;
358
308
  for (const mention of mentions) {
359
309
  if (spent >= tokenBudget) break;
360
- const raw = mention.startsWith("~/") ? join2(process.env.HOME ?? "", mention.slice(2)) : mention;
310
+ const raw = mention.startsWith("~/") ? join(process.env.HOME ?? "", mention.slice(2)) : mention;
361
311
  const direct = isAbsolute(raw) ? raw : resolve(cwd2, raw);
362
312
  let isDir = false;
363
313
  try {
@@ -484,7 +434,7 @@ function buildSubagentTool(deps) {
484
434
 
485
435
  // src/images.ts
486
436
  import { readFile as readFile3, stat as stat2 } from "fs/promises";
487
- import { resolve as resolve2, extname, relative as relative2, isAbsolute as isAbsolute2, join as join3 } from "path";
437
+ import { resolve as resolve2, extname, relative as relative2, isAbsolute as isAbsolute2, join as join2 } from "path";
488
438
  import { execFile } from "child_process";
489
439
  import { tmpdir, platform as platform2 } from "os";
490
440
  var EXT_TO_MEDIA = {
@@ -505,7 +455,7 @@ function run(cmd, args) {
505
455
  });
506
456
  }
507
457
  async function grabClipboardImage() {
508
- const out = join3(tmpdir(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
458
+ const out = join2(tmpdir(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
509
459
  const os = platform2();
510
460
  if (os === "darwin") {
511
461
  const script = `set thePath to "${out}"
@@ -568,7 +518,7 @@ async function scanImages(message, cwd2) {
568
518
  }
569
519
  }
570
520
  for (const tok of tokens) {
571
- const raw = tok.startsWith("~/") ? join3(process.env.HOME ?? "", tok.slice(2)) : tok;
521
+ const raw = tok.startsWith("~/") ? join2(process.env.HOME ?? "", tok.slice(2)) : tok;
572
522
  const abs = isAbsolute2(raw) ? raw : resolve2(cwd2, raw);
573
523
  const mediaType = EXT_TO_MEDIA[extname(abs).toLowerCase()];
574
524
  if (!mediaType) continue;
@@ -624,7 +574,7 @@ async function fetchReadable(url) {
624
574
  const res = await fetch(url, {
625
575
  signal: ctrl.signal,
626
576
  redirect: "follow",
627
- headers: { "user-agent": "zeta-g-cli/0.4 (+web-context)" }
577
+ headers: { "user-agent": "wholestack-cli (+web-context)" }
628
578
  });
629
579
  if (!res.ok) return null;
630
580
  const ctype = res.headers.get("content-type") ?? "";
@@ -675,7 +625,7 @@ ${body}`);
675
625
  }
676
626
 
677
627
  // src/export.ts
678
- import { writeFileSync as writeFileSync2 } from "fs";
628
+ import { writeFileSync } from "fs";
679
629
  import { resolve as resolve3 } from "path";
680
630
  function renderContent(content) {
681
631
  if (typeof content === "string") return content.trim();
@@ -692,7 +642,7 @@ function renderContent(content) {
692
642
  }
693
643
  function renderTranscript(messages, meta) {
694
644
  const head = [
695
- `# zeta-g transcript`,
645
+ `# wholestack transcript`,
696
646
  ``,
697
647
  `- model: ${meta.model}`,
698
648
  meta.startedAt ? `- started: ${meta.startedAt}` : "",
@@ -716,33 +666,31 @@ ${body}
716
666
  }
717
667
  function writeTranscript(messages, meta, file, cwd2) {
718
668
  const abs = resolve3(cwd2, file);
719
- writeFileSync2(abs, renderTranscript(messages, meta), "utf8");
669
+ writeFileSync(abs, renderTranscript(messages, meta), "utf8");
720
670
  return abs;
721
671
  }
722
672
 
723
673
  // src/cli.ts
724
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
725
- import { homedir as homedir4 } from "os";
726
- import { join as join6 } from "path";
674
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
675
+ import { join as join5 } from "path";
727
676
 
728
677
  // src/update-check.ts
729
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
730
- import { homedir as homedir2 } from "os";
731
- import { join as join4 } from "path";
678
+ import { existsSync, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
679
+ import { join as join3 } from "path";
732
680
  var PKG = "wholestack";
733
- var CACHE = join4(homedir2(), ".zeta-g", "update-check.json");
681
+ var CACHE = join3(stateDir(), "update-check.json");
734
682
  var DAY_MS = 24 * 60 * 60 * 1e3;
735
683
  function readCache() {
736
684
  try {
737
- return JSON.parse(readFileSync2(CACHE, "utf8"));
685
+ return JSON.parse(readFileSync(CACHE, "utf8"));
738
686
  } catch {
739
687
  return { lastCheck: 0, latest: null };
740
688
  }
741
689
  }
742
690
  function writeCache(c2) {
743
691
  try {
744
- mkdirSync2(join4(homedir2(), ".zeta-g"), { recursive: true });
745
- writeFileSync3(CACHE, JSON.stringify(c2));
692
+ mkdirSync(stateDir(), { recursive: true });
693
+ writeFileSync2(CACHE, JSON.stringify(c2));
746
694
  } catch {
747
695
  }
748
696
  }
@@ -757,7 +705,7 @@ function isNewer(a, b) {
757
705
  return false;
758
706
  }
759
707
  async function checkForUpdate(current) {
760
- if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync2) return null;
708
+ if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync) return null;
761
709
  const cache = readCache();
762
710
  const fresh = Date.now() - cache.lastCheck < DAY_MS;
763
711
  let latest = cache.latest;
@@ -783,22 +731,21 @@ async function checkForUpdate(current) {
783
731
  }
784
732
 
785
733
  // src/access.ts
786
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync4 } from "fs";
787
- import { homedir as homedir3 } from "os";
788
- import { join as join5 } from "path";
789
- var CACHE2 = join5(homedir3(), ".zeta-g", "access.json");
734
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
735
+ import { join as join4 } from "path";
736
+ var CACHE2 = join4(stateDir(), "access.json");
790
737
  var TTL_MS = 60 * 60 * 1e3;
791
738
  function readCache2() {
792
739
  try {
793
- return JSON.parse(readFileSync3(CACHE2, "utf8"));
740
+ return JSON.parse(readFileSync2(CACHE2, "utf8"));
794
741
  } catch {
795
742
  return null;
796
743
  }
797
744
  }
798
745
  function writeCache2(c2) {
799
746
  try {
800
- mkdirSync3(join5(homedir3(), ".zeta-g"), { recursive: true });
801
- writeFileSync4(CACHE2, JSON.stringify(c2));
747
+ mkdirSync2(stateDir(), { recursive: true });
748
+ writeFileSync3(CACHE2, JSON.stringify(c2));
802
749
  } catch {
803
750
  }
804
751
  }
@@ -858,20 +805,20 @@ function showPaywall(loggedIn, webUrl) {
858
805
  }
859
806
 
860
807
  // src/cli.ts
861
- import { readFileSync as readFileSync4 } from "fs";
808
+ import { readFileSync as readFileSync3 } from "fs";
862
809
  import { basename, dirname as dirname2 } from "path";
863
810
  import { fileURLToPath } from "url";
864
811
  var VERSION = (() => {
865
812
  try {
866
813
  const here = dirname2(fileURLToPath(import.meta.url));
867
- return JSON.parse(readFileSync4(join6(here, "..", "package.json"), "utf8")).version;
814
+ return JSON.parse(readFileSync3(join5(here, "..", "package.json"), "utf8")).version;
868
815
  } catch {
869
816
  return "0.0.0";
870
817
  }
871
818
  })();
872
819
  function gitBranch(dir) {
873
820
  try {
874
- const head = readFileSync4(join6(dir, ".git", "HEAD"), "utf8").trim();
821
+ const head = readFileSync3(join5(dir, ".git", "HEAD"), "utf8").trim();
875
822
  const m = head.match(/ref:\s*refs\/heads\/(.+)$/);
876
823
  return m ? m[1] : head.slice(0, 7);
877
824
  } catch {
@@ -925,19 +872,25 @@ function parse(raw) {
925
872
  a.prompt = rest.join(" ").trim();
926
873
  return a;
927
874
  }
928
- var HELP = `${c.cyan("zeta-g")} \u2014 conversational coding agent for the ZETA engine
875
+ var HELP = `${c.cyan("wholestack")} \u2014 conversational coding agent for the Wholestack engine ${c.dim("(alias: zeta)")}
929
876
 
930
877
  ${c.bold("Usage")}
931
- zeta-g start an interactive session
932
- zeta-g "build me a todo app" run one prompt and exit
933
- zeta-g -m zeta-g1-max "..." drive it with the most capable brain
878
+ wholestack start an interactive session
879
+ wholestack "build me a todo app" run one prompt and exit
880
+ wholestack -m zeta-g1-max "..." drive it with the most capable brain
881
+
882
+ ${c.bold("Projects")} ${c.dim("(one spine across terminal, dashboard and the web IDE)")}
883
+ wholestack projects list your platform projects
884
+ wholestack pull <id> [dir] download a project's working tree
885
+ wholestack push [dir] sync local edits back to the web IDE
886
+ wholestack open [buildId] open a generated build in the web IDE
934
887
 
935
888
  ${c.bold("Web3 security")} ${c.dim("(forge \xB7 slither \xB7 firewall \xB7 halmos \xB7 signed cert)")}
936
- zeta-g audit <dir> <Contract> full auto-detect security sweep
937
- zeta-g prove <dir> <Contract> --property <k> prove one invariant
938
- zeta-g verify <dir> <Contract> --cert c.json passthrough + signed certificate
889
+ wholestack audit <dir> <Contract> full auto-detect security sweep
890
+ wholestack prove <dir> <Contract> --property <k> prove one invariant
891
+ wholestack verify <dir> <Contract> --cert c.json passthrough + signed certificate
939
892
  ${c.dim("kinds: reentrancy-safety, access-control, conservation, no-value-extraction, \u2026")}
940
- zeta-g --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
893
+ wholestack --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
941
894
 
942
895
  ${c.bold("Session")}
943
896
  -m, --model <key> zeta-g1-lite | zeta-g1 | zeta-g1-max | vision
@@ -957,15 +910,15 @@ ${c.bold("Extensibility")}
957
910
  --no-mcp skip MCP servers --no-plugins skip plugins
958
911
 
959
912
  ${c.bold("Build engine")}
960
- --zeta-url <u> ZETA engine base URL (default http://localhost:3000)
913
+ --zeta-url <u> build engine base URL (default https://wholestack.ai)
961
914
  --local build via the in-repo worker (no server needed)
962
- --http build via the running ZETA engine only
915
+ --http build via the running engine only
963
916
 
964
- ${c.bold("Auth")} ${c.dim("(set once, then just run zeta-g)")}
965
- zeta-g login interactive \u2014 pick member/brain/engine, paste key
966
- zeta-g login --member <key> membership key (keep + deploy generated apps)
967
- zeta-g login --brain <key> brain key (powers the Zeta-G1.0 tiers)
968
- zeta-g login --build <key> ZETA build-engine key (for /build)
917
+ ${c.bold("Auth")} ${c.dim("(set once, then just run wholestack)")}
918
+ wholestack login browser sign-in (or pick member/brain/engine, paste key)
919
+ wholestack login --member <key> membership key (keep + deploy generated apps)
920
+ wholestack login --brain <key> brain key (powers the Zeta-G1.0 tiers)
921
+ wholestack login --build <key> build-engine key (for /build)
969
922
 
970
923
  ${c.bold("In a session")} ${c.dim("(type /help for the full list)")}
971
924
  /model /cost /tools /undo /redo /checkpoints /compact /resume /memory /mcp /mode /think /init /doctor /clear /exit
@@ -1000,14 +953,14 @@ async function runSecuritySubcommand(raw) {
1000
953
  }
1001
954
  function maybeYoloNotice() {
1002
955
  try {
1003
- const dir = join6(homedir4(), ".zeta-g");
1004
- const marker = join6(dir, "yolo-notice-seen");
1005
- if (existsSync3(marker)) return;
956
+ const dir = stateDir();
957
+ const marker = join5(dir, "yolo-notice-seen");
958
+ if (existsSync2(marker)) return;
1006
959
  line(
1007
960
  " " + 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"
1008
961
  );
1009
- mkdirSync4(dir, { recursive: true });
1010
- writeFileSync5(marker, (/* @__PURE__ */ new Date()).toISOString());
962
+ mkdirSync3(dir, { recursive: true });
963
+ writeFileSync4(marker, (/* @__PURE__ */ new Date()).toISOString());
1011
964
  } catch {
1012
965
  }
1013
966
  }
@@ -1062,7 +1015,7 @@ async function runLogin(raw) {
1062
1015
  }
1063
1016
  if (!name || !value) {
1064
1017
  if (!stdin.isTTY) {
1065
- line(c.red(" usage: zeta-g login --member <key> | --brain <key> | --build <key>"));
1018
+ line(c.red(" usage: wholestack login --member <key> | --brain <key> | --build <key>"));
1066
1019
  return 1;
1067
1020
  }
1068
1021
  const rl = createInterface({ input: stdin, output: stdout });
@@ -1081,7 +1034,88 @@ async function runLogin(raw) {
1081
1034
  const path = saveKey(name, value);
1082
1035
  if (name === "ZETA_API_KEY") clearAccessCache();
1083
1036
  line(c.green(` \u2713 saved key for the ${what}`) + c.dim(` \u2192 ${path} (chmod 600)`));
1084
- line(c.dim(" now just run `zeta-g`."));
1037
+ line(c.dim(" now just run `wholestack`."));
1038
+ return 0;
1039
+ }
1040
+ async function runProjectSubcommand(raw) {
1041
+ const verb = raw[0];
1042
+ if (verb !== "projects" && verb !== "pull" && verb !== "push" && verb !== "open") return null;
1043
+ const base = webBaseUrl();
1044
+ if (verb === "projects") {
1045
+ const r2 = await listProjects(base);
1046
+ if (!r2.ok) {
1047
+ line(c.red(` ${r2.error}`));
1048
+ return 1;
1049
+ }
1050
+ if (!r2.projects?.length) {
1051
+ line(c.dim(' no projects yet \u2014 build something (`wholestack "an idea"`) or create one in the web IDE.'));
1052
+ return 0;
1053
+ }
1054
+ line();
1055
+ for (const p of r2.projects) {
1056
+ const when = p.updatedAt ? new Date(p.updatedAt).toISOString().slice(0, 10) : "";
1057
+ line(" " + c.cyan(p.id) + " " + c.bold(p.name) + (when ? c.dim(` \xB7 ${when}`) : ""));
1058
+ if (p.description) line(" " + c.dim(` ${p.description}`));
1059
+ }
1060
+ line();
1061
+ line(c.dim(" pull one with ") + c.cyan("wholestack pull <id>") + c.dim(" \xB7 open in the IDE at ") + c.cyan(`${base}/ide/studio?projectId=<id>`));
1062
+ return 0;
1063
+ }
1064
+ if (verb === "pull") {
1065
+ const projectId = raw[1];
1066
+ if (!projectId) {
1067
+ line(c.red(" usage: wholestack pull <projectId> [dir]"));
1068
+ return 1;
1069
+ }
1070
+ const r2 = await pullWorkingTree(base, projectId);
1071
+ if (!r2.ok) {
1072
+ line(c.red(` ${r2.error}`));
1073
+ return 1;
1074
+ }
1075
+ const dest = raw[2] ?? (r2.name ? r2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") : projectId);
1076
+ const written = writeWorkingTree(dest, r2.files ?? []);
1077
+ writeProjectLink(dest, { projectId });
1078
+ line(c.green(` \u2713 pulled ${written} files`) + c.dim(` \u2192 ${dest}/`));
1079
+ line(c.dim(" edit locally, then ") + c.cyan(`wholestack push ${dest === "." ? "" : dest}`).trimEnd() + c.dim(" to sync back to the web IDE."));
1080
+ return 0;
1081
+ }
1082
+ if (verb === "push") {
1083
+ const dir = raw[1] ?? ".";
1084
+ const link = readProjectLink(dir);
1085
+ const flagIdx = raw.indexOf("--project");
1086
+ const projectId = (flagIdx >= 0 ? raw[flagIdx + 1] : void 0) ?? link?.projectId;
1087
+ if (!projectId) {
1088
+ line(c.red(" no linked project \u2014 pull first, or pass --project <id>."));
1089
+ return 1;
1090
+ }
1091
+ const { files, skipped } = await collectLocalFiles(dir);
1092
+ if (!files.length) {
1093
+ line(c.red(" nothing to push (no readable text files found)."));
1094
+ return 1;
1095
+ }
1096
+ const r2 = await pushWorkingTree(base, projectId, files);
1097
+ if (!r2.ok) {
1098
+ line(c.red(` ${r2.error}`));
1099
+ return 1;
1100
+ }
1101
+ writeProjectLink(dir, { projectId });
1102
+ line(c.green(` \u2713 pushed ${files.length} files`) + c.dim(skipped ? ` (${skipped} skipped: binary/oversize)` : ""));
1103
+ line(c.dim(" open it: ") + c.cyan(`${base}/ide/studio?projectId=${projectId}`));
1104
+ return 0;
1105
+ }
1106
+ const buildId = raw[1] ?? readProjectLink(".")?.buildId;
1107
+ if (!buildId) {
1108
+ line(c.red(" usage: wholestack open <buildId> (or run it inside a delivered app dir)"));
1109
+ return 1;
1110
+ }
1111
+ const r = await openBuildInEditor(base, buildId);
1112
+ if (!r.ok) {
1113
+ line(c.red(` ${r.error}`));
1114
+ return 1;
1115
+ }
1116
+ if (r.projectId) writeProjectLink(".", { buildId, projectId: r.projectId });
1117
+ line(c.green(" \u2713 project ready in the web IDE"));
1118
+ line(c.dim(" ") + c.cyan(`${base}${r.editorUrl ?? `/ide/studio?projectId=${r.projectId}`}`));
1085
1119
  return 0;
1086
1120
  }
1087
1121
  var EMPTY_PLUGINS = {
@@ -1112,6 +1146,8 @@ async function main() {
1112
1146
  if (loggedIn !== null) exit(loggedIn);
1113
1147
  const sub = await runSecuritySubcommand(rawArgs);
1114
1148
  if (sub !== null) exit(sub);
1149
+ const proj = await runProjectSubcommand(rawArgs);
1150
+ if (proj !== null) exit(proj);
1115
1151
  const args = parse(rawArgs);
1116
1152
  if (args.version) {
1117
1153
  line(`wholestack ${VERSION}`);
@@ -1303,7 +1339,7 @@ ${m.context}` : args.prompt;
1303
1339
  await ic.runInterruptible((sig) => agent.send(text, sig, images));
1304
1340
  statusLine({
1305
1341
  model: modelLabel(modelKey),
1306
- tokens: agent.usage.totalTokens,
1342
+ tokens: agent.usage.outputTokens,
1307
1343
  tps: agent.lastTps,
1308
1344
  elapsedMs: Date.now() - t0,
1309
1345
  cost: agent.usage.totalCost,
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ declare class Permissions {
70
70
  private readonly confirm?;
71
71
  /**
72
72
  * Persist a chosen mode so it survives across sessions. Wired by the CLI to
73
- * write `~/.zeta-g/config.json`. Optional — when absent, choices are
73
+ * write `~/.wholestack/config.json`. Optional — when absent, choices are
74
74
  * session-only (e.g. piped / non-TTY runs).
75
75
  */
76
76
  private readonly persistMode?;
@@ -79,7 +79,7 @@ declare class Permissions {
79
79
  constructor(mode: PermissionMode, confirm?: ConfirmFn | undefined,
80
80
  /**
81
81
  * Persist a chosen mode so it survives across sessions. Wired by the CLI to
82
- * write `~/.zeta-g/config.json`. Optional — when absent, choices are
82
+ * write `~/.wholestack/config.json`. Optional — when absent, choices are
83
83
  * session-only (e.g. piped / non-TTY runs).
84
84
  */
85
85
  persistMode?: ((mode: PermissionMode) => void) | undefined);
@@ -194,6 +194,7 @@ declare function buildTools(ctx: ToolContext): {
194
194
  scope: "full" | "static" | "component" | "page";
195
195
  buildModel: "zeta-g1" | "zeta-g1-max";
196
196
  keep: boolean;
197
+ themeId?: "slate-minimal" | "clean-inter" | "friendly-nunito" | "geometric-jakarta" | "techno-mono" | undefined;
197
198
  }, BuildResult | {
198
199
  ok: boolean;
199
200
  liveUrl: string | undefined;
@@ -566,7 +567,7 @@ declare class HookRunner {
566
567
  }
567
568
  /** Merge hook sets (later wins on nothing — they all concatenate). */
568
569
  declare function mergeHookSets(...sets: HookSet[]): HookSet;
569
- /** Load hooks.json from the global config dir and the project's .zeta-g/. */
570
+ /** Load hooks.json from the global config dir and the project's .wholestack/ (or legacy .zeta-g/). */
570
571
  declare function loadHookFiles(cwd: string): HookSet;
571
572
  /**
572
573
  * Wrap every tool's execute so PreToolUse hooks can block it and PostToolUse
@@ -635,6 +636,8 @@ declare class UsageMeter {
635
636
  private cost;
636
637
  add(modelId: string, u: TokenUsage): void;
637
638
  get totalTokens(): number;
639
+ /** Output tokens only — the "what did it actually generate" number. */
640
+ get outputTokens(): number;
638
641
  get totalCost(): number;
639
642
  snapshot(): {
640
643
  input: number;
@@ -905,7 +908,7 @@ declare function loadMcpTools(opts: LoadMcpOptions): Promise<LoadedMcp>;
905
908
 
906
909
  /**
907
910
  * Slash commands — the in-session control surface. An extensible registry, not
908
- * a hardcoded if/else: built-ins live here, plugins and ~/.zeta-g/commands/*.md
911
+ * a hardcoded if/else: built-ins live here, plugins and ~/.wholestack/commands/*.md
909
912
  * register more. A command returns a CommandResult the REPL acts on, so the
910
913
  * registry stays decoupled from the agent it drives.
911
914
  */
@@ -971,7 +974,7 @@ declare class CommandRegistry {
971
974
  get(name: string): SlashCommand | undefined;
972
975
  list(): SlashCommand[];
973
976
  names(): string[];
974
- /** Load *.md commands from ~/.zeta-g/commands and <cwd>/.zeta-g/commands. */
977
+ /** Load *.md commands from ~/.wholestack/commands and <cwd>/.wholestack/commands (or legacy .zeta-g/). */
975
978
  loadCustom(cwd: string): void;
976
979
  dispatch(input: string, base: Omit<CommandContext, "args" | "commands">): Promise<CommandResult | null>;
977
980
  }
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  resolveModelKey,
50
50
  severityRank,
51
51
  supportsThinking
52
- } from "./chunk-FI67ER5S.js";
52
+ } from "./chunk-K6MSDQJT.js";
53
53
 
54
54
  // src/prompts/template.ts
55
55
  var PLACEHOLDER = /\{\{([A-Za-z0-9_.]+)\}\}/g;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wholestack",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Wholestack — a pro-grade conversational terminal agent for the Wholestack codegen engine. Talk to it in plain language: it writes ISL, generates full-stack or Solidity apps, and proves them with ShipGate. Browser login, membership-gated builds, slash commands, sessions, plan mode, diffs, MCP, plugins.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",