wholestack 0.5.3 → 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,12 +797,20 @@ function showPaywall(loggedIn, webUrl) {
858
797
  }
859
798
 
860
799
  // src/cli.ts
861
- import { readFileSync as readFileSync4 } from "fs";
862
- import { basename } from "path";
863
- var VERSION = "0.5.0";
800
+ import { readFileSync as readFileSync3 } from "fs";
801
+ import { basename, dirname as dirname2 } from "path";
802
+ import { fileURLToPath } from "url";
803
+ var VERSION = (() => {
804
+ try {
805
+ const here = dirname2(fileURLToPath(import.meta.url));
806
+ return JSON.parse(readFileSync3(join5(here, "..", "package.json"), "utf8")).version;
807
+ } catch {
808
+ return "0.0.0";
809
+ }
810
+ })();
864
811
  function gitBranch(dir) {
865
812
  try {
866
- const head = readFileSync4(join6(dir, ".git", "HEAD"), "utf8").trim();
813
+ const head = readFileSync3(join5(dir, ".git", "HEAD"), "utf8").trim();
867
814
  const m = head.match(/ref:\s*refs\/heads\/(.+)$/);
868
815
  return m ? m[1] : head.slice(0, 7);
869
816
  } catch {
@@ -917,19 +864,19 @@ function parse(raw) {
917
864
  a.prompt = rest.join(" ").trim();
918
865
  return a;
919
866
  }
920
- 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)")}
921
868
 
922
869
  ${c.bold("Usage")}
923
- zeta-g start an interactive session
924
- zeta-g "build me a todo app" run one prompt and exit
925
- 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
926
873
 
927
874
  ${c.bold("Web3 security")} ${c.dim("(forge \xB7 slither \xB7 firewall \xB7 halmos \xB7 signed cert)")}
928
- zeta-g audit <dir> <Contract> full auto-detect security sweep
929
- zeta-g prove <dir> <Contract> --property <k> prove one invariant
930
- 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
931
878
  ${c.dim("kinds: reentrancy-safety, access-control, conservation, no-value-extraction, \u2026")}
932
- zeta-g --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
879
+ wholestack --nzt48 "audit ./contracts" NZT-48 web3 special-ops persona
933
880
 
934
881
  ${c.bold("Session")}
935
882
  -m, --model <key> zeta-g1-lite | zeta-g1 | zeta-g1-max | vision
@@ -949,15 +896,15 @@ ${c.bold("Extensibility")}
949
896
  --no-mcp skip MCP servers --no-plugins skip plugins
950
897
 
951
898
  ${c.bold("Build engine")}
952
- --zeta-url <u> ZETA engine base URL (default http://localhost:3000)
899
+ --zeta-url <u> build engine base URL (default https://wholestack.ai)
953
900
  --local build via the in-repo worker (no server needed)
954
- --http build via the running ZETA engine only
901
+ --http build via the running engine only
955
902
 
956
- ${c.bold("Auth")} ${c.dim("(set once, then just run zeta-g)")}
957
- zeta-g login interactive \u2014 pick member/brain/engine, paste key
958
- zeta-g login --member <key> membership key (keep + deploy generated apps)
959
- zeta-g login --brain <key> brain key (powers the Zeta-G1.0 tiers)
960
- 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)
961
908
 
962
909
  ${c.bold("In a session")} ${c.dim("(type /help for the full list)")}
963
910
  /model /cost /tools /undo /redo /checkpoints /compact /resume /memory /mcp /mode /think /init /doctor /clear /exit
@@ -992,14 +939,14 @@ async function runSecuritySubcommand(raw) {
992
939
  }
993
940
  function maybeYoloNotice() {
994
941
  try {
995
- const dir = join6(homedir4(), ".zeta-g");
996
- const marker = join6(dir, "yolo-notice-seen");
997
- if (existsSync3(marker)) return;
942
+ const dir = stateDir();
943
+ const marker = join5(dir, "yolo-notice-seen");
944
+ if (existsSync2(marker)) return;
998
945
  line(
999
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"
1000
947
  );
1001
- mkdirSync4(dir, { recursive: true });
1002
- writeFileSync5(marker, (/* @__PURE__ */ new Date()).toISOString());
948
+ mkdirSync3(dir, { recursive: true });
949
+ writeFileSync4(marker, (/* @__PURE__ */ new Date()).toISOString());
1003
950
  } catch {
1004
951
  }
1005
952
  }
@@ -1054,7 +1001,7 @@ async function runLogin(raw) {
1054
1001
  }
1055
1002
  if (!name || !value) {
1056
1003
  if (!stdin.isTTY) {
1057
- 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>"));
1058
1005
  return 1;
1059
1006
  }
1060
1007
  const rl = createInterface({ input: stdin, output: stdout });
@@ -1073,7 +1020,7 @@ async function runLogin(raw) {
1073
1020
  const path = saveKey(name, value);
1074
1021
  if (name === "ZETA_API_KEY") clearAccessCache();
1075
1022
  line(c.green(` \u2713 saved key for the ${what}`) + c.dim(` \u2192 ${path} (chmod 600)`));
1076
- line(c.dim(" now just run `zeta-g`."));
1023
+ line(c.dim(" now just run `wholestack`."));
1077
1024
  return 0;
1078
1025
  }
1079
1026
  var EMPTY_PLUGINS = {
@@ -1295,7 +1242,7 @@ ${m.context}` : args.prompt;
1295
1242
  await ic.runInterruptible((sig) => agent.send(text, sig, images));
1296
1243
  statusLine({
1297
1244
  model: modelLabel(modelKey),
1298
- tokens: agent.usage.totalTokens,
1245
+ tokens: agent.usage.outputTokens,
1299
1246
  tps: agent.lastTps,
1300
1247
  elapsedMs: Date.now() - t0,
1301
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.3",
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",