wholestack 0.5.2 → 0.5.4

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.
@@ -801,10 +801,10 @@ ${e.message}` });
801
801
  }
802
802
 
803
803
  // src/app-runner.ts
804
- import { spawn as spawn3 } from "child_process";
804
+ import { spawn as spawn3, spawnSync } from "child_process";
805
805
  import { readFile } from "fs/promises";
806
- import { existsSync as existsSync3 } from "fs";
807
- import { join as join3 } from "path";
806
+ import { existsSync as existsSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
807
+ import { join as join3, dirname as dirname2 } from "path";
808
808
  var running = [];
809
809
  function killRunningApps() {
810
810
  for (const a of running.splice(0)) {
@@ -834,13 +834,98 @@ async function detectRun(dir) {
834
834
  }
835
835
  return { error: "no dev/start/serve script in package.json" };
836
836
  }
837
+ var _pnpmOnPath;
838
+ function pnpmAvailable() {
839
+ if (_pnpmOnPath !== void 0) return _pnpmOnPath;
840
+ try {
841
+ _pnpmOnPath = spawnSync("pnpm", ["--version"], { stdio: "ignore" }).status === 0;
842
+ } catch {
843
+ _pnpmOnPath = false;
844
+ }
845
+ return _pnpmOnPath;
846
+ }
847
+ function installPmFor(dir) {
848
+ if (existsSync3(join3(dir, "yarn.lock"))) return "yarn";
849
+ if (existsSync3(join3(dir, "pnpm-lock.yaml"))) return "pnpm";
850
+ return pnpmAvailable() ? "pnpm" : "npm";
851
+ }
852
+ function ensureNpmrc(dir, lines) {
853
+ try {
854
+ const npmrc = join3(dir, ".npmrc");
855
+ const prev = existsSync3(npmrc) ? readFileSync(npmrc, "utf8") : "";
856
+ const missing = lines.filter((l) => !prev.includes(l.split("=")[0]));
857
+ if (missing.length === 0) return;
858
+ const sep3 = prev && !prev.endsWith("\n") ? "\n" : "";
859
+ writeFileSync2(npmrc, prev + sep3 + missing.join("\n") + "\n");
860
+ } catch {
861
+ }
862
+ }
863
+ function isolateFromWorkspace(dir) {
864
+ if (existsSync3(join3(dir, "pnpm-workspace.yaml"))) return;
865
+ let cur = dirname2(dir);
866
+ let nested = false;
867
+ for (let i = 0; i < 12; i++) {
868
+ if (existsSync3(join3(cur, "pnpm-workspace.yaml"))) {
869
+ nested = true;
870
+ break;
871
+ }
872
+ const up = dirname2(cur);
873
+ if (up === cur) break;
874
+ cur = up;
875
+ }
876
+ if (!nested) return;
877
+ try {
878
+ writeFileSync2(join3(dir, "pnpm-workspace.yaml"), "packages:\n - .\n");
879
+ ensureNpmrc(dir, ["ignore-workspace-root-check=true"]);
880
+ } catch {
881
+ }
882
+ }
883
+ function installDeps(dir, onLog, signal) {
884
+ const pm = installPmFor(dir);
885
+ if (pm === "pnpm" && !existsSync3(join3(dir, "pnpm-lock.yaml"))) {
886
+ ensureNpmrc(dir, ["shamefully-hoist=true", "prefer-offline=true"]);
887
+ }
888
+ const args = pm === "pnpm" ? (
889
+ // --prefer-offline reuses the shared content-addressed store (hard-links
890
+ // the heavy common deps instead of re-fetching); --ignore-workspace keeps
891
+ // a nested app from walking up into an outer monorepo.
892
+ ["install", "--ignore-workspace", "--no-frozen-lockfile", "--prefer-offline", "--config.confirmModulesPurge=false"]
893
+ ) : pm === "yarn" ? ["install"] : ["install", "--legacy-peer-deps", "--no-audit", "--no-fund", "--prefer-offline"];
894
+ onLog?.(`installing dependencies (${pm})\u2026`);
895
+ return new Promise((resolve3) => {
896
+ const child = spawn3(pm, args, { cwd: dir, shell: false, env: process.env });
897
+ let log = "";
898
+ const timer = setTimeout(() => child.kill("SIGKILL"), 8 * 6e4);
899
+ const onAbort = () => child.kill("SIGKILL");
900
+ signal?.addEventListener("abort", onAbort, { once: true });
901
+ const sink = (b) => {
902
+ log += b.toString();
903
+ };
904
+ child.stdout.on("data", sink);
905
+ child.stderr.on("data", sink);
906
+ child.on("error", (e) => {
907
+ clearTimeout(timer);
908
+ resolve3({ ok: false, log: log + `
909
+ ${e.message}` });
910
+ });
911
+ child.on("close", (code) => {
912
+ clearTimeout(timer);
913
+ signal?.removeEventListener("abort", onAbort);
914
+ resolve3({ ok: code === 0, log: log.slice(-4e3) });
915
+ });
916
+ });
917
+ }
837
918
  async function runApp(dir, opts = {}) {
838
919
  if (!existsSync3(join3(dir, "node_modules"))) {
839
- return {
840
- ok: false,
841
- log: "",
842
- error: `dependencies not installed in ${dir} \u2014 run \`pnpm install\` (or build with install:true) first`
843
- };
920
+ isolateFromWorkspace(dir);
921
+ const inst = await installDeps(dir, void 0, opts.signal);
922
+ if (!inst.ok) {
923
+ return {
924
+ ok: false,
925
+ log: tail(inst.log),
926
+ error: `dependency install failed in ${dir} \u2014 see log`
927
+ };
928
+ }
844
929
  }
