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/README.md +7 -7
- package/dist/{chunk-TLYCOIEA.js → chunk-C56OI76J.js} +833 -215
- package/dist/cli.js +150 -39
- package/dist/index.d.ts +27 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/fix-failing-build.md +7 -1
- package/skills/review-before-ship.md +6 -0
- package/skills/ship-a-saas.md +7 -1
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-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(
|
|
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(
|
|
477
|
-
|
|
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(
|
|
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)
|
|
804
|
-
|
|
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(
|
|
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(
|
|
855
|
-
|
|
856
|
-
|
|
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")
|
|
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(
|
|
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:
|
|
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")
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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))
|
|
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)
|
|
2124
|
-
|
|
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
|
-
"
|
|
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
|
|
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 = {
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wholestack",
|
|
3
|
-
"version": "0.
|
|
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.
|
package/skills/ship-a-saas.md
CHANGED
|
@@ -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.
|