# CLI

The Fayz CLI scaffolds projects, validates manifests and architecture boundaries, assists the migration from config-in-code to a manifest, and provisions an app's Supabase database from the installed packages. You run it without installing anything, via `npx`:

```bash
npx @fayz-ai/cli <command>
```

## Commands

| Command | What it does |
| --- | --- |
| `fayz create storefront <name>` | Scaffolds a storefront (customer-facing shop). |
| `fayz create admin <name>` | Scaffolds a multi-tenant admin app. |
| `fayz create member <name>` | Scaffolds a member/student portal. |
| `fayz create plugin <name>` | Scaffolds an app-local plugin (incubator). |
| `fayz doctor [dir]` | Validates the manifest + architecture boundaries. |
| `fayz extract [dir]` | Assisted migration from config-in-code → manifest. |
| `fayz db apply [dir]` | Provisions Supabase from the installed packages. |
| `fayz db pool status` | Shows the migration ledger for each *industry pool* (Runner v2). *(≥ 0.3.0)* |
| `fayz db pool apply <name> --app <dir>` | Applies an app's plan to **one** pool, always ledger-gated. *(≥ 0.3.0)* |
| `fayz db pool move-tenant --from <p> --to <p> --tenant <uuid> [--yes]` | Moves a tenant between pools (dry-run unless `--yes`). *(≥ 0.3.0)* |
| `fayz db fan-out --app <dir>` | Applies an app's plan across every pool: canary first, then the rest. *(≥ 0.3.0)* |
| `fayz login [--token \| --status]` | Stores the Fayz platform token used by `deploy`. *(≥ 0.3.0, rolling out)* |
| `fayz logout` | Removes the stored credential. *(≥ 0.3.0, rolling out)* |
| `fayz deploy [dir] [--dry-run \| --yes]` | Publishes the app to the Fayz platform. *(≥ 0.3.0, rolling out)* |
| `fayz --help` | Shows help. |
| `fayz --version` | Shows the version. |

{% callout type="info" %}
The `db pool`, `db fan-out`, `login`, `logout` and `deploy` commands **require CLI ≥ 0.3.0** — they don't exist in the published 0.2.0. `login`/`logout`/`deploy` are still **rolling out** to the invited developer network.
{% /callout %}

`create` takes a **kind** and a **kebab-case name** (`^[a-z0-9][a-z0-9-]*$`) — no flags. It generates a Vite project with `app.manifest.json`, `src/plugins.generated.ts`, `src/registry.tsx`, `AGENTS.md` and `.env.example`. See [tutorial 01](/en/docs/tutorial/01-create-the-app).

## `fayz create plugin`

Creates an app-local plugin in `src/plugins/<name>/`, following the same contract as the official plugins (index/data/schema/README). Details in [Incubator](/en/docs/building-plugins/incubator).

## `fayz doctor`

Validates the app in the current directory: manifest structure, referenced plugins, locale coverage and the architecture boundaries (provider imports + supported surface). **Structural** manifest problems exit with an error (exit 1); **boundary** problems are warnings (soft enforcement). An app without an `app.manifest.json` but with a `package.json` is treated as code-first — only the boundaries run, and it passes. See [Test and debug](/en/docs/test-and-debug).

## `fayz db apply`

Builds the migration order — **core** first, then each plugin's versioned modules, then the incubator ones — from the installed packages, and applies it to your Supabase project via the Management API. The current CLI labels the steps `spine → drizzle → seed → plugins → incubator` (the first three are the core; the label changes in the *industry pools* wave).

### Flags

| Flag | Effect |
| --- | --- |
| `--dry-run` | Prints the ordered plan only; **no** network calls. |
| `--yes`, `-y` | Skips confirmation (required in non-interactive shells). |
| `--spine-only` | Applies only the `@fayz-ai/db` core (the flag keeps the `spine` label in the current wave). |
| `--plugins-only` | Applies only plugin + incubator migrations. |
| `--only-plugins a,b` | Restricts the plugins step to the named ids. |

### Env (required for a real apply; never for `--dry-run`)

| Variable | Description |
| --- | --- |
| `SUPABASE_PROJECT_REF` | Project ref (alias: `SUPABASE_REF`). Dashboard → Project Settings → General. |
| `SUPABASE_PAT` | Personal access token (alias: `SUPABASE_ACCESS_TOKEN`). Dashboard → Account → Access Tokens. |

The command reads from the process environment first, then `<app>/.env.local`, then `<app>/.env` — and the files never override the environment. Without these variables, a real apply stops with a clear error; `--dry-run` doesn't need them.