845
930
  let command;
846
931
  let cmdArgs;
@@ -1007,7 +1092,7 @@ import { tool } from "ai";
1007
1092
  import { z } from "zod";
1008
1093
  import { spawn as spawn5 } from "child_process";
1009
1094
  import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
1010
- import { resolve as resolve2, dirname as dirname2, relative, join as join4, sep as sep2 } from "path";
1095
+ import { resolve as resolve2, dirname as dirname3, relative, join as join4, sep as sep2 } from "path";
1011
1096
  import fg from "fast-glob";
1012
1097
  var IGNORE = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.next/**", "**/build/**", "**/.turbo/**"];
1013
1098
  function inside(ctx, p) {
@@ -1018,9 +1103,19 @@ function inside(ctx, p) {
1018
1103
  }
1019
1104
  return abs;
1020
1105
  }
1021
- function runShell(cmd, args, opts) {
1106
+ function shQuote(s) {
1107
+ return "'" + s.replace(/'/g, "'\\''") + "'";
1108
+ }
1109
+ var CWD_MARK = "__ZCWD__";
1110
+ function runShellLine(cmdLine, opts) {
1111
+ const shell = process.env.SHELL || "/bin/bash";
1112
+ const script = `cd ${shQuote(opts.cwd)} 2>/dev/null || cd ${shQuote(opts.root)} 2>/dev/null
1113
+ ${cmdLine}
1114
+ __zrc=$?
1115
+ printf '\\n${CWD_MARK}:%s\\n' "$PWD"
1116
+ exit $__zrc`;
1022
1117
  return new Promise((res) => {
1023
- const child = spawn5(cmd, args, { cwd: opts.cwd, shell: false });
1118
+ const child = spawn5(shell, ["-lc", script], { cwd: opts.cwd, shell: false });
1024
1119
  let out = "";
1025
1120
  const timer = setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs);
1026
1121
  const onAbort = () => child.kill("SIGKILL");
@@ -1033,12 +1128,26 @@ function runShell(cmd, args, opts) {
1033
1128
  child.on("error", (e) => {
1034
1129
  clearTimeout(timer);
1035
1130
  res({ code: 1, out: out + `
1036
- ${e.message}` });
1131
+ ${e.message}`, cwd: opts.cwd });
1037
1132
  });
1038
1133
  child.on("close", (code) => {
1039
1134
  clearTimeout(timer);
1040
1135
  opts.signal?.removeEventListener("abort", onAbort);
1041
- res({ code: code ?? 1, out });
1136
+ let nextCwd = opts.cwd;
1137
+ const idx = out.lastIndexOf(`
1138
+ ${CWD_MARK}:`);
1139
+ if (idx >= 0) {
1140
+ const rest = out.slice(idx + 1 + CWD_MARK.length + 1);
1141
+ const nl = rest.indexOf("\n");
1142
+ const p = (nl >= 0 ? rest.slice(0, nl) : rest).trim();
1143
+ out = out.slice(0, idx);
1144
+ if (p) {
1145
+ const cand = resolve2(p);
1146
+ const rel = relative(opts.root, cand);
1147
+ nextCwd = rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) ? cand : opts.root;
1148
+ }
1149
+ }
1150
+ res({ code: code ?? 1, out, cwd: nextCwd });
1042
1151
  });
1043
1152
  });
1044
1153
  }
@@ -1102,6 +1211,7 @@ async function search(ctx, pattern, searchPath, globPat, ignoreCase, maxResults,
1102
1211
  }
1103
1212
  function buildTools(ctx) {
1104
1213
  let todos = [];
1214
+ let sessionCwd = ctx.cwd;
1105
1215
  const { permissions } = ctx;
1106
1216
  return {
1107
1217
  todo_write: tool({
@@ -1194,7 +1304,28 @@ function buildTools(ctx) {
1194
1304
  dest,
1195
1305
  (m) => line(c.dim(` ${m}`))
1196
1306
  );
1197
- return { ...result, delivered };
1307
+ const rel = relative(ctx.cwd, dest) || ".";
1308
+ if (delivered.ok) {
1309
+ isolateFromWorkspace(dest);
1310
+ if (install) {
1311
+ const inst = await installDeps(dest, (m) => line(c.dim(` ${m}`)));
1312
+ return {
1313
+ ...result,
1314
+ delivered,
1315
+ dir: rel,
1316
+ installed: inst.ok,
1317
+ nextStep: inst.ok ? `App ready in "${rel}". Boot it: run_app with dir "${rel}".` : `Code saved to "${rel}" but install failed \u2014 read the log, fix package.json, then run_app.`
1318
+ };
1319
+ }
1320
+ return {
1321
+ ...result,
1322
+ delivered,
1323
+ dir: rel,
1324
+ installed: false,
1325
+ nextStep: `Code saved to "${rel}". Boot it with run_app (dir "${rel}") \u2014 it installs deps automatically.`
1326
+ };
1327
+ }
1328
+ return { ...result, delivered, dir: rel };
1198
1329
  }
1199
1330
  return result;
1200
1331
  } catch (e) {
@@ -1281,7 +1412,7 @@ function buildTools(ctx) {
1281
1412
  ctx.checkpoints?.begin();
1282
1413
  ctx.checkpoints?.capture(abs, before);
1283
1414
  ctx.checkpoints?.commit(before == null ? `create ${path}` : `write ${path}`);
1284
- await mkdir(dirname2(abs), { recursive: true });
1415
+ await mkdir(dirname3(abs), { recursive: true });
1285
1416
  await writeFile(abs, content, "utf8");
1286
1417
  toolLine("write_file", c.dim(path));
1287
1418
  return { ok: true, path, bytes: Buffer.byteLength(content) };
@@ -1464,13 +1595,13 @@ function buildTools(ctx) {
1464
1595
  }
1465
1596
  }),
1466
1597
  run_command: tool({
1467
- description: "Run a shell command in the workspace (e.g. pnpm install, forge test, git status). Use for builds, tests, and inspection. Gated by the permission layer.",
1598
+ description: "Run a shell command in the workspace (e.g. pnpm install, forge test, git status). Runs through a real shell, so chaining (&&), pipes (|), redirects (>) and `cd` all work \u2014 and `cd` persists to the next call. Use for builds, tests, and inspection. Gated by the permission layer.",
1468
1599
  inputSchema: z.object({
1469
- command: z.string().describe("The executable, e.g. pnpm, git, forge, node."),
1600
+ command: z.string().describe("The executable, e.g. pnpm, git, forge, node \u2014 or a full shell line."),
1470
1601
  args: z.array(z.string()).default([])
1471
1602
  }),
1472
1603
  execute: async ({ command, args }, { abortSignal }) => {
1473
- const display = [command, ...args].join(" ");
1604
+ const display = [command, ...args].join(" ").trim();
1474
1605
  if (await permissions.requestCommand(display) === "deny") {
1475
1606
  return {
1476
1607
  ok: false,
@@ -1479,16 +1610,23 @@ function buildTools(ctx) {
1479
1610
  };
1480
1611
  }
1481
1612
  toolLine("run_command", c.dim(display));
1482
- const { code, out } = await runShell(command, args, {
1483
- cwd: ctx.cwd,
1613
+ const { code, out, cwd: nextCwd } = await runShellLine(display, {
1614
+ cwd: sessionCwd,
1615
+ root: ctx.cwd,
1484
1616
  timeoutMs: 5 * 6e4,
1485
1617
  signal: abortSignal
1486
1618
  });
1487
- return { ok: code === 0, exitCode: code, output: out.slice(-6e3) };
1619
+ sessionCwd = nextCwd;
1620
+ return {
1621
+ ok: code === 0,
1622
+ exitCode: code,
1623
+ cwd: relative(ctx.cwd, sessionCwd) || ".",
1624
+ output: out.slice(-6e3)
1625
+ };
1488
1626
  }
1489
1627
  }),
1490
1628
  run_app: tool({
1491
- description: "Boot a generated app and watch it come up. Spawns its dev server (pnpm/npm run dev|start), then returns either the live localhost URL (success \u2014 the server keeps running) or the boot log (failure). Use it after generate_app delivers code to disk, or anytime you need to PROVE the app actually runs \u2014 then read runtime/compile errors from the log and fix them. Dependencies must already be installed (build with install:true).",
1629
+ description: "Boot a generated app and watch it come up. Spawns its dev server (pnpm/npm run dev|start), then returns either the live localhost URL (success \u2014 the server keeps running) or the boot log (failure). Use it after generate_app delivers code to disk, or anytime you need to PROVE the app actually runs \u2014 then read runtime/compile errors from the log and fix them. Installs dependencies automatically if node_modules is missing.",
1492
1630
  inputSchema: z.object({
1493
1631
  dir: z.string().describe("App directory, relative to the workspace (e.g. the delivered zeta-xxxx folder)."),
1494
1632
  script: z.string().optional().describe("package.json script to run. Omit to auto-detect dev/start/serve."),
@@ -1582,7 +1720,7 @@ function buildTools(ctx) {
1582
1720
 
1583
1721
  // src/hooks.ts
1584
1722
  import { spawn as spawn6 } from "child_process";
1585
- import { readFileSync, existsSync as existsSync4 } from "fs";
1723
+ import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
1586
1724
  import { homedir } from "os";
1587
1725
  import { join as join5 } from "path";
1588
1726
  var HOOK_TIMEOUT_MS = 15e3;
@@ -1675,7 +1813,7 @@ function loadHookFiles(cwd) {
1675
1813
  for (const f of files) {
1676
1814
  if (!existsSync4(f)) continue;
1677
1815
  try {
1678
- sets.push(JSON.parse(readFileSync(f, "utf8")));
1816
+ sets.push(JSON.parse(readFileSync2(f, "utf8")));
1679
1817
  } catch {
1680
1818
  }
1681
1819
  }
@@ -2366,9 +2504,14 @@ var detectExfiltration = (text) => {
2366
2504
  note: "fetch( call in untrusted data \u2014 possible network egress."
2367
2505
  },
2368
2506
  {
2507
+ // A bare process.env reference is ubiquitous in ordinary Node/Next source
2508
+ // (config, feature flags, NODE_ENV checks) — flagging it "high" blocked a
2509
+ // coding agent from reading its own generated files. The real leak risk is
2510
+ // a literal credential VALUE, which detectSecrets already catches; this
2511
+ // stays as a low signal so it warns without withholding the content.
2369
2512
  re: /process\.env\b[^\n]{0,40}/,
2370
- severity: "high",
2371
- note: "Reads environment variables \u2014 likely secret harvesting."
2513
+ severity: "low",
2514
+ note: "References environment variables."
2372
2515
  },
2373
2516
  {
2374
2517
  re: /~\/\.ssh\b[^\n]{0,40}/,
@@ -2718,6 +2861,7 @@ var UNTRUSTED_NATIVE = /* @__PURE__ */ new Set([
2718
2861
  "verify_contract"
2719
2862
  ]);
