pyreon

@pyreon/create-zero is the interactive project scaffolder for Pyreon Zero. One command walks you through template choice, rendering mode, deployment adapter, feature selection, backend integrations, AI tooling, and framework-migration compat — then writes a ready-to-run project. Every prompt also has a flag, so the whole flow is scriptable for CI, templates repos, and --yes one-liners.

@pyreon/create-zerostable

Installation

You don't install it — you invoke it through your package manager's create command. It ships under two bin aliases (create-pyreon-app is canonical, create-zero is the back-compat alias) but both run the same scaffolder.

npm create @pyreon/zero@latest my-app
bun create @pyreon/zero my-app
pnpm create @pyreon/zero my-app
yarn create @pyreon/zero my-app

Then start the dev server:

cd my-app
npm install
npm run dev
cd my-app
bun install
bun run dev
cd my-app
pnpm install
pnpm run dev
cd my-app
yarn
yarn dev

The scaffolder never installs dependencies itself — it prints cd, install, and dev as next steps and stops. (It accepts --install / --no-install as no-ops, since people pass them out of habit from create-vite / create-next-app.)

Why a scaffolder?

A Pyreon Zero project has several genuinely-computed pieces — the dependencies map depends on which features you pick, the vite.config.ts plugins array depends on rendering mode + adapter + compat shim, and the deploy artefacts depend on the target platform. Wiring all of that by hand means cross-referencing docs and keeping versions in sync. The scaffolder does it in one pass:

  • Feature → dependency resolution — pick query and it adds @pyreon/query + @tanstack/query-core; pick forms and it adds @pyreon/form + @pyreon/validation + zod. Every @pyreon/* dep is pinned to the same monorepo version as the scaffolder itself, so the set is always internally consistent.

  • Mode + adapter → vite.config.ts — the generated config imports the matching *Adapter() factory and sets zero({ mode, ssr }) for you.

  • Platform → deploy artefacts — vercel.json, wrangler.toml, Dockerfile, etc. land alongside your code.

  • AI tooling — generates CLAUDE.md, .mcp.json, Cursor rules, and more from one shared "Pyreon principles" body so the guidance stays consistent across editors.

Quick Start

The fastest non-interactive paths, by template:

# Standard app (store + query + forms), SSR streaming, Vercel
npm create @pyreon/zero@latest my-app -- --preset standard --yes

# SSG markdown blog on Cloudflare Pages
npm create @pyreon/zero@latest my-blog -- --template blog --adapter cloudflare --yes

# SaaS-shape dashboard with Supabase + Resend
npm create @pyreon/zero@latest my-saas -- --template dashboard \
  --integrations supabase,email --yes

# Bun-workspaces monorepo
npm create @pyreon/zero@latest my-mono -- --template monorepo --preset standard --yes

The interactive flow

Run without --yes (or with any prompt's flag omitted) and you get an ordered series of prompts. Every prompt is skipped when its flag is supplied — flags and prompts are interchangeable.

  1. Project name — the positional argument, or a text prompt. The target directory must not already exist (it bails if it does). Required up front when using --yes.

  2. Template — app / blog / dashboard / monorepo.

  3. Rendering mode — ssr-stream / ssr-string / ssg / isr / spa. Skipped for templates that force a mode (blog → SSG, dashboard → SSR streaming).

  4. Deployment target — filtered to the adapters the chosen template supports.

  5. Feature preset — pick a preset, or Custom to drop into a grouped multiselect of all 22 features (8 categories).

  6. Package imports — @pyreon/meta single barrel, or individual packages.

  7. Backend integrations — Supabase, Email (Resend), or none.

  8. AI tooling — multi-select of rule files (MCP, CLAUDE.md, Cursor, Copilot, AGENTS.md).

  9. Compat mode — native Pyreon, or a React / Vue / Solid / Preact migration shim.

  10. Lint — include @pyreon/lint (the prompt advertises "59 Pyreon-specific rules").

  11. Typed routes — <Link href> autocompletes real route paths and rejects typos at compile time (default on).

Cancelling at any prompt (Ctrl-C) aborts cleanly.

Templates

Four curated starting points, selected by --template or interactively. Each declares its own default rendering mode, default feature set, supported adapters, and (for dashboard) preselected integrations.

TemplateDefault modeMode forced?Default featuresWhat you get
appSSR streamingnostore + query + formsFull-featured starter — counter, posts list + detail, an (admin) route group, _layout / _error / _loading, and api/ route handlers.
blogStatic (SSG)yes(none)Markdown-style TSX posts in src/content/posts/, an RSS feed at /api/rss, SEO-ready. Static-first — node / bun adapters are excluded.
dashboardSSR streamingyesstore + query + forms + tableSaaS shape — marketing landing → auth-gated app/* routes (dashboard, users, invoices, settings) → an invoice PDF/email export demo built on @pyreon/document-primitives. Server-required, so static is excluded. Preselects both integrations.
monorepoSSR streamingnostore + query + formsBun workspaces shell — apps/web/ (a full app-shaped project) + packages/ui/ + packages/types/.
npm create @pyreon/zero@latest my-app -- --template dashboard
npm create @pyreon/zero@latest my-blog -- --template blog

Generated structure (flat templates)

The app template scaffolds the following. blog and dashboard share the same shell (entry points, config, public/) with different src/routes/ content.

my-app/
├── package.json            # generated — deps computed from your selections
├── vite.config.ts          # generated — plugins computed from mode + adapter + compat
├── tsconfig.json           # strict, jsxImportSource: @pyreon/core, moduleResolution: bundler
├── index.html              # #app mount point + theme-flash-prevention script
├── env.d.ts                # ambient types for virtual:zero/* modules
├── .gitignore
├── README.md
├── public/
│   └── favicon.svg
└── src/
    ├── entry-client.ts     # startClient({ routes }) from @pyreon/zero/client
    ├── entry-server.ts     # createServer({ ... }) with the SSR mode baked in
    ├── global.css
    └── routes/             # file-based routing
        ├── index.tsx
        ├── about.tsx
        ├── counter.tsx
        ├── _layout.tsx
        ├── _error.tsx
        ├── _loading.tsx
        ├── (admin)/dashboard.tsx
        ├── api/health.ts
        ├── api/posts.ts
        └── posts/
            ├── index.tsx
            └── [id].tsx

There is no pyreon.config.ts — all configuration lives in vite.config.ts via the zero({ ... }) plugin and in tsconfig.json. Selecting the store feature adds src/stores/app.ts (and swaps in a store-aware _layout.tsx); selecting feature adds src/features/posts.ts + a posts/new.tsx route. Integration and AI selections add their own files (see below).

Monorepo layout

--template monorepo scaffolds the web app first (running the full flat pipeline against apps/web/ with the app shape), then wraps it in a Bun workspaces root:

my-mono/
├── package.json            # workspaces: ["apps/*", "packages/*"] + proxy scripts
├── tsconfig.json           # root TS settings
├── README.md
├── .gitignore
├── apps/
│   └── web/                # full app-shaped project
│       ├── package.json    # name: "web" + workspace deps on the shared packages
│       └── …               # everything the `app` template produces
└── packages/
    ├── ui/                 # @my-mono/ui — shared component stub
    │   ├── package.json    # deps: { "@my-mono/types": "workspace:^" }
    │   ├── tsconfig.json
    │   └── src/index.ts    # Button + ButtonProps stub
    └── types/              # @my-mono/types — framework-agnostic shared types
        ├── package.json
        ├── tsconfig.json
        └── src/index.ts    # ButtonVariant + User stubs

The @<projectName>/ scope is derived from the project name automatically — no extra prompt. The root package.json carries no dependencies; it's a dispatcher whose dev / build / preview scripts proxy to the web app via bun run --filter='web' …, and typecheck runs --filter='*'. All your feature / adapter / integration / AI / lint choices apply to the inner web app (which is always app-shaped today).

Rendering modes

Set with --mode (ignored when the template forces a mode). The mode maps directly to the generated vite.config.ts and entry-server.ts.

Modezero({ ... })When to use
ssr-streammode: 'ssr', ssr: { mode: 'stream' }Recommended. Progressive HTML with Suspense — best TTFB.
ssr-stringmode: 'ssr'Buffered HTML — simpler, slower first byte.
ssgmode: 'ssg'Pre-rendered at build time. No runtime server.
isrmode: 'isr'Static-first with periodic revalidation (a server-side SWR cache).
spamode: 'spa'Client-only — no server rendering.

At the server level the ssr.mode field only has stream / string; ssg, isr, and spa all fall back to string (SSG renders once at build time; ISR caches complete HTML documents, and a stream can't be cached-then-replayed; SPA never SSRs). See Zero → Rendering modes for the full semantics.

Deployment adapters

Pick a target during the prompt or pass --adapter. The prompt is filtered to the adapters the chosen template supports. Each adapter writes its platform deploy artefacts and (where applicable) injects the matching *Adapter() factory into the generated vite.config.ts.

AdapterVite factoryFiles writtenEnv keys
vercelvercelAdapter()vercel.json—
cloudflarecloudflareAdapter()wrangler.toml (the adapter writes the authoritative dist/_routes.json at build time)—
netlifynetlifyAdapter()netlify.toml (render-mode-aware — SSR modes publish dist/publish + wire the ssr function from dist/netlify/functions; SSG/SPA publish dist)—
nodenodeAdapter()Dockerfile, .dockerignorePORT
bunbunAdapter()Dockerfile (Bun-based), .dockerignorePORT
static(none)(none — dist/ after build is the whole site)—
npm create @pyreon/zero@latest my-app -- --adapter cloudflare

Features

22 Pyreon fundamentals can be enabled per project. The interactive flow offers a preset shortcut first; choosing Custom drops you into a multiselect grouped into 8 categories for discoverability.

Presets

Four atomic shortcuts via --preset. Each is a (features, mode, adapter) triple:

PresetFeature setModeAdapter
minimal(none)spastatic
standardstore + query + formsssr-streamvercel
dashboardstandard + table + chartsssr-streamvercel
fullevery feature (22)ssr-streamvercel
npm create @pyreon/zero@latest my-app -- --preset standard --yes
npm create @pyreon/zero@latest my-app -- --preset full --yes

Feature categories

When you pick Custom, the multiselect groups features for browsing:

CategoryFeatures
State managementstore, state-tree, storage, url-state
Forms + validationforms, feature
Data fetching + transformsquery, rx
UI primitives + animationstyler, elements, animations, coolgrid
Collections + tablestable, virtual
Interactive widgetscharts, code, flow, toast
Internationalization + accessibilityi18n, hotkeys, permissions
Utility hookshooks

Each feature pulls its package(s) into dependencies. Some pull peers — query adds @tanstack/query-core, forms adds @pyreon/validation + zod, and the feature (CRUD) bundle pulls store + query + forms + their peers.

Atomic add / remove

--with-<feature> adds one feature; --no-<feature> removes one. They compose on top of whatever the base set is (preset, explicit --features, or template default under --yes):

# Standard preset, plus i18n, minus forms
npm create @pyreon/zero@latest my-app -- --preset standard --with-i18n --no-forms --yes

--no-X always wins over --with-X for the same feature (set-subtraction semantics). An unknown feature in --with- / --no- is a hard error that prints the known list.

Explicit feature list

--features <csv> is an explicit set that overrides --preset entirely (only --with- / --no- still compose on top):

npm create @pyreon/zero@latest my-app -- --features store,query,i18n --with-toast --yes

Resolution order

When several feature sources are combined, the highest-priority source wins as the base, then --with-X / --no-X overlay on top:

  1. --features <csv> — explicit list, ignores preset + template default

  2. --preset <id> — preset's feature set as the base (skips the feature prompts)

  3. --yes (no preset / features) — the chosen template's default features

  4. Interactive — preset prompt → Custom → grouped multiselect

Package imports

The --packages flag (prompt: "Package imports") chooses how features reach your code:

ValueBehaviour
metaAdds @pyreon/meta — a single barrel re-exporting everything, tree-shaken at build. Simpler imports.
individualInstalls only the packages your selected features need — smaller node_modules.
npm create @pyreon/zero@latest my-app -- --packages individual --yes

Either way, the specific feature packages are still added to dependencies (templates import them by their real package name, e.g. @pyreon/query); meta just adds the barrel on top for ergonomic imports.

Backend integrations

Two scaffolders that write plain files into your project — no Pyreon-side wrapper packages and no version coupling. You own the code and update it independently of Pyreon releases. Select with --integrations (csv) or the multiselect.

IntegrationFiles writtenDependenciesEnv keys
supabasesrc/lib/supabase.ts (+ src/lib/auth.ts, src/lib/db.ts on the dashboard template)@supabase/supabase-jsSUPABASE_URL, SUPABASE_ANON_KEY
emailsrc/lib/email.ts, src/emails/welcome.tsx, src/routes/api/email/welcome.tsresend, @pyreon/document-primitives, @pyreon/document, @pyreon/connector-documentRESEND_API_KEY, EMAIL_FROM

Each integration appends its env keys to a generated .env.example. On the dashboard template, Supabase's auth.ts / db.ts replace the template's in-memory stubs (same contract). The dashboard template preselects both integrations; on app / blog they're off by default but selectable.

npm create @pyreon/zero@latest my-saas -- --template dashboard --integrations supabase,email --yes

The email integration showcases the document-primitives angle: the same <DocDocument> / <DocSection> / <DocText> tree renders in the browser and exports to email HTML via @pyreon/document-primitives — one author surface for many output formats.

AI tooling

Generate per-tool rule files so AI assistants understand Pyreon's conventions. All share a canonical "Pyreon principles" body, so the guidance stays consistent across editors. Select with --ai (csv) or the multiselect.

OptionFile writtenDefault
mcp.mcp.json (also adds @pyreon/mcp as a dev dep)✓
claudeCLAUDE.md (includes a bun run doctor line)✓
cursor.cursor/rules/pyreon.md—
copilot.github/copilot-instructions.md—
agentsAGENTS.md—
npm create @pyreon/zero@latest my-app -- --ai mcp,claude,cursor --yes

Compat mode

Migrating from another framework? --compat (or the "Migrating from another framework?" prompt) configures @pyreon/vite-plugin with the matching shim and adds the matching compat package.

ValuePackage addedLets you write
none—Native Pyreon (recommended)
react@pyreon/react-compatuseState, useEffect, …
vue@pyreon/vue-compatref, computed, watch, …
solid@pyreon/solid-compatcreateSignal, createEffect, …
preact@pyreon/preact-compatuseState, signals, …
npm create @pyreon/zero@latest my-app -- --compat react --yes

Lint

--lint / --no-lint (prompt: "Include @pyreon/lint?", default yes) toggles @pyreon/lint. When enabled, the scaffolder adds the dev dep, writes a .pyreonlintrc.json with the recommended preset, and adds a lint script (pyreon-lint .).

npm create @pyreon/zero@latest my-app -- --no-lint --yes

Generated package.json scripts

Every scaffolded project gets these scripts (lint adds one more when enabled):

ScriptCommandPurpose
devzero devDev server with HMR
buildzero buildProduction build
previewzero previewPreview the production build locally
doctorzero doctorProject health audit (React-pattern + anti-pattern detection)
doctor:fixzero doctor --fixAuto-fix what the audit can
doctor:cizero doctor --ciExit non-zero on findings (CI gate)
lintpyreon-lint .Only present when @pyreon/lint is enabled

The dev dependencies always include @pyreon/vite-plugin, @pyreon/zero-cli (the zero CLI), typescript, and vite. See Zero CLI for the zero command surface.

CLI flags

Every prompt maps to a flag, so the entire flow is scriptable. Run --help (or -h) for the built-in usage text.

FlagValues
[name]Positional project name (required with --yes)
--template <id>app / blog / dashboard / monorepo
--mode <id>ssr-stream / ssr-string / ssg / spa / isr
--typed-routes / --no-typed-routesTyped routes (<Link href> autocomplete + typo rejection); default on
--adapter <id>vercel / cloudflare / netlify / node / bun / static
--preset <id>minimal / standard / dashboard / full (composes with --with-X / --no-X)
--features <csv>e.g. store,query,forms — overrides --preset entirely
--with-<feature>atomic add, e.g. --with-store --with-i18n
--no-<feature>atomic remove, e.g. --no-forms
--integrations <csv>supabase,email
--ai <csv>mcp,claude,cursor,copilot,agents
--compat <id>none / react / vue / solid / preact
--packages <id>meta (single barrel) / individual (selected packages only)
--lint / --no-linttoggle @pyreon/lint
--yesskip every prompt that has a usable default
--install / --no-installaccepted no-ops (the scaffolder never installs)
--help, -hprint usage and exit

--packages also accepts the alias --package-strategy (--pm is rejected: the package manager is whichever one ran create). --mode that a mode-forcing template (blog, dashboard) cannot honor, and --adapter static with an SSR or ISR mode, are refused rather than silently overridden. An unknown flag, an invalid enum value, or an extra positional argument fails fast with an actionable message and a non-zero exit code.

Worked examples

# Standard app, defaults for everything else
npm create @pyreon/zero@latest my-app -- --yes

# Dashboard + Supabase + Resend + full AI tooling, on Vercel
npm create @pyreon/zero@latest my-saas -- --template dashboard \
  --adapter vercel \
  --integrations supabase,email \
  --ai mcp,claude,cursor \
  --yes

# Standard preset, plus i18n, minus forms
npm create @pyreon/zero@latest my-app -- --preset standard --with-i18n --no-forms --yes

# Blog on Cloudflare Pages
npm create @pyreon/zero@latest my-blog -- --template blog --adapter cloudflare --yes

# Monorepo with the full preset, individual package imports
npm create @pyreon/zero@latest my-mono -- --template monorepo \
  --preset full --packages individual --yes

# React migration target, no lint
npm create @pyreon/zero@latest my-app -- --compat react --no-lint --yes

After scaffolding

cd my-app
bun install        # or npm install / pnpm install / yarn
bun run dev        # zero dev — HMR dev server

The generated project is a complete Pyreon Zero app: file-based routing, SSR/SSG/SPA per your mode, @pyreon/vite-plugin wired up, strict TypeScript with jsxImportSource: @pyreon/core, and your selected features, adapter, integrations, and AI rule files all in place. From here, see:

  • Zero — the meta-framework the scaffolder targets (routing, rendering modes, adapters, server actions).

  • Zero CLI — the zero command your scripts call (dev / build / preview / doctor).

  • SSG — static generation (the blog template's mode).

  • Deployment — per-platform deploy notes for each adapter.

  • Create Multiplatform — the scaffolder for iOS + Android + web targets, when you need native.

Reference

FlagDefault (interactive)Notes
--templateprompt (app under --yes)Drives mode/adapter/feature/integration defaults
--modetemplate defaultSkipped when the template forces a mode
--adaptertemplate defaultPrompt filtered to template-supported adapters
--presetprompt (Custom)minimal / standard / dashboard / full
--featurestemplate defaultcsv; overrides --preset; not validated
--with-<feature>—composes on top of the base set; errors on unknown
--no-<feature>—composes on top; wins over --with- for the same feature
--integrationstemplate default ([], both for dashboard)supabase / email
--aimcp,claudemcp / claude / cursor / copilot / agents
--compatnonereact / vue / solid / preact shim
--packagespromptmeta / individual (alias: --package-strategy)
--linttrue--no-lint to opt out
--yes—requires a positional name
@pyreon/create-zero