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.
- package/dist/{chunk-TLYCOIEA.js → chunk-JXARRLAW.js} +173 -16
- package/dist/cli.js +40 -10
- package/dist/index.d.ts +26 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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 = {
|
|
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
|
|
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
|
|
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
|
|
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|
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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>
|
|
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 (
|
|
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("
|
|
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-
|
|
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:
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wholestack",
|
|
3
|
-
"version": "0.5.
|
|
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",
|