Skip to content
Documentation

CLI

The Stanza command-line verbs, flags, and environment variables.

Stanza ships six verbs against the category taxonomy. Run any verb with --help for its full flag list:

npx stanza-cli add --help

init

Scaffold a new monorepo. Without flags it launches an interactive wizard; with --yes it takes every pick from flags.

npx stanza-cli init [name] --yes --framework=next --orm=drizzle --db=postgres --pm=pnpm
  • name — project directory name (positional; prompted if omitted).
  • --yes — non-interactive; take selections from category flags.
  • --<category>=<ids> — pick modules for a category. There's one flag per category in the registry (--framework, --ui, --db, --orm, --auth, --ai, --payments, --email, --tooling, --testing, --deploy, --monorepo, …); single-choice categories take one id, multi-choice categories take a comma-separated list. Omitted categories are skipped — --yes chooses no defaults.
  • --pm=<pnpm|bun|npm> — package manager recorded in the manifest.

init always scaffolds a single web app today — { id: "web", dir: "apps/web", kind: "web" } in stanza.json — and tags every app-home record with apps: ["web"]. Multi-app init (scaffolding apps/native alongside apps/web, for example) is planned; the schema and runtime already support it. See Concepts → Apps.

add

Add one module to an existing project.

npx stanza-cli add <category> <module> [--app=<id>]

Resolves peers, selects the matching adapter, and writes the module's templates, deps, env, and scripts to the right home. If an apply step fails partway through — including inside a codemod — Stanza rolls the changes back, so a failed add leaves your tree as it was. Adding a module to a single-choice category that's already filled fails until you remove the existing one — and that limit is per app for home: "app" categories (so picking a framework for one app doesn't block a different framework for another).

  • --app=<id> — pick which app the install targets. Required for app-relevant modules when the project has multiple apps and you're not running inside one of them. Single-app projects auto-target; multi-app projects also auto-pick when cwd is inside one app's directory. Interactive runs (TTY, no --yes) fall back to a prompt; non-interactive runs error out asking for the flag.
  • The flag is meaningless for home: "repo" modules like tooling.

remove

Remove a module and sweep the files (regions) it owns.

npx stanza-cli remove <category> [id] [--app=<id>]

For single-choice categories the id is optional; for multi-choice categories (like testing) it's required. --app=<id> scopes removal in projects with multiple apps — without it, remove looks across every app for a matching record.

The package-dir sweep (when removing the last module under packages/<dir>/) strips the workspace:* dep from every app's package.json, not just the first one.

list

Print installed modules, grouped by category, from the nearest stanza.json.

npx stanza-cli list

List registry modules and their category/id pairs. Pass a query to filter.

npx stanza-cli search [query]

Use the printed id (not the display label) when passing a module to add.

doctor

Check stanza.json against the filesystem and report drift, without changing anything.

npx stanza-cli doctor

Walks every region claim in the manifest and verifies it still holds on disk — claimed files exist, claimed dependencies/scripts/env vars are still present, and each internal package with claims is wired into its consuming apps. Read-only; exits non-zero when it finds drift, so it slots into CI or a pre-commit check. It doesn't repair anything — use add / remove for that.

Planned: swap (replace a module with another in the same category) and update (re-pull a module at a newer version) are on the roadmap. The manifest already reserves the fields they need; the verbs aren't implemented yet.

Global flags

These apply to the mutating verbs (init, add, remove):

  • --dry-run — print the actions that would be taken and write nothing.
  • --dangerously-allow-dirty — allow a mutating command to run with a dirty git working tree. By default Stanza refuses, so its edits never mix with uncommitted changes. Commit or stash first when you can.
  • --no-telemetry — disable anonymous usage events for this invocation.

Environment variables

  • STANZA_REGISTRY=<url-or-path> — override the @stanza default namespace's source: the full URL or filesystem path to a registry's main JSON file (not a directory). For per-namespace third-party registries, use the registries field in stanza.json instead — see Third-party registries.
  • STANZA_NO_NPM_LOOKUP=1 — skip npm version lookups and write dependency ranges verbatim.
  • STANZA_NPM_REGISTRY=<url> — override the npm registry used for version lookups.
  • STANZA_TELEMETRY=0 / DO_NOT_TRACK=1 — disable telemetry persistently. Telemetry is also auto-skipped in CI.
  • STANZA_TELEMETRY_URL=<url> — point telemetry at a self-hosted ingest endpoint instead of https://stanza.tools/api/events.

Telemetry

Stanza captures a small set of anonymous events to help us see which modules people actually pick. The aggregates are surfaced publicly on the Stats page.

What's sent

  • The command name (init, add, remove, list, search, doctor), its duration in milliseconds, and whether it succeeded.
  • CLI version, Node version, OS, and architecture.
  • For installs and removes: the module id, its category, and the namespace it came from (e.g. @stanza or @acme).
  • An ephemeral UUID generated per process so events from the same run can be grouped. It's regenerated next time and never persisted.

Third-party modules

Third-party module installs are counted in the aggregate totals at the top of the Stats page (so adoption of Stanza-as-a-platform shows up), but they're filtered out of the per-category leaderboards underneath. A private @acme/auth-internal doesn't outrank @stanza/better-auth in a public ranking.

What's never sent

  • File paths, project names, the contents of templates or env files, or anything else that could identify a project or person.
  • Your IP address — the CLI posts to a server-side proxy on stanza.tools that hands off to PostHog without forwarding the request IP.
  • Anything from CI environments. The CLI auto-detects CI, GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, and BUILD_NUMBER.

Opting out

  • One invocation: --no-telemetry.
  • Persistently: STANZA_TELEMETRY=0 or DO_NOT_TRACK=1 in your shell.

The implementation is a single file: apps/cli/src/lib/telemetry.ts.

Dependency versioning

On init and add, Stanza bumps each ^/~ dependency range to the latest npm version that satisfies it, keeping the modifier. Other ranges and workspace:* specifiers are written as-is. When offline, it falls back to the range declared in the module.