wholestack 0.5.4 → 0.5.5

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
@@ -19,84 +19,26 @@ import {
19
19
  loadMcpTools,
20
20
  loadPlugins,
21
21
  loadProjectMemory,
22
+ loadStoredEnv,
22
23
  mergeHookSets,
23
24
  modelContextWindow,
24
25
  modelLabel,
25
26
  resolveModel,
26
27
  resolveModelKey,
27
28
  runProver,
29
+ saveKey,
30
+ stateDir,
28
31
  statusLine,
29
32
  supportsThinking,
30
33
  tasks,
31
34
  userBox,
32
35
  visionCapable
33
- } from "./chunk-FI67ER5S.js";
36
+ } from "./chunk-7NZ77Q7Q.js";
34
37
 
35
38
  // src/cli.ts
36
39
  import { createInterface } from "readline/promises";
37
40
  import { stdin, stdout, argv, exit, cwd } from "process";
38
41
 
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
42
  // src/cli-login.ts
101
43
  import { createServer } from "http";
102
44
  import { spawn } from "child_process";
@@ -284,7 +226,7 @@ var CheckpointStore = class {
284
226
 
285
227
  // src/mentions.ts
286
228
  import { readFile as readFile2, stat, readdir } from "fs/promises";
287
- import { resolve, relative, join as join2, isAbsolute } from "path";
229
+ import { resolve, relative, join, isAbsolute } from "path";
288
230
  import fg from "fast-glob";
289
231
  var IGNORE = [
290
232
  "**/node_modules/**",
@@ -341,7 +283,7 @@ async function renderTree(absDir, relDir) {
341
283
  }
342
284
  count++;
343
285
  lines.push(`${prefix}${e.name}${e.isDirectory() ? "/" : ""}`);
344
- if (e.isDirectory()) await walk(join2(dir, e.name), prefix + " ", depth + 1);
286
+ if (e.isDirectory()) await walk(join(dir, e.name), prefix + " ", depth + 1);
345
287
  }
346
288
  }
347
289
  await walk(absDir, "", 0);
@@ -357,7 +299,7 @@ async function expandMentions(message, cwd2, tokenBudget = DEFAULT_TOKEN_BUDGET)
357
299
  let spent = 0;