2720
2863
  var MCP_DISPATCH = /* @__PURE__ */ new Set(["mcp_call_tool", "mcp_list_tools"]);
2864
+ var LOCAL_SOURCE = /* @__PURE__ */ new Set(["read_file", "grep", "list_dir", "glob"]);
2721
2865
  var TEXT_FIELDS = ["content", "output"];
2722
2866
  var ARRAY_FIELDS = ["matches", "entries", "files", "results"];
2723
2867
  function isUntrustedTool(name) {
@@ -2738,6 +2882,10 @@ function payloadOf(result) {
2738
2882
  return JSON.stringify(result).replace(/\\n/g, "\n");
2739
2883
  }
2740
2884
  function applyFirewall(tools, opts) {
2885
+ const relaxed = new PolicyEngine({
2886
+ ...opts.policy.config,
2887
+ onInjection: { ...opts.policy.config.onInjection, high: "strip" }
2888
+ });
2741
2889
  const out = {};
2742
2890
  for (const [name, t] of Object.entries(tools)) {
2743
2891
  if (!isUntrustedTool(name)) {
@@ -2751,6 +2899,7 @@ function applyFirewall(tools, opts) {
2751
2899
  continue;
2752
2900
  }
2753
2901
  const kind = kindForTool(name);
2902
+ const policy = LOCAL_SOURCE.has(name) ? relaxed : opts.policy;
2754
2903
  out[name] = {
2755
2904
  ...anyTool,
2756
2905
  execute: async (input, o) => {
@@ -2759,7 +2908,7 @@ function applyFirewall(tools, opts) {
2759
2908
  if (text == null) return result;
2760
2909
  const verdict = opts.firewall.guard(
2761
2910
  { kind, authority: channelAuthority(kind), source: name, content: text, trusted: false },
2762
- opts.policy
2911
+ policy
2763
2912
  );
2764
2913
  opts.onFinding?.(verdict, name);
2765
2914
  if (typeof result === "string") return verdict.safe;
@@ -3492,7 +3641,7 @@ function announcePlanMode() {
3492
3641
  import { readFile as readFile3, stat as stat2 } from "fs/promises";
3493
3642
  import { existsSync as existsSync5 } from "fs";
3494
3643
  import { homedir as homedir2 } from "os";
3495
- import { join as join6, dirname as dirname3 } from "path";
3644
+ import { join as join6, dirname as dirname4 } from "path";
3496
3645
  var FILE_NAMES = ["ZETA.md", "AGENTS.md", "CLAUDE.md"];
3497
3646
  var MAX_TOTAL = 14e3;
3498
3647
  var MAX_PER_FILE = 8e3;
@@ -3500,7 +3649,7 @@ function repoRootFrom(start) {
3500
3649
  let dir = start;
3501
3650
  for (let i = 0; i < 24; i++) {
3502
3651
  if (existsSync5(join6(dir, ".git"))) return dir;
3503
- const up = dirname3(dir);
3652
+ const up = dirname4(dir);
3504
3653
  if (up === dir) break;
3505
3654
  dir = up;
3506
3655
  }
@@ -3525,7 +3674,7 @@ async function loadProjectMemory(cwd) {
3525
3674
  for (let i = 0; i < 24; i++) {
3526
3675
  chain.unshift(dir);
3527
3676
  if (dir === root) break;
3528
- const up = dirname3(dir);
3677
+ const up = dirname4(dir);
3529
3678
  if (up === dir) break;
3530
3679
  dir = up;
3531
3680
  }
@@ -3560,7 +3709,7 @@ ${r.text.trim()}
3560
3709
  import {
3561
3710
  appendFileSync,
3562
3711
  mkdirSync as mkdirSync2,
3563
- readFileSync as readFileSync2,
3712
+ readFileSync as readFileSync3,
3564
3713
  readdirSync,
3565
3714
  existsSync as existsSync6,
3566
3715
  statSync
@@ -3605,7 +3754,7 @@ var Session = class _Session {
3605
3754
  static resume(id) {
3606
3755
  const path = fileFor(id);
3607
3756
  if (!existsSync6(path)) return null;
3608
- const lines = readFileSync2(path, "utf8").split("\n").filter(Boolean);
3757
+ const lines = readFileSync3(path, "utf8").split("\n").filter(Boolean);
3609
3758
  let meta = null;
3610
3759
  const messages = [];
3611
3760
  for (const l of lines) {
@@ -3637,7 +3786,7 @@ var Session = class _Session {
3637
3786
  let preview = "";
3638
3787
  let turns = 0;
3639
3788
  try {
3640
- const lines = readFileSync2(path, "utf8").split("\n").filter(Boolean);
3789
+ const lines = readFileSync3(path, "utf8").split("\n").filter(Boolean);
3641
3790
  for (const l of lines) {
3642
3791
  const rec = JSON.parse(l);
3643
3792
  if (rec.kind === "meta") meta = { id: rec.id, cwd: rec.cwd, model: rec.model, startedAt: rec.startedAt };
@@ -3670,7 +3819,7 @@ var Session = class _Session {
3670
3819
  };
3671
3820
 
3672
3821
  // src/commands.ts
3673
- import { writeFileSync as writeFileSync2, existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
3822
+ import { writeFileSync as writeFileSync3, existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
3674
3823
  import { homedir as homedir4 } from "os";
3675
3824
  import { join as join8 } from "path";
3676
3825
 
@@ -4004,7 +4153,7 @@ var BUILTINS = [
4004
4153
  return { type: "handled" };
4005
4154
  }
4006
4155
  try {
4007
- writeFileSync2(path, ZETA_MD_TEMPLATE, "utf8");
4156
+ writeFileSync3(path, ZETA_MD_TEMPLATE, "utf8");
4008
4157
  ctx.print(" " + c.green(`\u2713 wrote ${path}`) + c.dim(" \xB7 edit it, then /clear to reload"));
4009
4158
  } catch (e) {
4010
4159
  ctx.print(" " + c.red(e.message));
@@ -4206,7 +4355,7 @@ function loadMdCommands(dir, source) {
4206
4355
  for (const file of readdirSync2(dir)) {
4207
4356
  if (!file.endsWith(".md")) continue;
4208
4357
  try {
4209
- const body = readFileSync3(join8(dir, file), "utf8");
4358
+ const body = readFileSync4(join8(dir, file), "utf8");
4210
4359
  cmds.push(parseCustom(file.replace(/\.md$/, ""), body, source));
4211
4360
  } catch {
4212
4361
  }
@@ -4262,7 +4411,7 @@ var CommandRegistry = class {
4262
4411
  };
4263
4412
 
4264
4413
  // src/plugins.ts
4265
- import { readFileSync as readFileSync4, existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
4414
+ import { readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
4266
4415
  import { homedir as homedir5 } from "os";
4267
4416
  import { join as join9 } from "path";
4268
4417
  function pluginRoots(cwd) {
@@ -4272,7 +4421,7 @@ function resolveSystemPrompt(dir, value) {
4272
4421
  const asFile = join9(dir, value);
4273
4422
  if (value.length < 200 && existsSync8(asFile)) {
4274
4423
  try {
4275
- return readFileSync4(asFile, "utf8").trim();
4424
+ return readFileSync5(asFile, "utf8").trim();
4276
4425
  } catch {
4277
4426
  return "";
4278
4427
  }
@@ -4304,7 +4453,7 @@ function loadPlugins(cwd) {
4304
4453
  const manifestPath = join9(dir, "zeta-plugin.json");
4305
4454
  if (!existsSync8(manifestPath)) continue;
4306
4455
  try {
4307
- const manifest = JSON.parse(readFileSync4(manifestPath, "utf8"));
4456
+ const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
4308
4457
  const name = manifest.name ?? entry;
4309
4458
  if (manifest.systemPrompt) {
4310
4459
  const text = resolveSystemPrompt(dir, manifest.systemPrompt);
@@ -4340,15 +4489,15 @@ import { dynamicTool, jsonSchema } from "ai";
4340
4489
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4341
4490
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4342
4491
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4343
- import { readFileSync as readFileSync5, existsSync as existsSync9 } from "fs";
4492
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
4344
4493
  import { homedir as homedir6 } from "os";
4345
- import { join as join10, dirname as dirname4 } from "path";
4494
+ import { join as join10, dirname as dirname5 } from "path";
4346
4495
  function isHttp(c2) {
4347
4496
  return typeof c2.url === "string";
4348
4497
  }
4349
4498
  function readConfigFile(path) {
4350
4499
  try {
4351
- const json = JSON.parse(readFileSync5(path, "utf8"));
4500
+ const json = JSON.parse(readFileSync6(path, "utf8"));
4352
4501
  return json.mcpServers ?? {};
4353
4502
  } catch {
4354
4503
  return {};
@@ -4365,7 +4514,7 @@ function discoverConfigs(cwd) {
4365
4514
  Object.assign(merged, readConfigFile(p));
4366
4515
  break;
4367
4516
  }
4368
- const up = dirname4(dir);
4517
+ const up = dirname5(dir);
4369
4518
  if (up === dir) break;
4370
4519
  dir = up;
4371
4520
  }
@@ -4618,14 +4767,14 @@ function buildWebTools() {
4618
4767
  import { createInterface } from "readline/promises";
4619
4768
  import { emitKeypressEvents } from "readline";
4620
4769
  import { stdin, stdout } from "process";
4621
- import { readFileSync as readFileSync6, appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
4770
+ import { readFileSync as readFileSync7, appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
4622
4771
  import { homedir as homedir7 } from "os";
4623
- import { join as join11, dirname as dirname5, basename } from "path";
4772
+ import { join as join11, dirname as dirname6, basename } from "path";
4624
4773
  var HISTORY_FILE = join11(homedir7(), ".zeta-g", "history");
4625
4774
  var HISTORY_MAX = 1e3;
4626
4775
  function loadHistory() {
4627
4776
  try {
4628
- return readFileSync6(HISTORY_FILE, "utf8").split("\n").filter(Boolean).slice(-HISTORY_MAX);
4777
+ return readFileSync7(HISTORY_FILE, "utf8").split("\n").filter(Boolean).slice(-HISTORY_MAX);
4629
4778
  } catch {
4630
4779
  return [];
4631
4780
  }
@@ -4661,7 +4810,7 @@ var InputController = class {
4661
4810
  const at = line2.lastIndexOf("@");
4662
4811
  if (at >= 0 && at >= line2.lastIndexOf(" ")) {
4663
4812
  const partial = line2.slice(at + 1);
4664
- const dir = partial.includes("/") ? dirname5(partial) : ".";
4813
+ const dir = partial.includes("/") ? dirname6(partial) : ".";
4665
4814
  const base = basename(partial);
4666
4815
  try {
4667
4816
  const entries = readdirSync4(dir, { withFileTypes: true });
@@ -4927,7 +5076,7 @@ var InputController = class {
4927
5076
  }
4928
5077
  saveHistory(entry) {
4929
5078
  try {
4930
- mkdirSync3(dirname5(HISTORY_FILE), { recursive: true });
5079
+ mkdirSync3(dirname6(HISTORY_FILE), { recursive: true });
4931
5080
  if (!existsSync10(HISTORY_FILE) || entry !== loadHistory().slice(-1)[0]) {
4932
5081
  appendFileSync2(HISTORY_FILE, entry.replace(/\n/g, " ") + "\n", "utf8");
4933
5082
  }
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  tasks,
31
31
  userBox,
32
32
  visionCapable
33
- } from "./chunk-TDSLCPQL.js";
33
+ } from "./chunk-FI67ER5S.js";
34
34
 
35
35
  // src/cli.ts
36
36
  import { createInterface } from "readline/promises";
@@ -857,40 +857,18 @@ function showPaywall(loggedIn, webUrl) {
857
857
  line();
858
858
  }
859
859
 
860
- // src/art.ts
861
- var ART_PACK = [
862
- [" \u2588\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588\u2554\u255D", " \u2588\u2588\u2554\u255D ", " \u2588\u2588\u2554\u255D ", " \u2588\u2588\u2588\u2588\u2588\u2588\u2588 "],
863
- [" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E", " \u2502 \u2571\u2571 \u2502", " \u2502 \u2571\u2571 \u2502", " \u2502\u2571\u2571 \u2502", " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"],
864
- [" \u259F\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2599", " \u259F\u2588\u259B ", " \u259F\u2588\u259B ", " \u259F\u2588\u259B ", " \u259C\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259B "],
865
- [" \u250C\u2500\u2510\u250C\u2500\u2510\u250C\u252C\u2510\u250C\u2500\u2510", " \u250C\u2500\u2518\u251C\u2524 \u2502 \u251C\u2500\u2524", " \u2514\u2500\u2518\u2514\u2500\u2518 \u2534 \u2534 \u2534"],
866
- [" \u2571\u2572 ", " \u2571\u2500\u2500\u2572 ", " \u2571 \u2571\u2571 \u2572 ", "\u2571 \u2571\u2571 \u2572 ", "\u2572\u2571\u2571\u2500\u2500\u2500\u2500\u2500\u2572"]
867
- ];
868
- function randomArt(seed) {
869
- const i = seed != null ? seed % ART_PACK.length : Math.floor(Math.random() * ART_PACK.length);
870
- return ART_PACK[(i % ART_PACK.length + ART_PACK.length) % ART_PACK.length];
871
- }
872
- var vlen = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
873
- function fetchPanel(fields, art = randomArt()) {
874
- const shown = fields.filter((f) => f.value);
875
- const labelW = Math.max(0, ...shown.map((f) => f.label.length));
876
- const artW = Math.max(0, ...art.map(vlen));
877
- const rows = Math.max(art.length, shown.length);
878
- const out = [];
879
- out.push("");
880
- for (let i = 0; i < rows; i++) {
881
- const left = (art[i] ?? "").padEnd(artW + 2);
882
- const f = shown[i];
883
- const right = f ? c.dim(f.label.padStart(labelW) + " ") + c.bold(f.value) : "";
884
- out.push(" " + c.cyan(left) + right);
885
- }
886
- out.push("");
887
- for (const l of out) process.stdout.write(l + "\n");
888
- }
889
-
890
860
  // src/cli.ts
891
861
  import { readFileSync as readFileSync4 } from "fs";
892
- import { basename } from "path";
893
- var VERSION = "0.5.0";
862
+ import { basename, dirname as dirname2 } from "path";
863
+ import { fileURLToPath } from "url";
864
+ var VERSION = (() => {
865
+ try {
866
+ const here = dirname2(fileURLToPath(import.meta.url));
867
+ return JSON.parse(readFileSync4(join6(here, "..", "package.json"), "utf8")).version;
868
+ } catch {
869
+ return "0.0.0";
870
+ }
871
+ })();
894
872
  function gitBranch(dir) {
895
873
  try {
896
874
  const head = readFileSync4(join6(dir, ".git", "HEAD"), "utf8").trim();
@@ -1295,16 +1273,6 @@ ${m.context}` : args.prompt;
1295
1273
  return;
1296
1274
  }
1297
1275
  await banner();
1298
- fetchPanel([
1299
- { label: "model", value: modelLabel(modelKey) },
1300
- { label: "mode", value: mode },
1301
- { label: "dir", value: basename(workdir) },
1302
- { label: "git", value: gitBranch(workdir) },
1303
- { label: "node", value: process.version },
1304
- { label: "memory", value: memory.sources.length ? `${memory.sources.length} file(s)` : "" },
1305
- { label: "mcp", value: mcp.mounted.length ? `${mcp.mounted.length} mounted` : "" },
1306
- { label: "zeta", value: VERSION }
1307
- ]);
1308
1276
  bootSummary(memory, plugins, mcp, mode, thinking);
1309
1277
  if (mode === "plan") announcePlanMode();
1310
1278
  else if (yoloIsDefault) maybeYoloNotice();
package/dist/index.d.ts CHANGED
@@ -190,9 +190,9 @@ declare function buildTools(ctx: ToolContext): {
190
190
  }>;
191
191
  generate_app: ai.Tool<{
192
192
  idea: string;
193
+ install: boolean;
193
194
  scope: "full" | "static" | "component" | "page";
194
195
  buildModel: "zeta-g1" | "zeta-g1-max";
195
- install: boolean;
196
196
  keep: boolean;
197
197
  }, BuildResult | {
198
198
  ok: boolean;
@@ -205,6 +205,28 @@ declare function buildTools(ctx: ToolContext): {
205
205
  note: string;
206
206
  } | {
207
207
  delivered: DeliverResult;
208
+ dir: string;
209
+ installed: boolean;
210
+ nextStep: string;
211
+ ok: boolean;
212
+ error?: string;
213
+ buildId?: unknown;
214
+ verdict?: "SHIP" | "NO_SHIP";
215
+ themeId?: unknown;
216
+ fileCount?: number;
217
+ spec?: string | null;
218
+ verifyErrors?: string[];
219
+ fileList?: (string | null | undefined)[];
220
+ paywalled?: boolean;
221
+ upgradeUrl?: string;
222
+ liveUrl?: undefined;
223
+ files?: undefined;
224
+ loc?: undefined;
225
+ trust?: undefined;
226
+ note?: undefined;
227
+ } | {
228
+ delivered: DeliverResult;
229
+ dir: string;
208
230
  ok: boolean;
209
231
  error?: string;
210
232
  buildId?: unknown;
@@ -404,10 +426,12 @@ declare function buildTools(ctx: ToolContext): {
404
426
  declined: boolean;
405
427
  error: string;
406
428
  exitCode?: undefined;
429
+ cwd?: undefined;
407
430
  output?: undefined;
408
431
  } | {
409
432
  ok: boolean;
410
433
  exitCode: number;
434
+ cwd: string;
411
435
  output: string;
412
436
  declined?: undefined;
413
437
  error?: undefined;
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  resolveModelKey,
50
50
  severityRank,
51
51
  supportsThinking
52
- } from "./chunk-TDSLCPQL.js";
52
+ } from "./chunk-FI67ER5S.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.2",
3
+ "version": "0.5.4",
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",
@@ -47,12 +47,6 @@
47
47
  "engines": {
48
48
  "node": ">=20.0.0"
49
49
  },
50
- "scripts": {
51
- "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && node -e \"require('fs').chmodSync('dist/cli.js','755')\"",
52
- "dev": "tsx src/cli.ts",
53
- "typecheck": "tsc --noEmit",
54
- "clean": "rimraf dist"
55
- },
56
50
  "dependencies": {
57
51
  "@ai-sdk/openai": "^3.0.64",
58
52
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -62,15 +56,21 @@
62
56
  "zod": "^3.25.76"
63
57
  },
64
58
  "devDependencies": {
65
- "@isl-lang/zeta-models": "workspace:*",
66
59
  "@types/diff": "^5.2.0",
67
60
  "@types/node": "^20.10.0",
68
61
  "tsup": "^8.0.1",
69
62
  "tsx": "^4.7.0",
70
63
  "typescript": "^5.3.3",
71
- "rimraf": "^5.0.5"
64
+ "rimraf": "^5.0.5",
65
+ "@isl-lang/zeta-models": "1.0.0"
72
66
  },
73
67
  "author": "Wholestack",
74
68
  "license": "MIT",
75
- "sideEffects": false
76
- }
69
+ "sideEffects": false,
70
+ "scripts": {
71
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && node -e \"require('fs').chmodSync('dist/cli.js','755')\"",
72
+ "dev": "tsx src/cli.ts",
73
+ "typecheck": "tsc --noEmit",
74
+ "clean": "rimraf dist"
75
+ }
76
+ }