# CLI (/docs/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:

```sh
npx stanza-cli add --help
```

## `init` [#init]

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

```sh
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](/docs/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](/docs/concepts#apps).

## `add` [#add]

Add one module to an existing project.

```sh
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]

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

```sh
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` [#list]

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

```sh
npx stanza-cli list
```

## `search` [#search]

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

```sh
npx stanza-cli search [query]
```

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

## `doctor` [#doctor]

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

```sh
npx stanza-cli doctor
```

Walks every [region](/docs/concepts#regions) 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.

<Callout type="info">
  **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.
</Callout>

## Global flags [#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 [#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](/docs/registry#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 [#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](/stats).

**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](/stats) (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`](https://github.com/jakejarvis/stanza/blob/main/apps/cli/src/lib/telemetry.ts).

## Dependency versioning [#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.