358
300
  for (const mention of mentions) {
359
301
  if (spent >= tokenBudget) break;
360
- const raw = mention.startsWith("~/") ? join2(process.env.HOME ?? "", mention.slice(2)) : mention;
302
+ const raw = mention.startsWith("~/") ? join(process.env.HOME ?? "", mention.slice(2)) : mention;
361
303
  const direct = isAbsolute(raw) ? raw : resolve(cwd2, raw);
362
304
  let isDir = false;
363
305
  try {
@@ -484,7 +426,7 @@ function buildSubagentTool(deps) {
484
426
 
485
427
  // src/images.ts
486
428
  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";
429
+ import { resolve as resolve2, extname, relative as relative2, isAbsolute as isAbsolute2, join as join2 } from "path";
488
430
  import { execFile } from "child_process";
489
431
  import { tmpdir, platform as platform2 } from "os";
490
432
  var EXT_TO_MEDIA = {
@@ -505,7 +447,7 @@ function run(cmd, args) {
505
447
  });
506
448
  }
507
449
  async function grabClipboardImage() {
508
- const out = join3(tmpdir(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
450
+ const out = join2(tmpdir(), `zeta-clip-${process.pid}-${process.hrtime.bigint()}.png`);
509
451
  const os = platform2();
510
452
  if (os === "darwin") {
511
453
  const script = `set thePath to "${out}"
@@ -568,7 +510,7 @@ async function scanImages(message, cwd2) {
568
510
  }
569
511
  }
570
512
  for (const tok of tokens) {
571
- const raw = tok.startsWith("~/") ? join3(process.env.HOME ?? "", tok.slice(2)) : tok;
513
+ const raw = tok.startsWith("~/") ? join2(process.env.HOME ?? "", tok.slice(2)) : tok;
572
514
  const abs = isAbsolute2(raw) ? raw : resolve2(cwd2, raw);
573
515
  const mediaType = EXT_TO_MEDIA[extname(abs).toLowerCase()];
574
516
  if (!mediaType) continue;
@@ -624,7 +566,7 @@ async function fetchReadable(url) {
624
566
  const res = await fetch(url, {
625
567
  signal: ctrl.signal,
626
568
  redirect: "follow",
627
- headers: { "user-agent": "zeta-g-cli/0.4 (+web-context)" }
569
+ headers: { "user-agent": "wholestack-cli (+web-context)" }
628
570
  });
629
571
  if (!res.ok) return null;
630
572
  const ctype = res.headers.get("content-type") ?? "";
@@ -675,7 +617,7 @@ ${body}`);
675
617
  }
676
618
 
677
619
  // src/export.ts
678
- import { writeFileSync as writeFileSync2 } from "fs";
620
+ import { writeFileSync } from "fs";
679
621
  import { resolve as resolve3 } from "path";
680
622
  function renderContent(content) {
681
623
  if (typeof content === "string") return content.trim();
@@ -692,7 +634,7 @@ function renderContent(content) {
692
634
  }
693
635
  function renderTranscript(messages, meta) {
694
636
  const head = [
695
- `# zeta-g transcript`,
637
+ `# wholestack transcript`,
696
638
  ``,
697
639
  `- model: ${meta.model}`,
698
640
  meta.startedAt ? `- started: ${meta.startedAt}` : "",
@@ -716,33 +658,31 @@ ${body}
716
658
  }
717
659
  function writeTranscript(messages, meta, file, cwd2) {
718
660
  const abs = resolve3(cwd2, file);
719
- writeFileSync2(abs, renderTranscript(messages, meta), "utf8");
661
+ writeFileSync(abs, renderTranscript(messages, meta), "utf8");
720
662
  return abs;
721
663
  }
722
664
 
723
665
  // 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";
666
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
667
+ import { join as join5 } from "path";
727
668
 
728
669
  // 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";
670
+ import { existsSync, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
671
+ import { join as join3 } from "path";
732
672
  var PKG = "wholestack";
733
- var CACHE = join4(homedir2(), ".zeta-g", "update-check.json");
673
+ var CACHE = join3(stateDir(), "update-check.json");
734
674
  var DAY_MS = 24 * 60 * 60 * 1e3;
735
675
  function readCache() {
736
676
  try {
737
- return JSON.parse(readFileSync2(CACHE, "utf8"));
677
+ return JSON.parse(readFileSync(CACHE, "utf8"));
738
678
  } catch {
739
679
  return { lastCheck: 0, latest: null };
740
680
  }
741
681
  }
742
682
  function writeCache(c2) {
743
683
  try {
744
- mkdirSync2(join4(homedir2(), ".zeta-g"), { recursive: true });
745
- writeFileSync3(CACHE, JSON.stringify(c2));
684
+ mkdirSync(stateDir(), { recursive: true });
685
+ writeFileSync2(CACHE, JSON.stringify(c2));
746
686
  } catch {
747
687
  }
748
688
  }
@@ -757,7 +697,7 @@ function isNewer(a, b) {
757
697
  return false;
758
698
  }
759
699
  async function checkForUpdate(current) {
760
- if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync2) return null;
700
+ if (process.env.ZETA_NO_UPDATE_CHECK || !existsSync) return null;
761
701
  const cache = readCache();
762
702
  const fresh = Date.now() - cache.lastCheck < DAY_MS;
763
703
  let latest = cache.latest;
@@ -783,22 +723,21 @@ async function checkForUpdate(current) {
783
723
  }
784
724
 
785
725
  // 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");
726
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
727
+ import { join as join4 } from "path";
728
+ var CACHE2 = join4(stateDir(), "access.json");
790
729
  var TTL_MS = 60 * 60 * 1e3;
791
730
  function readCache2() {
792
731
  try {
793
- return JSON.parse(readFileSync3(CACHE2, "utf8"));
732
+ return JSON.parse(readFileSync2(CACHE2, "utf8"));
794
733
  } catch {
795
734
  return null;
796
735
  }
797
736
  }
798
737
  function writeCache2(c2) {
799
738
  try {
800
- mkdirSync3(join5(homedir3(), ".zeta-g"), { recursive: true });
801
- writeFileSync4(CACHE2, JSON.stringify(c2));
739
+ mkdirSync2(stateDir(), { recursive: true });
740
+ writeFileSync3(CACHE2, JSON.stringify(c2));
802
741
  } catch {
803
742
  }
804
743
  }
@@ -858,20 +797,20 @@ function showPaywall(loggedIn, webUrl) {
858
797
  }
859
798
 
860
799
  // src/cli.ts
861
- import { readFileSync as readFileSync4 } from "fs";
800
+ import { readFileSync as readFileSync3 } from "fs";
862
801
  import { basename, dirname as dirname2 } from "path";
863
802
  import { fileURLToPath } from "url";
864
803
  var VERSION = (() => {
865
804
  try {
866
805
  const here = dirname2(fileURLToPath(import.meta.url));
867
- return JSON.parse(readFileSync4(join6(here, "..", "package.json"), "utf8")).version;
806
+ return JSON.parse(readFileSync3(join5(here, "..", "package.json"), "utf8")).version;
868
807
  } catch {
869
808
  return "0.0.0";
870
809
  }
871
810
  })();
872
811
  function gitBranch(dir) {
873
812
  try {
874
- const head = readFileSync4(join6(dir, ".git", "HEAD"), "utf8").trim();
813
+ const head = readFileSync3(join5(dir, ".git", "HEAD"), "utf8").trim();
875
814
  const m = head.match(/ref:\s*refs\/heads\/(.+)$/);
876
815
  return m ? m[1] : head.slice(0, 7);
877
816
  } catch {
@@ -925,19 +864,19 @@ function parse(raw) {
925
864
  a.prompt = rest.join(" ").trim();
926
865
  return a;
927
866
  }
928
- var HELP = `${c.cyan("zeta-g")} \u2014 conversational coding agent for the ZETA engine
867
+ var HELP = `${c.cyan("wholestack")} \u2014 conversational coding agent for the Wholestack engine ${c.dim("(alias: zeta)")}
929
868
 
930
869
  ${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
870
+ wholestack start an interactive session
871
+ wholestack "build me a todo app" run one prompt and exit
872
+ wholestack -m zeta-g1-max "..." drive it with the most capable brain
934
873
 
935
874
  ${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
875
+ wholestack audit <dir> <Contract> full auto-detect security sweep
876
+ wholestack prove <dir> <Contract> --property <k> prove one invariant
877
+ wholestack verify <dir> <Contract> --cert c.json passthrough + signed certificate
939
878
  ${c.dim("kinds: reentrancy-safety, access-control, conservation, no-value-extraction, \u2026")}
940
- zeta-g --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
879
+ wholestack --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
941
880
 
942
881
  ${c.bold("Session")}
943
882
  -m, --model <key> zeta-g1-lite | zeta-g1 | zeta-g1-max | vision
@@ -957,15 +896,15 @@ ${c.bold("Extensibility")}
957
896
  --no-mcp skip MCP servers --no-plugins skip plugins
958
897
 
959
898
  ${c.bold("Build engine")}
960
- --zeta-url <u> ZETA engine base URL (default http://localhost:3000)
899
+ --zeta-url <u> build engine base URL (default https://wholestack.ai)
961
900
  --local build via the in-repo worker (no server needed)
962
- --http build via the running ZETA engine only
901
+ --http build via the running engine only
963
902
 
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)
903
+ ${c.bold("Auth")} ${c.dim("(set once, then just run wholestack)")}
904
+ wholestack login browser sign-in (or pick member/brain/engine, paste key)
905
+ wholestack login --member <key> membership key (keep + deploy generated apps)
906
+ wholestack login --brain <key> brain key (powers the Zeta-G1.0 tiers)
907
+ wholestack login --build <key> build-engine key (for /build)
969
908
 
970
909
  ${c.bold("In a session")} ${c.dim("(type /help for the full list)")}
971
910
  /model /cost /tools /undo /redo /checkpoints /compact /resume /memory /mcp /mode /think /init /doctor /clear /exit
@@ -1000,14 +939,14 @@ async function runSecuritySubcommand(raw) {
1000
939
  }
1001
940
  function maybeYoloNotice() {
1002
941
  try {
1003
- const dir = join6(homedir4(), ".zeta-g");
1004
- const marker = join6(dir, "yolo-notice-seen");
1005
- if (existsSync3(marker)) return;
942
+ const dir = stateDir();
943
+ const marker = join5(dir, "yolo-notice-seen");
944
+ if (existsSync2(marker)) return;
1006
945
  line(
1007
946
  " " + 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
947
  );
1009
- mkdirSync4(dir, { recursive: true });
1010
- writeFileSync5(marker, (/* @__PURE__ */ new Date()).toISOString());
948
+ mkdirSync3(dir, { recursive: true });
949
+ writeFileSync4(marker, (/* @__PURE__ */ new Date()).toISOString());
1011
950
  } catch {
1012
951
  }
1013
952
  }
@@ -1062,7 +1001,7 @@ async function runLogin(raw) {
1062
1001
  }
1063
1002
  if (!name || !value) {
1064
1003
  if (!stdin.isTTY) {
1065
- line(c.red(" usage: zeta-g login --member <key> | --brain <key> | --build <key>"));
1004
+ line(c.red(" usage: wholestack login --member <key> | --brain <key> | --build <key>"));
1066
1005
  return 1;
1067
1006
  }
1068
1007
  const rl = createInterface({ input: stdin, output: stdout });
@@ -1081,7 +1020,7 @@ async function runLogin(raw) {
1081
1020
  const path = saveKey(name, value);
1082
1021
  if (name === "ZETA_API_KEY") clearAccessCache();
1083
1022
  line(c.green(` \u2713 saved key for the ${what}`) + c.dim(` \u2192 ${path} (chmod 600)`));
1084
- line(c.dim(" now just run `zeta-g`."));
1023
+ line(c.dim(" now just run `wholestack`."));
1085
1024
  return 0;
1086
1025
  }
1087
1026
  var EMPTY_PLUGINS = {
@@ -1303,7 +1242,7 @@ ${m.context}` : args.prompt;
1303
1242
  await ic.runInterruptible((sig) => agent.send(text, sig, images));
1304
1243
  statusLine({
1305
1244
  model: modelLabel(modelKey),
1306
- tokens: agent.usage.totalTokens,
1245
+ tokens: agent.usage.outputTokens,
1307
1246
  tps: agent.lastTps,
1308
1247
  elapsedMs: Date.now() - t0,
1309
1248
  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-7NZ77Q7Q.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.5",
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",