wholestack 0.5.7 → 0.5.8

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.
@@ -1075,7 +1075,14 @@ async function localBuild(body, onPhase, repoRoot) {
1075
1075
  return new Promise((res) => {
1076
1076
  const child = spawn(process.execPath, args, {
1077
1077
  cwd: root,
1078
- env: process.env,
1078
+ // Launch Style + color scheme ride the SAME env vars the web build route sets
1079
+ // (the worker reads ZETA_COMPOSE_STYLE / ZETA_BUILD_COLOR_SCHEME). Absent → the
1080
+ // pipeline auto-picks from the idea.
1081
+ env: {
1082
+ ...process.env,
1083
+ ...body.styleId ? { ZETA_COMPOSE_STYLE: body.styleId } : {},
1084
+ ...body.colorScheme ? { ZETA_BUILD_COLOR_SCHEME: body.colorScheme } : {}
1085
+ },
1079
1086
  stdio: ["ignore", "pipe", "pipe"]
1080
1087
  });
1081
1088
  const sink = makeConsumer(onPhase);
@@ -2274,7 +2281,7 @@ function buildTools(ctx) {
2274
2281
  let sessionCwd = ctx.cwd;
2275
2282
  const { permissions } = ctx;
2276
2283
  async function runGenerate(opts) {
2277
- const { idea, scope, buildModel, install, keep, themeId } = opts;
2284
+ const { idea, scope, buildModel, install, keep, themeId, styleId, colorScheme } = opts;
2278
2285
  const preferBoot = opts.preferBoot ?? true;
2279
2286
  if (permissions.guardMutation() === "deny") {
2280
2287
  return { ok: false, error: permissions.planRefusal() };
@@ -2287,10 +2294,20 @@ function buildTools(ctx) {
2287
2294
  }
2288
2295
  const scopeTag = scope && scope !== "full" ? c.dim(` \xB7${scope}`) : "";
2289
2296
  toolLine("generate_app", c.dim(`"${idea.slice(0, 48)}${idea.length > 48 ? "\u2026" : ""}"`) + scopeTag);
2290
- const body = { idea, scope, buildModel, install, ...themeId ? { themeId } : {} };
2297
+ const body = {
2298
+ idea,
2299
+ scope,
2300
+ buildModel,
2301
+ install,
2302
+ ...themeId ? { themeId } : {},
2303
+ // Launch Style + color scheme — match the studio's style picker. Omitted → the
2304
+ // engine auto-picks the best-fit launch style from the idea (the web default).
2305
+ ...styleId ? { styleId } : {},
2306
+ ...colorScheme ? { colorScheme } : {}
2307
+ };
2291
2308
  const onPhase = (msg) => line(c.dim(` \u21B3 ${scrubVendor(msg)}`));
2292
2309
  try {
2293
- if (preferBoot && scope !== "static" && ctx.buildMode !== "local" && ctx.buildMode !== "http") {
2310
+ if (preferBoot && !styleId && !colorScheme && scope !== "static" && ctx.buildMode !== "local" && ctx.buildMode !== "http") {
2294
2311
  const booted = await demoBootBuild(ctx.zetaApiUrl, { idea, buildModel, ...themeId ? { themeId } : {} }, onPhase);
2295
2312
  if (booted.ok) {
2296
2313
  return {
@@ -2391,13 +2408,17 @@ function buildTools(ctx) {
2391
2408
  themeId: z6.enum(["slate-minimal", "clean-inter", "friendly-nunito", "geometric-jakarta", "techno-mono"]).optional().describe(
2392
2409
  "Optional design theme. Pass one matching the brand/style the user wants; OMIT to let the engine auto-pick the best fit for the idea (the web app's default). slate-minimal = clean professional, clean-inter = modern neutral, friendly-nunito = warm/rounded, geometric-jakarta = bold geometric, techno-mono = technical/developer mono."
2393
2410
  ),
2411
+ styleId: z6.string().max(120).optional().describe(
2412
+ "Optional LAUNCH STYLE family id \u2014 the studio's style gallery (e.g. 'voltage', 'mono-noir', 'editorial-luxe', 'glass-vision', 'luxury-dark'). Pass the id when the user names a look/vibe; run `zeta styles` (or GET /api/zeta/styles) for the full list. OMIT to let the engine auto-pick the best-fit style from the idea \u2014 same as the web app with no explicit pick."
2413
+ ),
2414
+ colorScheme: z6.enum(["dark", "light"]).optional().describe("Optional dark/light override for the landing. OMIT to let the engine choose."),
2394
2415
  buildModel: z6.enum(["zeta-g1", "zeta-g1-max"]).default("zeta-g1").describe("zeta-g1 is fast; zeta-g1-max is stronger for richer specs."),
2395
2416
  install: z6.boolean().default(false).describe("Install dependencies after generation. Slower but produces a runnable repo."),
2396
2417
  keep: z6.boolean().default(true).describe(
2397
2418
  "Save the generated code to disk (the paid delivery step \u2014 needs a membership/ZETA_API_KEY). false = preview/verify only, write nothing."
2398
2419
  )
2399
2420
  }),
2400
- execute: async ({ idea, scope, buildModel, install, keep, themeId }) => runGenerate({ idea, scope, buildModel, install, keep, themeId })
2421
+ execute: async ({ idea, scope, buildModel, install, keep, themeId, styleId, colorScheme }) => runGenerate({ idea, scope, buildModel, install, keep, themeId, styleId, colorScheme })
2401
2422
  }),
2402
2423
  deploy_app: tool6({
2403
2424
  description: "Deploy a BUILT app to a live preview URL (the studio's one-click Deploy \u2192 Vercel). Use AFTER generate_app has produced a build. Charges one build credit, paid once per build (re-deploying the same build is free); provisions a database + applies the schema when the app needs one. Polls until the deployment is live and returns its URL. This is an ephemeral PREVIEW \u2014 use promote_app to ship a durable production deployment.",
@@ -2416,6 +2437,45 @@ function buildTools(ctx) {
2416
2437
  return deployBuild(ctx.zetaApiUrl, id, { poll: true, onPhase: (s) => toolLine("deploy_app", c.dim(s)) });
2417
2438
  }
2418
2439
  }),
2440
+ personalize_app: tool6({
2441
+ description: "Apply the founder's business IDENTITY to a BUILT app \u2014 business name, tagline, logo, hero image, and pricing tiers \u2014 the CLI twin of the studio's post-build Personalize form. Use AFTER generate_app when the user gives a real name/pricing/brand. Re-renders the landing + pricing instantly. Omit any field to leave it unchanged.",
2442
+ inputSchema: z6.object({
2443
+ buildId: z6.string().optional().describe("The build to personalize. Omit to use the build linked in the current directory."),
2444
+ businessName: z6.string().max(120).optional().describe("Real product/business name for the landing + nav."),
2445
+ tagline: z6.string().max(300).optional().describe("One-line value proposition under the hero headline."),
2446
+ logoSrc: z6.string().max(500).optional().describe("Logo image URL."),
2447
+ heroImage: z6.string().max(500).optional().describe("Hero image URL."),
2448
+ pricingTiers: z6.array(
2449
+ z6.object({
2450
+ name: z6.string().max(60).describe('Tier name, e.g. "Pro".'),
2451
+ price: z6.string().max(40).describe('Display price, e.g. "$20/mo" or "Free".'),
2452
+ period: z6.string().max(40).optional(),
2453
+ features: z6.array(z6.string().max(200)).max(12).optional().describe("Bullet features for the tier.")
2454
+ })
2455
+ ).max(6).optional().describe("Replace the generated pricing table with these tiers.")
2456
+ }),
2457
+ execute: async ({ buildId, ...identity }) => {
2458
+ if (permissions.guardMutation() === "deny") return { ok: false, error: permissions.planRefusal() };
2459
+ if (!process.env.ZETA_API_KEY?.trim()) {
2460
+ return { ok: false, error: "Login required \u2014 run `wholestack login` (personalize edits your build on the platform)." };
2461
+ }
2462
+ const id = buildId?.trim() || readProjectLink(ctx.cwd)?.buildId;
2463
+ if (!id) {
2464
+ return { ok: false, error: "No buildId given and no build linked in this directory. Build an app first (generate_app with keep:true), or pass buildId." };
2465
+ }
2466
+ const body = Object.fromEntries(Object.entries(identity).filter(([, v]) => v !== void 0));
2467
+ if (Object.keys(body).length === 0) {
2468
+ return { ok: false, error: "Nothing to personalize \u2014 pass at least one of businessName / tagline / logoSrc / heroImage / pricingTiers." };
2469
+ }
2470
+ toolLine("personalize_app", c.dim(`applying identity to ${id}`));
2471
+ const r = await apiJson(
2472
+ `${ctx.zetaApiUrl}/api/zeta/build/${encodeURIComponent(id)}/personalize`,
2473
+ { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }
2474
+ );
2475
+ if (!r.ok) return { ok: false, error: r.error };
2476
+ return { ok: true, buildId: id, note: "Business identity applied \u2014 the landing + pricing re-rendered." };
2477
+ }
2478
+ }),
2419
2479
  promote_app: tool6({
2420
2480
  description: "Promote a BUILT app to a DURABLE PRODUCTION deployment \u2014 a real (non-ephemeral) database and production env with dev-auth OFF, optionally bound to a custom domain. This is the 'make it a real product' step (stronger than deploy_app's ephemeral preview). Requires the platform to have a database provider configured. Charges/credits follow the same rules as deploy.",
2421
2481
  inputSchema: z6.object({
@@ -3572,6 +3632,21 @@ var MODEL_IDS = {
3572
3632
  /** VISION lane — accepts image input. OpenRouter (Cerebras text lanes can't see). */
3573
3633
  vision: readEnv("ZETA_VISION_MODEL") || "anthropic/claude-sonnet-4.6"
3574
3634
  };
3635
+ var ZETA_AGENT_PIPELINE = Object.freeze({
3636
+ /** THINK lane — ISL spec authoring BASE. gpt-oss-120b ($0.35/$0.75 per 1M) so a build
3637
+ * costs ~a penny — the COGS that makes "free unlimited builds" sustainable. The
3638
+ * spec-writer escalates to `thinkEscalation` ONLY when the base fails to parse/prove. */
3639
+ think: "gpt-oss-120b",
3640
+ /** THINK escalation — GLM-4.7 ($2.25/$2.75 per 1M, ~6-8× the base). Richer modeling,
3641
+ * used ONLY on the hard specs gpt-oss can't get right (the ~10% tail), so the common
3642
+ * build stays a penny while quality holds where it matters. */
3643
+ thinkEscalation: "zai-glm-4.7",
3644
+ /** BUILD lane — landing copy + self-heal (codegen is deterministic). gpt-oss. */
3645
+ build: "gpt-oss-120b"
3646
+ });
3647
+ var ZETA_AGENT_THINK_MODEL = ZETA_AGENT_PIPELINE.think;
3648
+ var ZETA_AGENT_THINK_ESCALATION_MODEL = ZETA_AGENT_PIPELINE.thinkEscalation;
3649
+ var ZETA_AGENT_BUILD_MODEL = ZETA_AGENT_PIPELINE.build;
3575
3650
 
3576
3651
  // src/model.ts
3577
3652
  var CEREBRAS_URL = PROVIDERS.cerebras.baseURL;
@@ -3664,15 +3739,19 @@ function buildProviderOptions(key, thinkingOn) {
3664
3739
  }
3665
3740
  function resolveModel(key) {
3666
3741
  const s = spec(key);
3667
- const apiKey = process.env[s.keyEnv];
3742
+ const localKey = process.env[s.keyEnv];
3743
+ const memberToken = process.env.ZETA_API_KEY?.trim();
3744
+ const useGateway = !localKey && !!memberToken && s.baseURL === CEREBRAS_URL;
3745
+ const apiKey = localKey ?? (useGateway ? memberToken : void 0);
3668
3746
  if (!apiKey) {
3669
3747
  throw new Error(
3670
3748
  `${s.label} isn't configured yet (no brain key).
3671
- run \`wholestack login\` to add your key, or pick another tier with --model`
3749
+ run \`wholestack login\` to use it on our key, set ${s.keyEnv} to bring your own, or pick another tier with --model`
3672
3750
  );
3673
3751
  }
3752
+ const baseURL = useGateway ? `${webBaseUrl().replace(/\/$/, "")}/api/cli/llm` : s.baseURL;
3674
3753
  const brain = createOpenAI({
3675
- baseURL: s.baseURL,
3754
+ baseURL,
3676
3755
  apiKey,
3677
3756
  headers: { "HTTP-Referer": "https://wholestack.ai", "X-Title": "wholestack" }
3678
3757
  });
@@ -4214,8 +4293,20 @@ var SINK_PATTERNS = [
4214
4293
  { re: /dangerouslySetInnerHTML/, label: "dangerouslySetInnerHTML" },
4215
4294
  { re: /require\(\s*["']child_process["']\s*\)|from\s+["']child_process["']/, label: "child_process import" }
4216
4295
  ];
4296
+ var EXPANDED_SINK_PATTERNS = [
4297
+ {
4298
+ re: /(?:import\b[^'"]*?from|import|require\(|import\()\s*["'](?:https?:|file:|data:)/i,
4299
+ label: "untrusted-protocol import (http/file/data URL)"
4300
+ },
4301
+ { re: /\bfs(?:\.promises)?\s*\.\s*(?:unlink|rm|rmdir)(?:Sync)?\s*\(/, label: "destructive fs operation" },
4302
+ { re: /\bprocess\.exit\s*\(/, label: "process.exit() in app code" }
4303
+ ];
4304
+ function completenessGateEnabled() {
4305
+ const v = process.env.ZETA_COMPLETENESS_GATE?.trim().toLowerCase();
4306
+ return v === "1" || v === "true" || v === "on";
4307
+ }
4217
4308
  var MUTATION_CALL = /\b(?:prisma|db|tx|client|trx)\.\w+\.(create|update|delete|upsert|createMany|updateMany|deleteMany)\s*\(/;
4218
- var AUTH_GUARD = /\b(getServerSession|getSession|auth\s*\(|currentUser|getCurrentUser|getCurrentProjectIdentity|getAccessibleProject|require[A-Z]\w*|withTenantRole|clerkClient|getToken|verifyJwt|session\.user|userId)\b/;
4309
+ var AUTH_GUARD = /\b(?:getServerSession|getSession|currentUser|getCurrentUser|getCurrentProjectIdentity|getAccessibleProject|require[A-Z]\w*|withTenantRole|clerkClient|getToken|verifyJwt|userId)\b|\bauth\s*\(|session\??\.user/;
4219
4310
  var SKIP_DIRS = /* @__PURE__ */ new Set([
4220
4311
  "node_modules",
4221
4312
  ".next",
@@ -4274,7 +4365,10 @@ var ROLE_SIGNALS = [
4274
4365
  ];
4275
4366
  var WEBHOOK_SIGNALS = [
4276
4367
  { id: "constructEvent", re: /\bconstructEvent\s*\(/ },
4277
- { id: "verifyHmac", re: /\bverify(?:Hmac|Signature|WebhookSignature)\s*\(/ },
4368
+ // `verifyWebhook` is the generated Stripe route's wrapper (lib/stripe.verifyWebhook
4369
+ // → stripe.webhooks.constructEvent — a REAL HMAC check over the raw body); without
4370
+ // it a genuinely signature-verified webhook was mis-flagged as an unguarded orphan.
4371
+ { id: "verifyHmac/Webhook", re: /\bverify(?:Hmac|Signature|Webhook(?:Signature)?)\s*\(/ },
4278
4372
  { id: "timingSafeEqual", re: /\btimingSafeEqual\s*\(/ },
4279
4373
  { id: "createHmac", re: /\bcreateHmac\s*\(/ }
4280
4374
  ];
@@ -4391,7 +4485,8 @@ function staticAudit(files, dd) {
4391
4485
  const m = re.exec(f.content);
4392
4486
  if (m) blocking.push({ file: f.path, line: lineOf(f.content, m.index), detail: `Hardcoded ${label}` });
4393
4487
  }
4394
- for (const { re, label } of SINK_PATTERNS) {
4488
+ const sinks = completenessGateEnabled() ? [...SINK_PATTERNS, ...EXPANDED_SINK_PATTERNS] : SINK_PATTERNS;
4489
+ for (const { re, label } of sinks) {
4395
4490
  const m = re.exec(f.content);
4396
4491
  if (m) advisory.push({ file: f.path, line: lineOf(f.content, m.index), detail: `Dangerous sink ${label}` });
4397
4492
  }
@@ -4451,7 +4546,7 @@ function detectDefaultDeny(projectDir) {
4451
4546
  for (const im of c2.matchAll(/from\s+["']([^"']+)["']/g)) {
4452
4547
  const spec2 = im[1];
4453
4548
  if (!/default-deny|auth|public|access|guard|middleware/i.test(spec2)) continue;
4454
- const base = spec2.startsWith("@/") ? spec2.slice(2) : spec2.startsWith("./") || spec2.startsWith("../") ? join8(dirname5(entryRel), spec2) : null;
4549
+ const base = spec2.startsWith("@/") ? spec2.slice(2) : spec2.startsWith(".") ? join8(dirname5(entryRel), spec2) : null;
4455
4550
  if (!base) continue;
4456
4551
  for (const ext of [".ts", ".tsx", ".js", "/index.ts", "/index.js"]) {
4457
4552
  const txt = tryRead(base + ext);
@@ -7966,21 +8061,83 @@ var BUILTINS = [
7966
8061
  },
7967
8062
  {
7968
8063
  name: "build",
7969
- summary: "scaffold a full-stack app (/build <idea>)",
8064
+ summary: "scaffold a full-stack app (/build <idea> [--style <id>] [--color dark|light] [--deploy])",
7970
8065
  source: "builtin",
7971
8066
  run: (ctx) => {
7972
8067
  if (!ctx.args) {
7973
8068
  ctx.print(
7974
- " " + c.dim("usage: /build <what to build> e.g. /build a CRM with auth and billing")
8069
+ " " + c.dim("usage: /build <what to build> [--style <id>] [--color dark|light] [--deploy]")
7975
8070
  );
8071
+ ctx.print(" " + c.dim(" e.g. /build a CRM with auth and billing --style mono-noir --deploy"));
8072
+ ctx.print(" " + c.dim(" run /styles to see every Launch Style (or omit --style to auto-pick)"));
7976
8073
  return { type: "handled" };
7977
8074
  }
8075
+ let rest = ctx.args;
8076
+ let styleId;
8077
+ let colorScheme;
8078
+ const styleM = rest.match(/--style[=\s]+([a-z0-9-]+)/i);
8079
+ if (styleM) {
8080
+ styleId = styleM[1].toLowerCase();
8081
+ rest = rest.replace(styleM[0], "");
8082
+ }
8083
+ const colorM = rest.match(/--color[=\s]+(dark|light)/i);
8084
+ if (colorM) {
8085
+ colorScheme = colorM[1].toLowerCase();
8086
+ rest = rest.replace(colorM[0], "");
8087
+ }
8088
+ let deploy = false;
8089
+ if (/(^|\s)--deploy(\s|$)/.test(rest)) {
8090
+ deploy = true;
8091
+ rest = rest.replace(/(^|\s)--deploy(\s|$)/, " ");
8092
+ }
8093
+ const idea = rest.trim();
8094
+ if (!idea) {
8095
+ ctx.print(" " + c.dim("usage: /build <what to build> [--style <id>] [--color dark|light] [--deploy]"));
8096
+ return { type: "handled" };
8097
+ }
8098
+ const params = [
8099
+ 'scope: "full"',
8100
+ styleId ? `styleId: "${styleId}"` : null,
8101
+ colorScheme ? `colorScheme: "${colorScheme}"` : null
8102
+ ].filter(Boolean).join(", ");
8103
+ const tail2 = deploy ? " Once the build succeeds, immediately deploy it to a live preview URL with deploy_app." : "";
7978
8104
  return {
7979
8105
  type: "prompt",
7980
- text: `Build this as a complete full-stack application using generate_app (scope: "full"): ${ctx.args}`
8106
+ text: `Build this as a complete full-stack application using generate_app (${params}): ${idea}${tail2}`
7981
8107
  };
7982
8108
  }
7983
8109
  },
8110
+ {
8111
+ name: "styles",
8112
+ summary: "list the Launch Styles (/styles [idea] \u2014 shows the auto-pick for an idea)",
8113
+ source: "builtin",
8114
+ run: async (ctx) => {
8115
+ const idea = ctx.args.trim();
8116
+ const url = `${webBaseUrl()}/api/zeta/styles${idea ? `?idea=${encodeURIComponent(idea)}` : ""}`;
8117
+ let data = null;
8118
+ try {
8119
+ const res = await fetch(url);
8120
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
8121
+ data = await res.json();
8122
+ } catch (e) {
8123
+ ctx.print(" " + c.red(`couldn't load styles: ${e.message}`));
8124
+ return { type: "handled" };
8125
+ }
8126
+ const styles = data?.styles ?? [];
8127
+ if (data?.recommended) {
8128
+ ctx.print(" " + c.bold("Recommended for your idea: ") + c.cyan(data.recommended.id) + c.dim(` \u2014 ${data.recommended.why}`));
8129
+ if (data.recommended.alternatives.length) {
8130
+ ctx.print(" " + c.dim(` also: ${data.recommended.alternatives.join(", ")}`));
8131
+ }
8132
+ ctx.print("");
8133
+ }
8134
+ ctx.print(" " + c.dim(`${styles.length} Launch Styles \u2014 pass one as /build \u2026 --style <id>:`));
8135
+ for (const s of styles) {
8136
+ ctx.print(" " + c.cyan(s.id.padEnd(20)) + c.dim(s.aesthetic));
8137
+ }
8138
+ return { type: "handled" };
8139
+ }
8140
+ },
7984
8141
  {
7985
8142
  name: "deploy",
7986
8143
  summary: "deploy the linked build to a live preview (/deploy [buildId])",
@@ -7991,7 +8148,7 @@ var BUILTINS = [
7991
8148
  ctx.print(" " + c.red("no build linked here") + c.dim(" \u2014 deliver/pull one, or /deploy <buildId>"));
7992
8149
  return { type: "handled" };
7993
8150
  }
7994
- ctx.print(" " + c.dim("deploying a live preview\u2026 charges one build credit (paid once per build)"));
8151
+ ctx.print(" " + c.dim("deploy & host with us (managed add-on) \u2014 or own the code on Pro and self-host"));
7995
8152
  const r = await deployBuild(webBaseUrl(), buildId, {
7996
8153
  poll: true,
7997
8154
  onPhase: (s) => ctx.print(" " + c.dim(`\xB7 ${s}`))
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-JXARRLAW.js";
79
79
 
80
80
  // src/cli.ts
81
81
  import { createInterface } from "readline/promises";
@@ -1384,7 +1384,9 @@ function showPaywall(loggedIn, webUrl) {
1384
1384
  row("Run " + c.cyan("wholestack login") + c.dim(" again."));
1385
1385
  }
1386
1386
  row("");
1387
- row(c.dim("Free: 3 delivered apps/month \xB7 then ") + c.cyan(`${webUrl.replace(/\/$/, "")}/pricing`));
1387
+ row(c.dim("Free: make \xB7 edit \xB7 preview (watermarked)"));
1388
+ row(c.dim("Pro $29/mo: ship unlimited \u2014 own the code"));
1389
+ row(c.cyan(`${webUrl.replace(/\/$/, "")}/pricing`));
1388
1390
  line(" " + c.cyan("\u2570" + "\u2500".repeat(w) + "\u256F"));
1389
1391
  line();
1390
1392
  }
@@ -1479,6 +1481,7 @@ ${c.bold("Ship")} ${c.dim("(go live \u2014 same credits + entitlements as the we
1479
1481
  ${c.bold("Launch")} ${c.dim("(the studio's Launch HQ \u2014 go-to-market, in the terminal)")}
1480
1482
  wholestack launch [buildId] [--prompt "\u2026"] draw the AI go-to-market plan
1481
1483
  wholestack launch-kit [buildId] show the build's startup kit (brand/pricing/copy/\u2026)
1484
+ wholestack styles [idea] list Launch Styles (+ the auto-pick for an idea)
1482
1485
  wholestack legal <terms|privacy|cookie|refund> generate a SaaS legal doc (Markdown \u2192 stdout)
1483
1486
  wholestack metrics your REAL traffic + revenue (PostHog + Stripe)
1484
1487
  wholestack waitlist [buildId] live waitlist signup count
@@ -1946,7 +1949,7 @@ async function runShipSubcommand(raw) {
1946
1949
  }
1947
1950
  if (verb === "deploy") {
1948
1951
  line();
1949
- line(c.cyan(" \u259F deploy") + c.dim(" \xB7 live preview \xB7 charges one build credit (paid once per build)"));
1952
+ line(c.cyan(" \u259F deploy") + c.dim(" \xB7 deploy & host with us (managed add-on) \u2014 or own the code on Pro and self-host"));
1950
1953
  const r2 = await deployBuild(base2, buildId, {
1951
1954
  poll: true,
1952
1955
  onPhase: (s) => line(c.dim(` \xB7 ${s}`))
@@ -2028,7 +2031,7 @@ async function runLaunchSubcommand(raw) {
2028
2031
  }
2029
2032
  async function runHqSubcommand(raw) {
2030
2033
  const verb = raw[0];
2031
- if (!["legal", "metrics", "waitlist", "businesses", "business"].includes(verb)) return null;
2034
+ if (!["legal", "metrics", "waitlist", "businesses", "business", "styles"].includes(verb)) return null;
2032
2035
  const base2 = webBaseUrl();
2033
2036
  const rest = raw.slice(1);
2034
2037
  const wantsJson = rest.includes("--json");
@@ -2048,6 +2051,33 @@ async function runHqSubcommand(raw) {
2048
2051
  stdout3.write(r2.markdown.trimEnd() + "\n");
2049
2052
  return 0;
2050
2053
  }
2054
+ if (verb === "styles") {
2055
+ const idea = rest.filter((a) => !a.startsWith("-")).join(" ").trim();
2056
+ let data;
2057
+ try {
2058
+ const res = await fetch(`${base2}/api/zeta/styles${idea ? `?idea=${encodeURIComponent(idea)}` : ""}`);
2059
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2060
+ data = await res.json();
2061
+ } catch (e) {
2062
+ line(c.red(` \u2717 couldn't load styles: ${e.message}`));
2063
+ return 1;
2064
+ }
2065
+ if (wantsJson) {
2066
+ stdout3.write(JSON.stringify(data, null, 2) + "\n");
2067
+ return 0;
2068
+ }
2069
+ if (data.recommended) {
2070
+ line(" " + c.bold("Recommended: ") + c.cyan(data.recommended.id) + c.dim(` \u2014 ${data.recommended.why}`));
2071
+ if (data.recommended.alternatives?.length) {
2072
+ line(" " + c.dim(` also: ${data.recommended.alternatives.join(", ")}`));
2073
+ }
2074
+ line("");
2075
+ }
2076
+ const styles = data.styles ?? [];
2077
+ line(" " + c.dim(`${styles.length} Launch Styles \u2014 build in one with: wholestack build \u2026 --style <id>`));
2078
+ for (const s of styles) line(" " + c.cyan(String(s.id).padEnd(20)) + c.dim(s.aesthetic));
2079
+ return 0;
2080
+ }
2051
2081
  if (verb === "metrics") {
2052
2082
  const r2 = await readMetrics(base2);
2053
2083
  if (!r2.ok || !r2.metrics) {
@@ -2168,9 +2198,12 @@ async function main() {
2168
2198
  let token = process.env.ZETA_API_KEY;
2169
2199
  let access = await verifyAccess(webBaseUrl(), token);
2170
2200
  if (!access.active && stdin.isTTY) {
2201
+ line();
2202
+ line(" " + c.bold("Sign in to use Wholestack"));
2203
+ line(" " + c.dim(token ? "Your session expired." : "The agent is free \u2014 make, edit & preview unlimited. Sign in to start."));
2171
2204
  const rl = createInterface({ input: stdin, output: stdout3 });
2172
2205
  const ans = (await rl.question(
2173
- "\n " + (token ? c.dim("Token invalid or expired. ") + "Log in again? " : "Log in now? ") + c.dim("[Y/n] ")
2206
+ " " + c.dim("Open the browser to sign in with Google? ") + c.dim("[Y/n] ")
2174
2207
  )).trim().toLowerCase();
2175
2208
  rl.close();
2176
2209
  if (ans === "" || ans === "y" || ans === "yes") {
@@ -2186,12 +2219,9 @@ async function main() {
2186
2219
  showPaywall(Boolean(token), webBaseUrl());
2187
2220
  exit(1);
2188
2221
  }
2189
- if (!access.paid && access.freeDeliveries) {
2190
- const fd = access.freeDeliveries;
2222
+ if (!access.paid) {
2191
2223
  line(
2192
- c.dim(
2193
- ` free plan \xB7 ${fd.remaining}/${fd.limit} delivered apps left this month \xB7 `
2194
- ) + c.cyan(`${webBaseUrl().replace(/\/$/, "")}/pricing`)
2224
+ c.dim(" Free plan \xB7 make \xB7 edit \xB7 preview unlimited \xB7 ship with Pro \u2192 ") + c.cyan(`${webBaseUrl().replace(/\/$/, "")}/pricing`)
2195
2225
  );
2196
2226
  } else if (access.paid && access.tier) {
2197
2227
  line(c.dim(` ${access.tier} plan`));
package/dist/index.d.ts CHANGED
@@ -470,6 +470,8 @@ declare function buildTools(ctx: ToolContext): {
470
470
  buildModel: "zeta-g1" | "zeta-g1-max";
471
471
  keep: boolean;
472
472
  themeId?: "slate-minimal" | "clean-inter" | "friendly-nunito" | "geometric-jakarta" | "techno-mono" | undefined;
473
+ styleId?: string | undefined;
474
+ colorScheme?: "dark" | "light" | undefined;
473
475
  }, BuildResult | {
474
476
  ok: boolean;
475
477
  liveUrl: string | undefined;
@@ -523,6 +525,29 @@ declare function buildTools(ctx: ToolContext): {
523
525
  deploy_app: ai.Tool<{
524
526
  buildId?: string | undefined;
525
527
  }, DeployResult>;
528
+ personalize_app: ai.Tool<{
529
+ buildId?: string | undefined;
530
+ businessName?: string | undefined;
531
+ tagline?: string | undefined;
532
+ logoSrc?: string | undefined;
533
+ heroImage?: string | undefined;
534
+ pricingTiers?: {
535
+ name: string;
536
+ price: string;
537
+ period?: string | undefined;
538
+ features?: string[] | undefined;
539
+ }[] | undefined;
540
+ }, {
541
+ ok: boolean;
542
+ error: string;
543
+ buildId?: undefined;
544
+ note?: undefined;
545
+ } | {
546
+ ok: boolean;
547
+ buildId: string;
548
+ note: string;
549
+ error?: undefined;
550
+ }>;
526
551
  promote_app: ai.Tool<{
527
552
  domain?: string | undefined;
528
553
  buildId?: string | undefined;
@@ -593,8 +618,8 @@ declare function buildTools(ctx: ToolContext): {
593
618
  }>;
594
619
  generate_legal: ai.Tool<{
595
620
  kind: "terms" | "privacy" | "cookie" | "refund";
596
- brief?: string | undefined;
597
621
  businessName?: string | undefined;
622
+ brief?: string | undefined;
598
623
  }, {
599
624
  ok: boolean;
600
625
  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-JXARRLAW.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.5.8",
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",