```bash
# plan without touching the database
npx @fayz-ai/cli db apply --dry-run

# apply (asks for confirmation; use --yes in CI)
npx @fayz-ai/cli db apply
```

The full connection flow is in [Supabase](/en/docs/data/supabase).

## `fayz extract`

Assists the migration of a code-first app (config in `src/App.tsx` / `defineSaas`) to `app.manifest.json`. Run it at the root of an existing app; it reads the config code and proposes the equivalent manifest.

## `fayz db pool` and `fayz db fan-out` (industry pools — Runner v2) {% badge status="beta" %}

{% callout type="info" %}
Requires **CLI ≥ 0.3.0** — these commands don't exist in the published 0.2.0.
{% /callout %}

While `db apply` provisions **one** Supabase project, Runner v2 operates over a fleet of *industry pools* — the Supabase projects shared per vertical. It reads the pool registry from `cli/pools.config.json` (use `--pools-file` to point at another file) and takes the token from `SUPABASE_PAT` / `SUPABASE_ACCESS_TOKEN` (each pool's ref comes from the pools file, not the environment).

| Command | What it does |
| --- | --- |
| `fayz db pool status` | Prints each pool's migration ledger: what's already applied and what's pending. |
| `fayz db pool apply <name> --app <dir>` | Applies the app's plan (derived from the packages installed in `<dir>`) to **one** named pool. |
| `fayz db pool move-tenant --from <p> --to <p> --tenant <uuid> [--yes]` | Moves a tenant from one pool to another. |
| `fayz db fan-out --app <dir>` | Applies the app's plan **across every pool**: the canary first, then the rest. |

### Applies are always ledger-gated

Every apply goes through each pool's ledger. A file that's already applied and unchanged is **skipped**. A file that's already applied but whose **checksum changed** is a **HARD STOP** — the runner halts instead of reapplying. The rule is absolute: **never edit an applied migration; write the next one**. This keeps each pool's history reproducible and auditable.

### Fan-out: canary first

`db fan-out` doesn't apply everywhere at once. It applies to the pool marked as canary first (`--canary <pool>` to force another), and only then to the rest — so an error surfaces in one pool before it touches the whole fleet. Pools in `PROVISIONING` state are skipped during fan-out unless named explicitly; a pool marked `dataCritical` requires `--allow-critical` (alongside `--yes`).

### `move-tenant` is safe by construction

`move-tenant` is **dry-run by default** — without `--yes` it only prints the plan. With `--yes`, it: backs up to JSON **before** any write, inserts the records into the destination (parents-first), **verifies the counts** (a mismatch is a HARD STOP and the source stays intact), and only then deletes from the source (children-first). The destination pool can't be in `PROVISIONING`.

## `fayz login` / `fayz logout` / `fayz deploy` {% badge status="experimental" %}

{% callout type="info" %}
Requires **CLI ≥ 0.3.0** and is **rolling out** to the invited developer network. If `deploy` returns an authorization error, your token doesn't have CLI access enabled yet — the [static host](/en/docs/deploy/static) gets the same app live in the meantime.
{% /callout %}

This trio publishes an app straight to the Fayz platform, with no CI of your own.

- **`fayz login [--token <fayz_...> | --status]`** — stores the platform access token (prefix `fayz_`) in `~/.fayz/credentials.json` (mode `0600`, owner-readable only). Without `--token`, it reads the `FAYZ_TOKEN` env var or prompts interactively (in a non-interactive shell with no token, it fails fast with instructions). `--status` shows the masked credential, with no network call. Login performs no network validation — the token is validated on the first deploy.
- **`fayz logout`** — deletes the stored credential.
- **`fayz deploy [dir] [--dry-run | --yes]`** — collects the app's **source** files, creates/links a project on the platform and triggers the server-side build with static hosting. The project link lives in `<app>/.fayz/project.json` (add `.fayz/` to your `.gitignore`). The published app lands at `https://<app>.live.fayz.ai`. `--dry-run` makes **no** network calls; a real deploy asks for confirmation unless you pass `--yes`.

**Token and URL resolution:** the token comes from `FAYZ_TOKEN` (env, takes priority) → `~/.fayz/credentials.json`. The API URL comes from `FAYZ_API_URL` (default `https://beta.fayz.ai/api`). The step-by-step walkthrough with screenshots is in [Fayz deploy](/en/docs/deploy/fayz).

---

Next: the fields `doctor` validates, in [Plugin manifest — reference](/en/docs/reference/plugin-manifest).
