pyreon

@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.

@pyreon/zero-clistable

Installation

npm install -D @pyreon/zero-cli
bun add -D @pyreon/zero-cli
pnpm add -D @pyreon/zero-cli
yarn add -D @pyreon/zero-cli

Most 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 dev boots vite.createServer() and then prints the discovered route tree, so you see your page and API routes the moment the server is up.

  • zero build runs one vite build and lets the zero() plugin chain own the entire production pipeline — client bundle, SSR/ISR server bundle + production template, SSG prerendering, deploy-adapter staging. It exists for the scaffolded bun run build script and symmetric UX, not to add build steps.

  • zero preview serves the built client bundle (dist/client/ when a node/bun-adapter build staged one, otherwise your build.outDir).

  • zero doctor / zero context delegate 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 project

Every 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/site

Commands

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.

FlagDescription
--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).
--openOpen the browser on first listen.
--routesPrint 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 table

Port resolution order (highest precedence first):

1. --port <port>            CLI flag
2. zero({ port })           vite.config.ts plugin config
3. 3000                     framework default

The 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):

  1. Client bundledist/ (your project's build.outDir).

  2. SSR/ISR (mode: 'ssr' | 'isr'): server bundle → dist/server/entry-server.js (your src/entry-server.ts when present, a synthetic entry otherwise — zero-config apps get a server bundle too) plus dist/server/template.html, the built client index.html staged next to the entry as the production SSR template (hashed asset refs — this is what makes the deployed page hydrate).

  3. SSG (mode: 'ssg', plus hybrid static-first routes in server modes): prerendered per-route HTML into dist/.

  4. Deploy adapter (Vercel / Netlify / Cloudflare / Node / Bun / static): platform artefacts staged into the same dist/ tree — e.g. the node adapter emits dist/index.js + a clean dist/client/ copy of the client assets, Vercel emits dist/.vercel/output/, Cloudflare emits dist/_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 subdirectory

zero preview [root]

Serve the production build locally for smoke-testing, via vite preview.

FlagDescription
--port <port>Server port. Same resolution order as dev (--portzero({ 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 LAN

zero 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.

FlagDescription
--fixAuto-fix the fixable findings (e.g. classNameclass).
--jsonEmit machine-readable JSON instead of the formatted report.
--ciCI 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 subdirectory

zero 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.

FlagDescription
--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 project

zero 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-app

What it does:

  1. Refuses to overwrite — errors out if a directory named <name> already exists.

  2. Copies @pyreon/create-zero's default template into ./<name>.

  3. Rewrites the template's package.json name to your project name.

  4. Writes a starter .gitignore (node_modules, dist, .DS_Store, *.local).

  5. Prints next steps.

Created "my-app"!

Next steps:
  cd my-app
  bun install
  bun run dev

How it relates to Vite and the zero() plugin

zero is a wrapper, not a replacement, for Vite:

  • zero dev calls Vite's createServer().

  • zero build calls Vite's build() once per pass.

  • zero preview calls Vite's preview().

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.

  • --port does 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 the 3000 default. Trust the resolution order.

  • There is no --mode flag. The render mode is zero({ mode }) in vite.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 preview does not build for you — run zero build first. It serves dist/client/ when a node/bun-adapter build staged it, otherwise your build.outDir.

  • zero is not pyreon. zero (this package) is the Zero-aware CLI for @pyreon/zero apps; pyreon (@pyreon/cli) is the framework-wide CLI for non-Zero / library packages. Same doctor philosophy, different default scope. Zero apps should use zero.

  • zero --version reports 0.0.1. The --version string is a placeholder baked into the CLI, independent of the installed package version. Check package.json (or npm ls @pyreon/zero-cli) for the real version.

  • zero create is template-only. It copies the default @pyreon/create-zero template with no prompts. The next-steps it prints assume bun; substitute your package manager (npm install / pnpm install) as needed.

Command & flag reference

CommandPositionalFlagsPurpose
zero dev [root][root] (dir, default .)--port <port>, --host [host], --openVite 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, --ciPyreon 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 --helpPrint usage
zero --versionPrint 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-wide pyreon binary that backs zero doctor / zero context.

@pyreon/zero-cli