wholestack 0.5.7 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -75,7 +75,7 @@ import {
75
75
  webBaseUrl,
76
76
  writeProjectLink,
77
77
  writeWorkingTree
78
- } from "./chunk-TLYCOIEA.js";
78
+ } from "./chunk-C56OI76J.js";
79
79
 
80
80
  // src/cli.ts
81
81
  import { createInterface } from "readline/promises";
@@ -317,7 +317,9 @@ async function renderTree(absDir, relDir) {
317
317
  } catch {
318
318
  return;
319
319
  }
320
- const filtered = entries.filter((e) => !["node_modules", ".git", "dist", ".next", "build", ".turbo"].includes(e.name)).sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
320
+ const filtered = entries.filter((e) => !["node_modules", ".git", "dist", ".next", "build", ".turbo"].includes(e.name)).sort(
321
+ (a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name)
322
+ );
321
323
  for (const e of filtered) {
322
324
  if (count >= MAX_TREE_ENTRIES) {
323
325
  lines.push(`${prefix}\u2026 (truncated)`);
@@ -473,8 +475,11 @@ function flagValue(args, flag) {
473
475
  }
474
476
  function printFinding(f) {
475
477
  line();
476
- line(f.loaded ? c.green(" \u2713 loaded") + c.dim(` ${f.url}`) : c.red(" \u2717 not healthy") + c.dim(` ${f.url}`));
477
- if (f.httpStatus !== null) line(c.dim(` HTTP ${f.httpStatus}`) + (f.title ? c.dim(` \xB7 ${f.title}`) : ""));
478
+ line(
479
+ f.loaded ? c.green(" \u2713 loaded") + c.dim(` ${f.url}`) : c.red(" \u2717 not healthy") + c.dim(` ${f.url}`)
480
+ );
481
+ if (f.httpStatus !== null)
482
+ line(c.dim(` HTTP ${f.httpStatus}`) + (f.title ? c.dim(` \xB7 ${f.title}`) : ""));
478
483
  if (f.pageErrors.length) {
479
484
  line(c.red(` page errors (${f.pageErrors.length}):`));
480
485
  for (const e of f.pageErrors.slice(0, 8)) line(" " + c.dim(e));
@@ -672,7 +677,9 @@ function buildWorkersTool(deps) {
672
677
  "The complete, self-contained instruction for this worker. It must be doable WITHOUT coordinating with the other workers (they run concurrently in separate worktrees)."
673
678
  )
674
679
  })
675
- ).min(1).max(maxWorkers).describe(`1\u2013${maxWorkers} INDEPENDENT, edit-heavy tasks to run concurrently in isolated worktrees.`)
680
+ ).min(1).max(maxWorkers).describe(
681
+ `1\u2013${maxWorkers} INDEPENDENT, edit-heavy tasks to run concurrently in isolated worktrees.`
682
+ )
676
683
  }),
