@pyreon/zero-cli ships the zero binary — the dev/build CLI for apps built on @pyreon/zero. It is a thin, framework-aware wrapper over Vite: zero dev boots Vite's dev server and prints the file-system route table, zero build runs the production build (one vite build — the zero() plugin chain owns the whole pipeline), and the rest (preview, doctor, context, create) round out the day-to-day workflow.
Installation
npm install -D @pyreon/zero-clibun add -D @pyreon/zero-clipnpm add -D @pyreon/zero-cliyarn add -D @pyreon/zero-cliMost apps never install this directly — bun create @pyreon/zero my-app (see @pyreon/create-zero) adds it as a dev dependency and wires the package.json scripts for you.
The package installs a single binary, zero. Its dependencies (@pyreon/zero, @pyreon/server, @pyreon/cli, @pyreon/create-zero, plus vite and cac) are pulled in transitively — you don't list them yourself.
Why a dedicated CLI?
Vite alone doesn't know about Zero's conventions — file-system routing, per-route render modes, SSR/SSG server builds, or deploy adapters. zero closes that gap with the smallest possible surface:
zero devbootsvite.createServer()and then prints the discovered route tree, so you see your page and API routes the moment the server is up.zero buildruns onevite buildand lets thezero()plugin chain own the entire production pipeline — client bundle, SSR/ISR server bundle + production template, SSG prerendering, deploy-adapter staging. It exists for the scaffoldedbun run buildscript and symmetric UX, not to add build steps.zero previewserves the built client bundle (dist/client/when a node/bun-adapter build staged one, otherwise yourbuild.outDir).zero doctor/zero contextdelegate to@pyreon/cli's health gates and AI-context generator, scoped to a Zero project.
Everything heavier — plugins, aliases, build tuning — lives in vite.config.ts. The CLI deliberately exposes only the few flags that make sense to flip per-invocation.
Quick Start
A scaffolded project wires these into package.json scripts:
{
"scripts": {
"dev": "zero dev",
"build": "zero build",
"preview": "zero preview",
"doctor": "zero doctor"
}
}So the everyday loop is:
bun run dev # → zero dev (dev server + route table)
bun run build # → zero build (production build)
bun run preview # → zero preview (serve the build locally)
bun run doctor # → zero doctor (Pyreon health gates)You can also invoke the binary directly:
zero dev # dev server on :3000 with HMR + route table
zero build # production build (mode + adapter from vite.config.ts)
zero preview # serve the production build for smoke-testing
zero doctor # run Pyreon health gates
zero context # write an AI-readable project summary
zero create my-app # scaffold a new projectEvery command except create takes an optional [root] positional argument — the project directory to operate in (defaults to the current working directory). Pass it when the project lives in a subdirectory:
zero dev ./apps/web
zero build packages/siteCommands
zero dev [root]
Start the Vite dev server with HMR, then print a compact startup banner: a one-line route summary, the Local URL, and the ready time.
| Flag | Description |
|---|---|
--port <port> | Server port. Defaults to 3000 (see resolution order below). |
--host [host] | Bind host. Pass --host alone to bind 0.0.0.0 (exposes the server on your LAN); pass --host <addr> for a specific interface. Omitted → Vite's default (localhost only). |
--open | Open the browser on first listen. |
--routes | Print the full route table instead of the collapsed one-line summary. |
zero dev # localhost:3000 — collapsed route summary
zero dev --port 5173 # explicit port
zero dev --host # bind 0.0.0.0 — reachable from other devices
zero dev --host 192.168.1.50 # bind a specific interface
zero dev --open # auto-open the browser
zero dev --routes # expand the full route tablePort resolution order (highest precedence first):
1. --port <port> CLI flag
2. zero({ port }) vite.config.ts plugin config
3. 3000 framework defaultThe CLI intentionally has no hardcoded default on the --port flag. When you omit it, the value falls through to zero({ port }) from your vite.config.ts, and only then to 3000. This is what lets an app with zero({ port: 8080 }) work without also passing --port on every invocation.
The route banner is collapsed to a one-line summary by default — e.g. Routes SSR 15 · SSG 4 · API 1 — with per-mode page counts (SSR / SSG / SPA / ISR) plus the API count. Pass --routes to expand the full table (one line per route, API routes listed separately under their URL pattern). The banner is informational — if route scanning fails for any reason, the dev server still starts.
The Local URL and ready time are always printed last, after the route banner. This keeps them visible even when a large app is run under a wrapping task runner such as bun run --filter <app> dev, whose runner elides the middle of long child output and keeps only the tail — a full route table printed first would push the URL off the top.
The banner honors NO_COLOR / FORCE_COLOR and falls back to plain text on a non-interactive stdout, so piped output (bun run dev > log, CI) stays free of raw escape codes.
zero build [root]
Run the full production build — exactly vite build, run once. The zero() plugin chain from your vite.config.ts owns the entire pipeline (this is the same battle-tested path a plain vite build of any Zero app takes):
Client bundle →
dist/(your project'sbuild.outDir).SSR/ISR (
mode: 'ssr' | 'isr'): server bundle →dist/server/entry-server.js(yoursrc/entry-server.tswhen present, a synthetic entry otherwise — zero-config apps get a server bundle too) plusdist/server/template.html, the built clientindex.htmlstaged next to the entry as the production SSR template (hashed asset refs — this is what makes the deployed page hydrate).SSG (
mode: 'ssg', plus hybrid static-first routes in server modes): prerendered per-route HTML intodist/.Deploy adapter (Vercel / Netlify / Cloudflare / Node / Bun / static): platform artefacts staged into the same
dist/tree — e.g. the node adapter emitsdist/index.js+ a cleandist/client/copy of the client assets, Vercel emitsdist/.vercel/output/, Cloudflare emitsdist/_worker.js+_routes.json.
On success it prints Build completed in <N>ms. On failure it logs Build failed: <message> and exits with code 1 — including when an explicitly configured adapter's build step fails (an auto-selected adapter failure is reported but doesn't fail the build; the server bundle itself is still usable).
zero build # mode + adapter resolved from vite.config.ts
zero build ./apps/web # build a project in a subdirectoryzero preview [root]
Serve the production build locally for smoke-testing, via vite preview.
| Flag | Description |
|---|---|
--port <port> | Server port. Same resolution order as dev (--port → zero({ port }) → 3000). |
--host [host] | Bind host. --host alone binds 0.0.0.0. |
zero build && zero preview # build, then serve it
zero preview --port 4173 # explicit preview port
zero preview --host # expose the preview on your LANzero build puts the client bundle at your project's build.outDir (dist/ by default), which vite preview serves natively. When a dist/client/ directory exists — the node/bun adapters stage a clean copy of the client assets there, next to the emitted dist/index.js runner — zero preview prefers it, so the preview doesn't also expose the server bundle / adapter scaffolding at the dist/ top level.
zero doctor [root]
Run Pyreon's project health gates against the project. Delegates to @pyreon/cli's doctor — it surfaces React-isms that don't apply in Pyreon (useState, useEffect, className), Pyreon-specific anti-patterns (signal-write-as-call, <For> without by, …), lint findings, distribution issues, and more.
| Flag | Description |
|---|---|
--fix | Auto-fix the fixable findings (e.g. className → class). |
--json | Emit machine-readable JSON instead of the formatted report. |
--ci | CI mode — exit with code 1 when there are errors. |
zero doctor # formatted report against the cwd
zero doctor --fix # apply auto-fixes
zero doctor --json # JSON output for tooling
zero doctor --ci # non-zero exit on errors — wire into CI
zero doctor ./apps/web # check a project in a subdirectoryzero context [root]
Generate an AI-readable project-context summary (a structured view of routes, exports, and configuration) by delegating to @pyreon/cli's generateContext. Editor integrations and agent tooling consume the resulting file.
| Flag | Description |
|---|---|
--out <path> | Output path. Defaults to .pyreon/context.json. |
zero context # write .pyreon/context.json
zero context --out ./ctx.json # custom output path
zero context ./apps/web # generate for a subdirectory projectzero create <name>
Scaffold a new Pyreon Zero project from the bundled default template. <name> is required — it's both the new directory name and the generated package.json name.
zero create my-appWhat it does:
Refuses to overwrite — errors out if a directory named
<name>already exists.Copies
@pyreon/create-zero's default template into./<name>.Rewrites the template's
package.jsonnameto your project name.Writes a starter
.gitignore(node_modules,dist,.DS_Store,*.local).Prints next steps.
Created "my-app"!
Next steps:
cd my-app
bun install
bun run devHow it relates to Vite and the zero() plugin
zero is a wrapper, not a replacement, for Vite:
zero devcalls Vite'screateServer().zero buildcalls Vite'sbuild()once per pass.zero previewcalls Vite'spreview().
Your vite.config.ts is the single source of truth for everything else — plugins, aliases, build options, and crucially the zero({ ... }) plugin, which declares your render mode, port, deploy adapter, and SSG paths. The CLI reads that config back at runtime (via an internal accessor on the resolved pyreon-zero plugin instance) to decide how to build and which port to serve on:
import { defineConfig } from 'vite'
import { pyreon } from '@pyreon/vite-plugin'
import { zero } from '@pyreon/zero'
export default defineConfig({
plugins: [
pyreon(),
zero({
mode: 'ssr', // ← zero build reads this (wins over --mode)
port: 5173, // ← zero dev / preview read this (below --port)
// adapter, ssg: { paths }, … also read by zero build
}),
],
})The flags zero exposes are deliberately minimal — the per-invocation knobs that make sense on a command line (--port, --host, --open, the doctor flags). Anything structural belongs in vite.config.ts.
Gotchas
The CLI is a thin Vite wrapper. For custom plugins, alias maps, or build tuning, edit
vite.config.ts— don't look for CLI flags that don't exist. The complete flag set is in the reference table below.--portdoes not always win at the CLI. It does — but only when you actually pass it. If you omit it,zero({ port })from config wins over the3000default. Trust the resolution order.There is no
--modeflag. The render mode iszero({ mode })invite.config.ts— the plugin instances are constructed from that file, so a CLI flag can't reach them. Change the config to switch modes.zero previewdoes not build for you — runzero buildfirst. It servesdist/client/when a node/bun-adapter build staged it, otherwise yourbuild.outDir.zerois notpyreon.zero(this package) is the Zero-aware CLI for@pyreon/zeroapps;pyreon(@pyreon/cli) is the framework-wide CLI for non-Zero / library packages. Samedoctorphilosophy, different default scope. Zero apps should usezero.zero --versionreports0.0.1. The--versionstring is a placeholder baked into the CLI, independent of the installed package version. Checkpackage.json(ornpm ls @pyreon/zero-cli) for the real version.zero createis template-only. It copies the default@pyreon/create-zerotemplate with no prompts. The next-steps it prints assumebun; substitute your package manager (npm install/pnpm install) as needed.
Command & flag reference
| Command | Positional | Flags | Purpose |
|---|---|---|---|
zero dev [root] | [root] (dir, default .) | --port <port>, --host [host], --open | Vite dev server + route table |
zero build [root] | [root] | — | Production build (one vite build; the zero plugin owns client + SSR + prerender + adapter) |
zero preview [root] | [root] | --port <port>, --host [host] | Serve the built client bundle locally |
zero doctor [root] | [root] | --fix, --json, --ci | Pyreon health gates |
zero context [root] | [root] | --out <path> (default .pyreon/context.json) | AI-readable project summary |
zero create <name> | <name> (required) | — | Scaffold from the default template |
zero --help | — | — | Print usage |
zero --version | — | — | Print version (placeholder 0.0.1) |
Render modes (ssr, ssg, isr, spa) are set via zero({ mode }) in vite.config.ts; the default is ssr. What each mode emits is documented in the Zero overview.
See also
Zero overview — the full meta-framework: file-system routing, render modes, adapters, SSG paths.
@pyreon/create-zero— the interactive project scaffolder (bun create @pyreon/zero).@pyreon/cli— the framework-widepyreonbinary that backszero doctor/zero context.