Quickstart
A running, browsable app in one command — with the one mental model you need to keep it safe to change afterwards.
Prerequisite: Node ≥ 22. That's it — Docker only enters when you get to
deploy.md.
1. One command, no install #
cd ~/code
npx maxstack@latest start "a bug tracker for small teams"That scaffolds the project, lands the spec-ops your description implies, seeds sample rows, and serves the app — then prints the URL. Open it and there is something there: pages with rows in them, not an empty table.
Three things worth knowing about what just happened, because none of it is magic and all of it is inspectable:
- The starting spec is reviewable, not conjured. Every entity and page
arrived as a typed spec-op in the op log, recorded with
origin: ai./workbenchshows them as machine-authored, same as any later change an agent proposes. - The rows are labelled demo data. They carry a
demochip in every list and a notice at the top of the app, andnpx maxstack@latest demo --clearremoves exactly those rows and nothing else. You will never wonder which rows were yours. - With no API key it is still deterministic. The description is compiled by
a built-in compiler; an
ANTHROPIC_API_KEY(orOPENAI_API_KEY) lets a model draft a better-fitting model instead. Either way the result is a spec you review.
--no-seed starts empty, and --no-dev stops after generating the app tree.
Or scaffold without the starting spec #
start is init plus the three steps most people take next. If you want the
bare project — no entities, no rows, no server:
npx maxstack@latest init # prompts for a name, scaffolds ./<kebab-case-name>
cd <your-project>That is the whole install step: npx fetches the CLI and the prebuilt web
runtime it pins (maxstack-runtime) into npm's cache and runs it. No -g, no
global state, nothing to uninstall — and everything below works standalone, with
no repo checkout and no pnpm.
You now have a spec, an app tree generated from it, a validate gate, and a
.mcp.json that hands the platform tools to any agent you point at the project.
A project is its own directory and can live anywhere — you do not scaffold
inside a maxstack checkout.
Scaffolded through
npx, the project's.mcp.jsonand edit-guard hook are written asnpx -y maxstack@<version> …rather than a baremaxstack, so they resolve without a global install and stay pinned to the version that created the project.
If you'll come back to this often #
A global install drops the prefix from every command below, and is worth it once you have more than one project:
npm install -g maxstack
maxstack --versionEverything in this guide works either way; where it says maxstack <verb>, read
npx maxstack@latest <verb> if you skipped the install.
Right after a release npm's cached metadata can lag a few minutes and resolve an older version. If
--versionisn't what you expected, pin it:npx maxstack@<version>(ornpm install -g maxstack@<version>).
2. Run it #
start already left a server running; Ctrl-C and maxstack dev bring it back
(and it is what you run after an init):
maxstack dev # leave runningOpen the URL it prints. Two surfaces are worth knowing immediately:
/admin— CRUD over everything in your spec, derived from it directly. No per-entity code was written to make this exist./workbench— the review queue. Every AI-proposed change waits here for your accept/reject.
3. Make a change #
Leave dev running and use a second shell. Say the app needs tasks:
maxstack add-entity task --field title:string! --field done:bool
maxstack add-page task
maxstack demo # sample rows, so the page isn't an empty tableRefresh the browser — /task is there with rows in it, and /admin knows about
tasks. (maxstack demo only has something to seed once entities exist, which is
why it comes after this step and not before; maxstack demo --clear takes those
rows back out again.)
What actually happened: each command compiled to a typed spec-op
(data.addEntity, page.addPage), the op was validated and appended to the
op-log with its author recorded, and the generators derived the code. You did
not hand-edit a generated file, and you never will — that is the whole trick.
Changes land accepted and regenerate as they go — no flags. That is
"reviewMode": "auto", which maxstack init writes into maxstack.json, and
it settles a write by how it was made, not by who made it: a CLI verb run by
an agent lands accepted too, and apply_spec_change over MCP lands accepted by
design. So on a fresh project /workbench shows Review queue (0) because
nothing is queued, not because the queue is broken.
Set "reviewMode": "review" in maxstack.json to get the review-first loop:
every op/add-entity/add-field then lands suggested and waits for you in
/workbench until you Accept it, and --accept --gen is how you opt a single
write back out.
Check the gate whenever you want reassurance:
maxstack validate # spec valid · manifest intact · regen safe4. Point an agent at it #
The project's .mcp.json already registers the platform tools over stdio, so
an MCP-capable agent started in this directory has them in every session — no
port, no ordering against maxstack dev. From Claude Code, just start claude
in the project and ask for a feature; it will propose spec-ops that queue up in
/workbench for you.
If your client has no MCP, the CLI verbs above reach the identical op path.
Nothing is agent-only. See mcp-reference.md.
Where to go next #
| You want to | Read |
|---|---|
| The real walkthrough, including the 10th and 50th change | user-guide.md |
| What every CLI flag does | cli-reference.md |
| The full spec-op vocabulary with arg schemas | spec-ops.md |
| The agent surface | mcp-reference.md |
| Owning generated code — slots, eject, the manifest | ownership.md |
| Getting it deployed | deploy.md |
| How the 60-second entry claim is measured | measurement.md |
The one thing to remember #
The spec is the source of truth, and nothing grounds until a human decides.
Agents propose typed ops; proposals land suggested; you accept or reject them
in /workbench — that last step is the loop you get with "reviewMode": "review", and a fresh project ships auto, which accepts as it lands;
generators build only from what's accepted; generated files are never clobbered
once you touch or eject them.
When you need code the spec can't express, you don't fight it — you fill a slot or eject the route and own it outright. That ladder is §5 of the user guide, and it is the canonical statement of how change works here.