677
684
  execute: async ({ tasks: tasks2 }) => {
678
685
  if (deps.ctx.permissions.guardMutation() === "deny") {
@@ -800,8 +807,10 @@ async function runOpsSubcommand(raw) {
800
807
  line(
801
808
  " connected: " + (m.connected.posthog ? c.green("posthog") : c.dim("posthog")) + " \xB7 " + (m.connected.stripe ? c.green("stripe") : c.dim("stripe"))
802
809
  );
803
- if (m.traffic) line(" " + c.dim(`visitors 7d ${m.traffic.visitors7d} \xB7 signups 7d ${m.traffic.signups7d}`));
804
- if (m.revenue) line(" " + c.dim(`MRR $${m.revenue.mrr} \xB7 subs ${m.revenue.activeSubscriptions}`));
810
+ if (m.traffic)
811
+ line(" " + c.dim(`visitors 7d ${m.traffic.visitors7d} \xB7 signups 7d ${m.traffic.signups7d}`));
812
+ if (m.revenue)
813
+ line(" " + c.dim(`MRR $${m.revenue.mrr} \xB7 subs ${m.revenue.activeSubscriptions}`));
805
814
  return 0;
806
815
  }
807
816
  if (verb === "errors") {
@@ -831,7 +840,9 @@ async function runOpsSubcommand(raw) {
831
840
  return 0;
832
841
  }
833
842
  line(c.red(` unknown: wholestack ops ${verb ?? ""}`));
834
- line(c.dim(" verbs: health <url> \xB7 status <deploymentId> [buildId] \xB7 metrics \xB7 errors (+ --json)"));
843
+ line(
844
+ c.dim(" verbs: health <url> \xB7 status <deploymentId> [buildId] \xB7 metrics \xB7 errors (+ --json)")
845
+ );
835
846
  return 1;
836
847
  }
837
848
 
@@ -851,12 +862,19 @@ function buildLearnedMemoryTools(deps) {
851
862
  description: "Record ONE durable lesson learned this session so future sessions inherit it (cross-session memory). Use after finishing a non-trivial task to capture: a pattern that WORKED, an approach that FAILED, or a project QUIRK. Keep it to a single atomic fact \u2014 one lesson per call. Auto-loaded into the system prompt at the start of every session.",
852
863
  inputSchema: z3.object({
853
864
  text: z3.string().min(4).describe("The lesson \u2014 one self-contained, durable fact. Be specific and actionable."),
854
- kind: z3.enum(KIND_VALUES).default("note").describe("worked = a pattern that worked; failed = a dead end to avoid; quirk = a project gotcha; note = general."),
855
- tags: z3.array(z3.string()).optional().describe("Optional keywords to make this lesson findable later, e.g. ['prisma','migrations']."),
856
- scope: z3.string().optional().describe("Project/repo this applies to. Omit to use the current project; use 'global' for everywhere.")
865
+ kind: z3.enum(KIND_VALUES).default("note").describe(
866
+ "worked = a pattern that worked; failed = a dead end to avoid; quirk = a project gotcha; note = general."
867
+ ),
868
+ tags: z3.array(z3.string()).optional().describe(
869
+ "Optional keywords to make this lesson findable later, e.g. ['prisma','migrations']."
870
+ ),
871
+ scope: z3.string().optional().describe(
872
+ "Project/repo this applies to. Omit to use the current project; use 'global' for everywhere."
873
+ )
857
874
  }),
858
875
  execute: async ({ text, kind, tags, scope }) => {
859
- if (permissions.guardMutation() === "deny") return { ok: false, error: permissions.planRefusal() };
876
+ if (permissions.guardMutation() === "deny")
877
+ return { ok: false, error: permissions.planRefusal() };
860
878
  try {
861
879
  const saved = rememberLesson({
862
880
  text,
@@ -882,7 +900,9 @@ function buildLearnedMemoryTools(deps) {
882
900
  recall: tool3({
883
901
  description: "Search the lessons you learned in earlier sessions (cross-session memory). Use to check whether you've already solved something here, or to review what worked/failed before. Returns matching lessons newest-first. Lessons from the current project plus any 'global' lessons are always eligible.",
884
902
  inputSchema: z3.object({
885
- query: z3.string().optional().describe("Free-text search over lesson bodies, tags, and kind. Omit to list everything."),
903
+ query: z3.string().optional().describe(
904
+ "Free-text search over lesson bodies, tags, and kind. Omit to list everything."
905
+ ),
886
906
  scope: z3.string().optional().describe("Limit to a project/repo (plus global). Omit to use the current project."),
887
907
  limit: z3.number().int().min(1).max(50).default(10).describe("Max lessons to return.")
888
908
  }),
@@ -1384,7 +1404,9 @@ function showPaywall(loggedIn, webUrl) {
1384
1404
  row("Run " + c.cyan("wholestack login") + c.dim(" again."));
1385
1405
  }
1386
1406
  row("");
1387
- row(c.dim("Free: 3 delivered apps/month \xB7 then ") + c.cyan(`${webUrl.replace(/\/$/, "")}/pricing`));
1407
+ row(c.dim("Free: make \xB7 edit \xB7 preview (watermarked)"));
1408
+ row(c.dim("Pro $29/mo: ship unlimited \u2014 own the code"));
1409
+ row(c.cyan(`${webUrl.replace(/\/$/, "")}/pricing`));
1388
1410
  line(" " + c.cyan("\u2570" + "\u2500".repeat(w) + "\u256F"));
1389
1411
  line();
1390
1412
  }
@@ -1441,7 +1463,8 @@ function parse(raw) {
1441
1463
  else if (t === "--nzt48" || t === "--nzt-48") a.persona = "nzt-48";
1442
1464
  else if (t === "--think") a.think = true;
1443
1465
  else if (t === "--no-think") a.think = false;
1444
- else if (t === "--mcp") a.mcp = (raw[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1466
+ else if (t === "--mcp")
1467
+ a.mcp = (raw[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1445
1468
  else if (t === "--no-mcp") a.noMcp = true;
1446
1469
  else if (t === "--no-plugins") a.noPlugins = true;
1447
1470
  else if (t === "--print" || t === "-p") a.print = true;
@@ -1479,6 +1502,7 @@ ${c.bold("Ship")} ${c.dim("(go live \u2014 same credits + entitlements as the we
1479
1502
  ${c.bold("Launch")} ${c.dim("(the studio's Launch HQ \u2014 go-to-market, in the terminal)")}
1480
1503
  wholestack launch [buildId] [--prompt "\u2026"] draw the AI go-to-market plan
1481
1504
  wholestack launch-kit [buildId] show the build's startup kit (brand/pricing/copy/\u2026)
1505
+ wholestack styles [idea] list Launch Styles (+ the auto-pick for an idea)
1482
1506
  wholestack legal <terms|privacy|cookie|refund> generate a SaaS legal doc (Markdown \u2192 stdout)
1483
1507
  wholestack metrics your REAL traffic + revenue (PostHog + Stripe)
1484
1508
  wholestack waitlist [buildId] live waitlist signup count
@@ -1587,7 +1611,9 @@ async function runGithubSubcommand(raw) {
1587
1611
  line();
1588
1612
  return 0;
1589
1613
  }
1590
- line(c.red(" usage: wholestack github installations | wholestack github repos --installation <id>"));
1614
+ line(
1615
+ c.red(" usage: wholestack github installations | wholestack github repos --installation <id>")
1616
+ );
1591
1617
  return 1;
1592
1618
  }
1593
1619
  async function runAuditRepoSubcommand(rest) {
@@ -1650,7 +1676,9 @@ async function runAuditRepoSubcommand(rest) {
1650
1676
  return 1;
1651
1677
  }
1652
1678
  const verdict = String(r.result.verdict ?? r.result.shipVerdict ?? "unknown");
1653
- line(verdict === "SHIP" || verdict === "ship" ? c.green(" \u2713 SHIP") : c.yellow(` verdict: ${verdict}`));
1679
+ line(
1680
+ verdict === "SHIP" || verdict === "ship" ? c.green(" \u2713 SHIP") : c.yellow(` verdict: ${verdict}`)
1681
+ );
1654
1682
  if (r.result.certificateUrl) line(c.dim(` certificate: ${r.result.certificateUrl}`));
1655
1683
  if (r.result.bundleId) line(c.dim(` bundle: ${base2}/api/audit/bundle/${r.result.bundleId}`));
1656
1684
  return 0;
@@ -1708,7 +1736,12 @@ async function runProveSubcommand(raw) {
1708
1736
  if (data?.upgradeUrl) line(c.dim(` upgrade: ${base2}${data.upgradeUrl}`));
1709
1737
  return 1;
1710
1738
  }
1711
- printProveVerdict(data.verdict ?? "NO_SHIP", data.score ?? 0, data.checks ?? [], data.blockingReasons ?? []);
1739
+ printProveVerdict(
1740
+ data.verdict ?? "NO_SHIP",
1741
+ data.score ?? 0,
1742
+ data.checks ?? [],
1743
+ data.blockingReasons ?? []
1744
+ );
1712
1745
  return data.verdict === "SHIP" ? 0 : 1;
1713
1746
  }
1714
1747
  const path = rest.find((a) => !a.startsWith("-")) ?? cwd2();
@@ -1722,7 +1755,9 @@ async function runProveSubcommand(raw) {
1722
1755
  return result.verdict === "SHIP" ? 0 : 1;
1723
1756
  }
1724
1757
  line();
1725
- line(c.cyan(" \u259F ISL prove-gate") + c.dim(" \xB7 deterministic \xB7 fail-closed \xB7 foreign default-deny"));
1758
+ line(
1759
+ c.cyan(" \u259F ISL prove-gate") + c.dim(" \xB7 deterministic \xB7 fail-closed \xB7 foreign default-deny")
1760
+ );
1726
1761
  line();
1727
1762
  printProveVerdict(result.verdict, result.score, result.checks, result.blockingReasons);
1728
1763
  return result.verdict === "SHIP" ? 0 : 1;
@@ -1864,7 +1899,11 @@ async function runProjectSubcommand(raw) {
1864
1899
  return 1;
1865
1900
  }
1866
1901
  if (!r2.projects?.length) {
1867
- line(c.dim(' no projects yet \u2014 build something (`wholestack "an idea"`) or create one in the web IDE.'));
1902
+ line(
1903
+ c.dim(
1904
+ ' no projects yet \u2014 build something (`wholestack "an idea"`) or create one in the web IDE.'
1905
+ )
1906
+ );
1868
1907
  return 0;
1869
1908
  }
1870
1909
  line();
@@ -1874,7 +1913,9 @@ async function runProjectSubcommand(raw) {
1874
1913
  if (p.description) line(" " + c.dim(` ${p.description}`));
1875
1914
  }
1876
1915
  line();
1877
- line(c.dim(" pull one with ") + c.cyan("wholestack pull <id>") + c.dim(" \xB7 open in the IDE at ") + c.cyan(`${base2}/ide/studio?projectId=<id>`));
1916
+ line(
1917
+ c.dim(" pull one with ") + c.cyan("wholestack pull <id>") + c.dim(" \xB7 open in the IDE at ") + c.cyan(`${base2}/ide/studio?projectId=<id>`)
1918
+ );
1878
1919
  return 0;
1879
1920
  }
1880
1921
  if (verb === "pull") {
@@ -1892,7 +1933,9 @@ async function runProjectSubcommand(raw) {
1892
1933
  const written = writeWorkingTree(dest, r2.files ?? []);
1893
1934
  writeProjectLink(dest, { projectId });
1894
1935
  line(c.green(` \u2713 pulled ${written} files`) + c.dim(` \u2192 ${dest}/`));
1895
- line(c.dim(" edit locally, then ") + c.cyan(`wholestack push ${dest === "." ? "" : dest}`).trimEnd() + c.dim(" to sync back to the web IDE."));
1936
+ line(
1937
+ c.dim(" edit locally, then ") + c.cyan(`wholestack push ${dest === "." ? "" : dest}`).trimEnd() + c.dim(" to sync back to the web IDE.")
1938
+ );
1896
1939
  return 0;
1897
1940
  }
1898
1941
  if (verb === "push") {
@@ -1915,7 +1958,9 @@ async function runProjectSubcommand(raw) {
1915
1958
  return 1;
1916
1959
  }
1917
1960
  writeProjectLink(dir, { projectId });
1918
- line(c.green(` \u2713 pushed ${files.length} files`) + c.dim(skipped ? ` (${skipped} skipped: binary/oversize)` : ""));
1961
+ line(
1962
+ c.green(` \u2713 pushed ${files.length} files`) + c.dim(skipped ? ` (${skipped} skipped: binary/oversize)` : "")
1963
+ );
1919
1964
  line(c.dim(" open it: ") + c.cyan(`${base2}/ide/studio?projectId=${projectId}`));
1920
1965
  return 0;
1921
1966
  }
@@ -1946,7 +1991,9 @@ async function runShipSubcommand(raw) {
1946
1991
  }
1947
1992
  if (verb === "deploy") {
1948
1993
  line();
1949
- line(c.cyan(" \u259F deploy") + c.dim(" \xB7 live preview \xB7 charges one build credit (paid once per build)"));
1994
+ line(
1995
+ c.cyan(" \u259F deploy") + c.dim(" \xB7 deploy & host with us (managed add-on) \u2014 or own the code on Pro and self-host")
1996
+ );
1950
1997
  const r2 = await deployBuild(base2, buildId, {
1951
1998
  poll: true,
1952
1999
  onPhase: (s) => line(c.dim(` \xB7 ${s}`))
@@ -2028,7 +2075,8 @@ async function runLaunchSubcommand(raw) {
2028
2075
  }
2029
2076
  async function runHqSubcommand(raw) {
2030
2077
  const verb = raw[0];
2031
- if (!["legal", "metrics", "waitlist", "businesses", "business"].includes(verb)) return null;
2078
+ if (!["legal", "metrics", "waitlist", "businesses", "business", "styles"].includes(verb))
2079
+ return null;
2032
2080
  const base2 = webBaseUrl();
2033
2081
  const rest = raw.slice(1);
2034
2082
  const wantsJson = rest.includes("--json");
@@ -2048,6 +2096,41 @@ async function runHqSubcommand(raw) {
2048
2096
  stdout3.write(r2.markdown.trimEnd() + "\n");
2049
2097
  return 0;
2050
2098
  }
2099
+ if (verb === "styles") {
2100
+ const idea = rest.filter((a) => !a.startsWith("-")).join(" ").trim();
2101
+ let data;
2102
+ try {
2103
+ const res = await fetch(
2104
+ `${base2}/api/zeta/styles${idea ? `?idea=${encodeURIComponent(idea)}` : ""}`
2105
+ );
2106
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2107
+ data = await res.json();
2108
+ } catch (e) {
2109
+ line(c.red(` \u2717 couldn't load styles: ${e.message}`));
2110
+ return 1;
2111
+ }
2112
+ if (wantsJson) {
2113
+ stdout3.write(JSON.stringify(data, null, 2) + "\n");
2114
+ return 0;
2115
+ }
2116
+ if (data.recommended) {
2117
+ line(
2118
+ " " + c.bold("Recommended: ") + c.cyan(data.recommended.id) + c.dim(` \u2014 ${data.recommended.why}`)
2119
+ );
2120
+ if (data.recommended.alternatives?.length) {
2121
+ line(" " + c.dim(` also: ${data.recommended.alternatives.join(", ")}`));
2122
+ }
2123
+ line("");
2124
+ }
2125
+ const styles = data.styles ?? [];
2126
+ line(
2127
+ " " + c.dim(
2128
+ `${styles.length} Launch Styles \u2014 build in one with: wholestack build \u2026 --style <id>`
2129
+ )
2130
+ );
2131
+ for (const s of styles) line(" " + c.cyan(String(s.id).padEnd(20)) + c.dim(s.aesthetic));
2132
+ return 0;
2133
+ }
2051
2134
  if (verb === "metrics") {
2052
2135
  const r2 = await readMetrics(base2);
2053
2136
  if (!r2.ok || !r2.metrics) {
@@ -2120,8 +2203,10 @@ var EMPTY_PLUGINS = {
2120
2203
  function bootSummary(memory, plugins, mcp, mode, thinking) {
2121
2204
  const bits = [`mode ${c.yellow(mode)}`];
2122
2205
  if (thinking) bits.push("thinking on");
2123
- if (memory.sources.length) bits.push(`${memory.sources.length} memory file${memory.sources.length === 1 ? "" : "s"}`);
2124
- if (plugins.plugins.length) bits.push(`${plugins.plugins.length} plugin${plugins.plugins.length === 1 ? "" : "s"}`);
2206
+ if (memory.sources.length)
2207
+ bits.push(`${memory.sources.length} memory file${memory.sources.length === 1 ? "" : "s"}`);
2208
+ if (plugins.plugins.length)
2209
+ bits.push(`${plugins.plugins.length} plugin${plugins.plugins.length === 1 ? "" : "s"}`);
2125
2210
  if (mcp.mounted.length) bits.push(`${mcp.mounted.length} mcp`);
2126
2211
  line(" " + c.dim(bits.join(" \xB7 ")));
2127
2212
  for (const s of memory.sources) line(" " + c.dim(` memory: ${s.path}`));
@@ -2168,9 +2253,16 @@ async function main() {
2168
2253
  let token = process.env.ZETA_API_KEY;
2169
2254
  let access = await verifyAccess(webBaseUrl(), token);
2170
2255
  if (!access.active && stdin.isTTY) {
2256
+ line();
2257
+ line(" " + c.bold("Sign in to use Wholestack"));
2258
+ line(
2259
+ " " + c.dim(
2260
+ token ? "Your session expired." : "The agent is free \u2014 make, edit & preview unlimited. Sign in to start."
2261
+ )
2262
+ );
2171
2263
  const rl = createInterface({ input: stdin, output: stdout3 });
2172
2264
  const ans = (await rl.question(
2173
- "\n " + (token ? c.dim("Token invalid or expired. ") + "Log in again? " : "Log in now? ") + c.dim("[Y/n] ")
2265
+ " " + c.dim("Open the browser to sign in with Google? ") + c.dim("[Y/n] ")
2174
2266
  )).trim().toLowerCase();
2175
2267
  rl.close();
2176
2268
  if (ans === "" || ans === "y" || ans === "yes") {
@@ -2186,12 +2278,9 @@ async function main() {
2186
2278
  showPaywall(Boolean(token), webBaseUrl());
2187
2279
  exit(1);
2188
2280
  }
2189
- if (!access.paid && access.freeDeliveries) {
2190
- const fd = access.freeDeliveries;
2281
+ if (!access.paid) {
2191
2282
  line(
2192
- c.dim(
2193
- ` free plan \xB7 ${fd.remaining}/${fd.limit} delivered apps left this month \xB7 `
2194
- ) + c.cyan(`${webBaseUrl().replace(/\/$/, "")}/pricing`)
2283
+ c.dim(" Free plan \xB7 make \xB7 edit \xB7 preview unlimited \xB7 ship with Pro \u2192 ") + c.cyan(`${webBaseUrl().replace(/\/$/, "")}/pricing`)
2195
2284
  );
2196
2285
  } else if (access.paid && access.tier) {
2197
2286
  line(c.dim(` ${access.tier} plan`));
@@ -2282,7 +2371,13 @@ async function main() {
2282
2371
  };
2283
2372
  const permissions = new Permissions(mode, confirm, persistMode);
2284
2373
  const checkpoints = new CheckpointStore();
2285
- const toolCtx = { cwd: workdir, zetaApiUrl: args.zetaUrl, buildMode: args.buildMode, permissions, checkpoints };
2374
+ const toolCtx = {
2375
+ cwd: workdir,
2376
+ zetaApiUrl: args.zetaUrl,
2377
+ buildMode: args.buildMode,
2378
+ permissions,
2379
+ checkpoints
2380
+ };
2286
2381
  const extraTools = {
2287
2382
  ...buildWebTools(),
2288
2383
  ...mcp.tools,
@@ -2355,7 +2450,8 @@ ${m.context}` : args.prompt;
2355
2450
  bootSummary(memory, plugins, mcp, mode, thinking);
2356
2451
  if (mode === "plan") announcePlanMode();
2357
2452
  else if (yoloIsDefault) maybeYoloNotice();
2358
- if (resumedMessages) line(" " + c.dim(`resumed ${session.meta.id} \xB7 ${resumedMessages.length} messages`) + "\n");
2453
+ if (resumedMessages)
2454
+ line(" " + c.dim(`resumed ${session.meta.id} \xB7 ${resumedMessages.length} messages`) + "\n");
2359
2455
  void checkForUpdate(VERSION).then((latest) => {
2360
2456
  if (latest) {
2361
2457
  line(
@@ -2466,7 +2562,12 @@ ${m.context}` : args.prompt;
2466
2562
  } else if (res.type === "export") {
2467
2563
  const file = res.file ?? `zeta-transcript-${gitBranch(workdir) || "session"}.md`;
2468
2564
  try {
2469
- const abs = writeTranscript(agent.snapshot(), { model: modelLabel(modelKey) }, file, workdir);
2565
+ const abs = writeTranscript(
2566
+ agent.snapshot(),
2567
+ { model: modelLabel(modelKey) },
2568
+ file,
2569
+ workdir
2570
+ );
2470
2571
  line(" " + c.green(`\u2713 transcript \u2192 ${abs}`) + "\n");
2471
2572
  } catch (e) {
2472
2573
  line(" " + c.red(e.message) + "\n");
@@ -2489,17 +2590,27 @@ ${m.context}` : args.prompt;
2489
2590
  modelKey = vkey;
2490
2591
  agent.setModel(resolveModel(vkey), vkey, modelContextWindow(vkey));
2491
2592
  agent.setThinking(false);
2492
- line(c.dim(` \u29C9 image attached \u2192 switched to ${modelLabel(vkey)} (/model zeta-g1 to switch back)`) + "\n");
2593
+ line(
2594
+ c.dim(
2595
+ ` \u29C9 image attached \u2192 switched to ${modelLabel(vkey)} (/model zeta-g1 to switch back)`
2596
+ ) + "\n"
2597
+ );
2493
2598
  } catch (e) {
2494
2599
  line(c.red(` \u29C9 couldn't switch to the vision lane: ${e.message}`) + "\n");
2495
2600
  }
2496
2601
  }
2497
2602
  if (visionCapable(modelKey)) {
2498
2603
  attachments = imgs.images.map((i) => ({ dataUrl: i.dataUrl }));
2499
- line(c.dim(` \u29C9 attached ${imgs.images.length} image(s): ${imgs.images.map((i) => i.path).join(", ")}`) + "\n");
2604
+ line(
2605
+ c.dim(
2606
+ ` \u29C9 attached ${imgs.images.length} image(s): ${imgs.images.map((i) => i.path).join(", ")}`
2607
+ ) + "\n"
2608
+ );
2500
2609
  } else {
2501
2610
  line(
2502
- c.yellow(` \u29C9 ${imgs.images.length} image(s) ignored \u2014 ${modelLabel(modelKey)} is text-only.`) + c.dim(" set OPENROUTER_API_KEY (or /model vision) to enable Zeta-G1.0 Vision") + "\n"
2611
+ c.yellow(
2612
+ ` \u29C9 ${imgs.images.length} image(s) ignored \u2014 ${modelLabel(modelKey)} is text-only.`
2613
+ ) + c.dim(" set OPENROUTER_API_KEY (or /model vision) to enable Zeta-G1.0 Vision") + "\n"
2503
2614
  );
2504
2615
  }
2505
2616
  }
package/dist/index.d.ts CHANGED
@@ -465,11 +465,14 @@ declare function buildTools(ctx: ToolContext): {
465
465
  }>;
466
466
  generate_app: ai.Tool<{
467
467
  idea: string;
468
+ lean: boolean;
468
469
  install: boolean;
469
470
  scope: "full" | "static" | "component" | "page";
470
471
  buildModel: "zeta-g1" | "zeta-g1-max";
471
472
  keep: boolean;
472
473
  themeId?: "slate-minimal" | "clean-inter" | "friendly-nunito" | "geometric-jakarta" | "techno-mono" | undefined;
474
+ styleId?: string | undefined;
475
+ colorScheme?: "dark" | "light" | undefined;
473
476
  }, BuildResult | {
474
477
  ok: boolean;
475
478
  liveUrl: string | undefined;
@@ -523,6 +526,29 @@ declare function buildTools(ctx: ToolContext): {
523
526
  deploy_app: ai.Tool<{
524
527
  buildId?: string | undefined;
525
528
  }, DeployResult>;
529
+ personalize_app: ai.Tool<{
530
+ buildId?: string | undefined;
531
+ businessName?: string | undefined;
532
+ tagline?: string | undefined;
533
+ logoSrc?: string | undefined;
534
+ heroImage?: string | undefined;
535
+ pricingTiers?: {
536
+ name: string;
537
+ price: string;
538
+ period?: string | undefined;
539
+ features?: string[] | undefined;
540
+ }[] | undefined;
541
+ }, {
542
+ ok: boolean;
543
+ error: string;
544
+ buildId?: undefined;
545
+ note?: undefined;
546
+ } | {
547
+ ok: boolean;
548
+ buildId: string;
549
+ note: string;
550
+ error?: undefined;
551
+ }>;
526
552
  promote_app: ai.Tool<{
527
553
  domain?: string | undefined;
528
554
  buildId?: string | undefined;
@@ -593,8 +619,8 @@ declare function buildTools(ctx: ToolContext): {
593
619
  }>;
594
620
  generate_legal: ai.Tool<{
595
621
  kind: "terms" | "privacy" | "cookie" | "refund";
596
- brief?: string | undefined;
597
622
  businessName?: string | undefined;
623
+ brief?: string | undefined;
598
624
  }, {
599
625
  ok: boolean;
600
626
  error: string | undefined;
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  resolveModelKey,
50
50
  severityRank,
51
51
  supportsThinking
52
- } from "./chunk-TLYCOIEA.js";
52
+ } from "./chunk-C56OI76J.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.7",
3
+ "version": "0.6.0",
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",
@@ -9,14 +9,17 @@ when-to-use: When a build errors, tests fail, the prove gate returns NO_SHIP, or
9
9
  Work from evidence, not guesses. Fix the ROOT cause, not the symptom, then prove the fix.
10
10
 
11
11
  ## 1. Collect the real failure
12
+
12
13
  - Get the exact error text: prove `blockingReasons`, the build/run log, the failing test, or the compiler error.
13
14
  - Quote the precise message and the file:line it points at. Do not paraphrase a guess.
14
15
 
15
16
  ## 2. Locate
17
+
16
18
  - Read the file(s) the error names. If the cause is unclear, grep for the failing symbol/string across the repo.
17
19
  - For breadth (several suspect areas at once), fan out read-only sub-agents, one per hypothesis.
18
20
 
19
21
  ## 3. Identify the root cause
22
+
20
23
  - Map the message to a cause, e.g.:
21
24
  - module-not-found / unresolved import → missing dep or wrong path.
22
25
  - type mismatch → fix the type, never silence with `as any`.
@@ -25,14 +28,17 @@ Work from evidence, not guesses. Fix the ROOT cause, not the symptom, then prove
25
28
  - State the root cause in one sentence before editing.
26
29
 
27
30
  ## 4. Apply the minimal fix
31
+
28
32
  - Make the smallest change that addresses the cause. Match the surrounding code's patterns and conventions.
29
33
  - Avoid scope creep — fix the failure, not unrelated nits.
30
34
 
31
35
  ## 5. Re-verify (mandatory)
36
+
32
37
  - Re-run whatever was red: build, run, the failing test, AND the prove gate.
33
38
  - A fix is only done when the previously-red signal is now green. If still red, return to step 1 with the new error.
34
39
 
35
40
  ## Done criteria
41
+
36
42
  - The original failing signal now passes.
37
43
  - If this was a NO_SHIP, the prove verdict is now SHIP.
38
- Report the root cause and the now-green verification.
44
+ Report the root cause and the now-green verification.
@@ -9,26 +9,32 @@ when-to-use: When the user asks for a review, a sanity check, or "is this ready
9
9
  A quick, evidence-based pass to catch what an automated gate can miss. Report findings by severity; do not rubber-stamp.
10
10
 
11
11
  ## 1. Scope the diff
12
+
12
13
  - Identify what actually changed (the files touched this session, or the generated app's entities/routes).
13
14
  - Skip unchanged code — review the delta.
14
15
 
15
16
  ## 2. Correctness
17
+
16
18
  - Do the new/changed functions do what their name says? Check the obvious edge cases (empty input, error path).
17
19
  - Look for swallowed errors: empty catch blocks, try/catch that ignores, handlers that return 200 for everything.
18
20
 
19
21
  ## 3. Security
22
+
20
23
  - Any endpoint touching sensitive data must enforce auth/ownership — flag unprotected ones.
21
24
  - No secrets in client code; server-only keys stay server-side.
22
25
  - DB access is parameterized (no string-interpolated queries).
23
26
 
24
27
  ## 4. Fake-success
28
+
25
29
  - Functions that always return success regardless of input.
26
30
  - Loading states with no error state.
27
31
  - A "pass" that doesn't bind the real artifact (a proof that can't actually fail is no proof).
28
32
 
29
33
  ## 5. Run the gate
34
+
30
35
  - Run the prove gate and read the verdict alongside your manual findings.
31
36
 
32
37
  ## Done criteria
38
+
33
39
  - Findings listed by severity (critical / high / medium / low) with file:line and a concrete fix each.
34
40
  - A clear go / no-go recommendation, reconciled with the prove verdict.
@@ -9,31 +9,37 @@ when-to-use: When the user wants to build AND ship a full SaaS — "ship this",
9
9
  Follow these steps in order. After each step, report what happened before moving on. Do not skip the prove gate — a NO_SHIP verdict blocks deploy.
10
10
 
11
11
  ## 1. Clarify the build
12
+
12
13
  - Restate the app in one sentence (what it does, who it's for).
13
14
  - If the idea is too vague to generate (no core entities/actions), ask ONE focused question, then proceed.
14
15
 
15
16
  ## 2. Generate
17
+
16
18
  - Use the build tool to generate the full-stack app from the idea.
17
19
  - Capture the resulting `buildId` — every later step keys off the linked build.
18
20
  - If generation fails, switch to the `fix-failing-build` skill, then return here.
19
21
 
20
22
  ## 3. Prove (ShipGate — the gate)
23
+
21
24
  - Run the prove gate on the generated repo.
22
25
  - Read the `verdict`:
23
26
  - `SHIP` → continue to deploy.
24
27
  - `NO_SHIP` → STOP. List the `blockingReasons` plainly. Fix the underlying issue (often via `fix-failing-build`), regenerate or edit, then re-prove. Never deploy a NO_SHIP build.
25
28
 
26
29
  ## 4. Deploy
30
+
27
31
  - Only after a SHIP verdict, deploy the build (the linked `buildId`).
28
32
  - Report the live URL and the deploy status. If a database was provisioned, say so.
29
33
 
30
34
  ## 5. Launch kit
35
+
31
36
  - Pull the launch kit for the build (brand, pricing, copy, social, email, pitch, analytics, checklist).
32
37
  - Summarize what's ready and what the founder still needs to fill in.
33
38
  - Offer the next concrete action (custom domain, payments, waitlist, first campaign).
34
39
 
35
40
  ## Done criteria
41
+
36
42
  - A SHIP verdict was recorded.
37
43
  - A live URL was returned.
38
44
  - The launch kit was surfaced.
39
- Report all three back to the user.
45
+ Report all three back to the user.