# CLI

`volter` is the user's command: a noun, then a verb. `volter world …` acts on the world of the
current directory; `volter twin <vendor> …` acts on one twin, in a world or on its own;
`volter remote …` names the worlds this one pushes to and fetches from. `volter-world` is the
operator's command and names worlds and configs explicitly. Both come from `@volter/world`
(`volter`) and `@volter/world-runtime` (`volter-world`).

The `volter` section below is checked against the command's own help text by
`scripts/docs-reference.ts`: every verb the command knows appears here.

## `volter world`

Every verb finds the world by walking up from the current directory to the nearest
`.volter/world.json`, and acts on the checked-out branch.

Global flags:

| flag | meaning |
|---|---|
| `--world <dir>` | the world root, when the cwd is not inside it |
| `--branch <name>` | act on that branch instead of the checked-out one |
| `--json` | machine-readable output, where a verb offers it |
| `--version` | as the first argument: the command's version, as `volter <version>` (also `volter version`, `-v`) |

### Lifecycle

| verb | does |
|---|---|
| `volter world init [--install] [--name <world>] [--twins a,b] [--source vendor=package] [--allow-unknown] [--force]` | detect vendors or select them explicitly with `--twins`; repeat `--source` to choose installed publishers before discovery resolves ambiguity. Missing selected twins are installed with the app's package manager, asked at a terminal or at once with `--install`. Write `.volter/world.json`, handlers, seeds and the ignore file; retain authored files and refuse a conflicting saved publisher pin |
| `volter world init --bare <org>/<world>` | a world with no app: a shared world for a team, or a world standing in for a vendor |
| `volter world up [--sandbox] [--no-seed]` | start the twins on this branch. A branch that has run before resumes with its state; a fresh branch loads the default data |
| `volter world run [--verbose] -- <command...>` | run the command inside the world: the twins' URLs, the fake credentials and the SDK redirect in its env. A stopped world is started first, as `up` does, and left running. Exits with the command's code |
| `volter world activate` | print the script that activates the world in the current shell: `eval "$(volter world activate)"` |
| `volter world shell` | a subshell with the world active |
| `volter world serve [--port <p>] [--host <h>] [--console-port <c>] [--origin <url>]` | serve this world on a URL, under `/<org>/<world>/<vendor>/…`, for apps and scripts; prints the token that opens it and writes it to `.volter/token`. On loopback (the default) it is the same local front as `view`, without opening the browser: the console it prints opens with no token. A non-loopback `--host`, or `--console-port`, serves it for others, and its console (its own origin, port p+1 by default) asks for the token |
| `volter world view [--port <p>] [--host <h>] [--no-open] [--no-origins] [--origin <url>]` | serve this world to step into: its HTTP API, each twin's vendor screens, the branches made of it and, with `@volter/world-console` installed, the console, opened in the browser at the world's own origin (`http://<world>--<org>.localhost:<port>/-/console/<org>/<world>`, where its browser session lives; `--no-origins` serves every world on one origin). On loopback the browser never needs a token: the World's own page opens its session, after a restart or a rotated token too (a non-loopback `--host` is a World served for others, and its console asks for one; with `--no-origins` the browser is opened with the token in the URL's fragment). Anything that can reach the port can open that session, so never forward or expose it; see the architecture's "A local World asks for no token". `--origin <url>` is the World's origin as a browser reaches it through a proxy on this machine (an app that serves the World as one of its own pages): a request the proxy forwards from that host (`X-Forwarded-Host`) is answered as at the World's own origin, its local session included, so the proxy is then the World's front and the same caution holds for it. Branches it makes live under `.volter/branches/` until their time runs out |
| `volter world console [<https://host/org/world>] [--port <p>]` | the console on this machine for a World served elsewhere (a hosted World), on two local origins as `serve` has: the console (port p), its endpoints forwarded to the host, and the Worlds (port p+1), forwarding the twins' screens and the session endpoint; defaults to this world's origin, and needs no world when a URL is given; open the World with its token; needs `@volter/world-console` |
| `volter world status [--json]` | the world, the branch, whether it runs, the origin, the base, unpushed changes, each twin's URL and root |
| `volter world down [--purge]` | stop the twins; the branch's state stays. `--purge` deletes it if no dependent local branch references it |

### State

App initialization's `--twins` selects only those vendors and reports other detected uses as excluded,
not covered. `--source stripe=@publisher/twin-stripe` chooses an already installed package and records
its actual version. Install the exact release first; a source whose published vendor differs refuses.
Use separate app directories to trial a different implementation. Check your installed CLI's help
before using these options: older versions accepted `--twins` for bare Worlds only.

| verb | does |
|---|---|
| `volter world log [--receipts] [--json]` | every write the app made, across the twins, oldest first; `--receipts` adds each change's receipt |
| `volter world diff [--json]` | the changes since the base, grouped by twin |
| `volter world seed` | load the default data |
| `volter world reset` | back to the default data: forget the branch's changes, boot, seed |

Named browser states are made by the seed through `browser(name)` from `@volter/world-core/browser`, or loaded through
[the HTTP API](./http-api.md#browsers). There is no separate CLI command. Local states are browser profile
files in `.volter/browsers/<name>.json` under the World state directory, regardless of the env output path;
read one as `profile` and load it with `newContext({ ...profile.context, storageState: profile.storageState, proxy })`
and the World's proxy and session CA. Only the selected current World writes these files; checkout replaces its
recorded exports after changing the selection. The helper reads existing state and creates empty state only when
absent. See the HTTP API for input caps and refused hostname collisions. `up` resumes retained browsers,
`reset` recreates the seeded defaults,
and `branch` inherits the parent's browsers. They are outside the log, diff, changesets and push. `view` displays
each page for every browser whose address can be made, grouped by name. If any browser address cannot be made,
it also displays one plain copy at its ordinary claimed-site address or vendor screen path, or with the reason
it cannot open here; with no browsers it displays each page once.
[Seed and reset](../guides/seed-and-reset.md#make-browser-states-in-the-seed) explains seed ownership and links the local and hosted loading API.

### Branches

| verb | does |
|---|---|
| `volter world branch` | list branches; `*` marks the checked-out one |
| `volter world branch <name> [--at <instant\|twin@n,…\|twin@view:n,…>]` | a new branch from here — or from any point in the history: an instant, or a position within the current or named view per twin — checked out; the current branch stops. Nothing is copied |
| `volter world checkout <name>` | switch to a branch: it resumes with its state, the current one stops |
| `volter world replay <changeset> --into <branch>` | feed a changeset's changes into another branch's twins, in order, with the same ids; replaying twice is a no-op |

### Time

| verb | does |
|---|---|
| `volter world clock show` | the world's clock: the frozen instant every twin stamps from, or the wall clock when none is set |
| `volter world clock set <iso>` | set it; every twin stamps from it until it moves, and so does the World's application, whose time then stands still (a World serving an application moves time with `shift`) |
| `volter world clock advance <N s\|m\|h\|d>` | move a set clock forward: `30d`, `12h` |
| `volter world clock shift <N s\|m\|h\|d>` | move a World forward while its time keeps running (a World serving an application, which follows the World's time) |
| `volter world clock clear` | return the World to the machine's time |

### Remotes and changesets

| verb | does |
|---|---|
| `volter world clone <url> --token <token>` | record the world at `<url>` as origin, remember the token, and bring its whole history in |
| `volter world fetch [<remote>] [--token <token>]` | cache the remote's current snapshot without changing reads (clone before branching to inherit its origin); `pull` integrates origin history, and named changeset rebase can use the fetched snapshot |
| `volter world origin [--json]` | the remote this world clones from and pushes to; `default data` when it has none |
| `volter world changeset [<name>] -m "<message>"` | cut the unpushed changes into a changeset, named from the message unless given |
| `volter world push [<remote>] [<changeset>] [--token <token>] [--force]` | send changesets to the remote, oldest first, and move the base; refused on drift. A served World records who pushed it: the person a key was made for, or the key itself for an automation's |
| `volter world pull [--token <token>]` | fetch and integrate the origin snapshot, preserving inherited local layers and naming conflicts |
| `volter world rebase [<changeset>]` | without a name, move onto the local base's current position; with a changeset, integrate the fetched origin snapshot and re-check that changeset. Both name conflicts by record and field |

### Deploying

On a world whose twins have a root. The receipts land on the changes.

| verb | does |
|---|---|
| `volter world verify <changeset>` | run the world's checks over the changeset and record the result |
| `volter world approve <changeset> --as <principal> [--note <text>]` | sign the changeset's current hash |
| `volter world deploy [<changeset>]` | perform landed changes against each twin's root, by its policy: every deployable changeset, or the named one |

Tokens come from `~/.config/volter/credentials.json` (or `$XDG_CONFIG_HOME/volter/`), written by
`remote add` and `clone --token`; `--token` overrides the store for one call.

## `volter remote`

The worlds this world pushes to and fetches from, by name, like git's remotes. A remote is a URL
a served world printed, or a path to another world's directory on this machine.

| verb | does |
|---|---|
| `volter remote add <name> <url\|path> [--token <token>]` | name a remote; `origin` is the one `fetch` and `push` use by default. A path is a world on this machine, reached with no server and no token |
| `volter remote remove <name>` | forget it |
| `volter remote list` | every remote, with its URL and whether a token is stored |
| `volter remote add <name> <org>/<world> [--create]` | a world the hosted platform holds for your org, its address resolved through the platform you signed in to. One that is not there yet is made there with this world's twins: asked at a terminal, or at once with `--create` (without either, the command stops and names `--create`). `<world>` alone names it in your only org |

## `volter login`

| command | does |
|---|---|
| `volter login <platform url> [--no-open]` | sign the command into the hosted platform as you, through the browser: it shows a code, opens the platform's approval page (where you sign in with your Volter account, making one if you are new), and on approval keeps a personal token named for this machine, for 30 days. The person it signs in as is also who a World served here (`volter world view`) acts as from its own page: your cuts and approvals there are yours |
| `volter login <platform url> --token <personal token>` | the same without a browser, with a token made on the platform under Account (CI) |
| `volter whoami` | the platform the command is signed into, as whom, and the orgs it reaches |
| `volter logout` | revoke the command's token at the platform and forget it here |

## `volter open`

| command | does |
|---|---|
| `volter open [<org>/<world>] [--no-open]` | a World's dashboard: a hosted one (named, or this world's origin on the platform you signed in to) through the platform, which signs you in; otherwise this world's own, as `volter world view` serves it. The address is printed; `--no-open` only prints it |

## `volter completion`

| command | does |
|---|---|
| `volter completion bash\|zsh\|fish\|powershell` | print a script that completes volter's nouns and verbs: `eval "$(volter completion bash)"` in `~/.bashrc` (or zsh), `volter completion fish \| source`, `volter completion powershell \| Out-String \| Invoke-Expression` in your profile |

## Installing and updating

For an application, install the CLI and selected twin releases as exact development dependencies,
then commit the package manager's lockfile. The tutorials use project-local `npx volter`; a global
installation is convenient but does not pin the version the team runs. Use the package manager
already selected by the app:

| Manager | Add an exact CLI or twin release | Install the team's locked dependencies |
|---|---|---|
| npm | `npm install --save-dev --save-exact <package>@<version>` | `npm ci` |
| pnpm | `pnpm add --save-dev --save-exact <package>@<version>` | `pnpm install --frozen-lockfile` |
| Yarn | `yarn add --dev --exact <package>@<version>` | Modern Yarn: `yarn install --immutable`; Yarn Classic: `yarn install --frozen-lockfile` |
| Bun | `bun add --dev --exact <package>@<version>` | `bun install --frozen-lockfile` |

Published runtime packages require Node 22.3 or newer. The worked tutorials use Node 22.6 or
newer because project-owned TypeScript seeds use Node's type stripping. App prerequisites
such as a native tool or database are listed in their own guides. A World does not replace
your package manager or supply every application dependency.

`npm install -g @volter/world` (or `bun add -g`), or the site's install script, which checks for Node
22.3 or later (or Bun) and runs the same install: `curl -fsSL https://world.volter.ai/install.sh | sh`,
or `irm https://world.volter.ai/install.ps1 | iex` in PowerShell. Once a day at most, the command asks
the npm registry for the latest version while it works and says in one line on stderr when a newer
one is out. It never asks in CI, for `--json`, or away from a terminal, and
`VOLTER_NO_UPDATE_NOTIFIER=1` turns it off.

## Agents

| command | does |
|---|---|
| `volter agents install [--claude] [--cursor] [--vscode] [--codex] [--yes]` | put the `volter-world` skill and the MCP server into the coding agents found here (or those named): Claude Code (`claude mcp add --scope user`, else `~/.claude.json`; the skill in `~/.claude/skills/volter-world`), Cursor (`~/.cursor/mcp.json`), VS Code (`.vscode/mcp.json` in this project), Codex (`~/.codex/config.toml`, the skill's instructions in `~/.codex/AGENTS.md`); only its own entries are written, and running it again changes nothing; without `--yes` it asks first, and at no terminal it only says what it would do |
| `volter mcp` | the MCP server an agent starts, over stdio: tools `world_status`, `world_init`, `world_up`, `world_run`, `world_down`, `world_view`, `world_log`, `world_diff`, `world_branch`, `world_checkout`, `world_reset`, `platform_login`, `platform_whoami`, `remote_link`, `world_changeset`, `world_push` (each takes the app's folder as `dir`), and the prompt `get-started`; a tool that acts runs `volter` itself, so it answers and refuses as the command does |

`volter world init` ends by offering `volter agents install` at a terminal. [Use Volter World with a
coding agent](../guides/use-with-a-coding-agent.md) walks through it.

## `volter twin`

One twin: in the world of the current directory, or on its own.

| verb | does |
|---|---|
| `volter twin <vendor>` | the twin in this world: its URL, its root and deploy policy, whether a credential is sealed, and for a twin with a root its ingest URL (a served World's endpoint) to register at the vendor as its webhook URL |
| `volter twin submit --source <id> --vendor <vendor> --package <@scope/name> --version <exact>` | delegate to the installed `@volter/twin-catalog` CLI to prepare a submission JSON file; no publication, pull request or approval is performed |
| `volter twin <vendor> root <url> [--deploy auto\|gated\|hold] [--scope <path>] [--refresh <every>] [--at-most <span>]` | set the twin's root to the vendor at `<url>` and its deploy policy; `--scope` names a resource path such as GitHub's `repos/<owner>/<repo>`; it is not a Slack channel filter. `--none` clears the root |
| `volter twin <vendor> credential` | seal the twin's root credential beside the world, never readable back: what is piped in, else (signed in to a vault: `BAO_ADDR`/`VAULT_ADDR` and that CLI's token) the vault's `kv/vendors/<vendor>` record, field `credential` (a token, or a payload with `headers`), else the one credential the repo's `.env` files hold for the vendor (`.env.local` over `.env`). A vault that refuses stops the seal; one that cannot be reached falls back, saying so. A JSON payload may carry the vendor's webhook signing secret (`signingSecret`); without one a secret is minted, sealed with it and printed once, to enter at the vendor |
| `volter twin <vendor> refresh [--force]` | observe the root now — the twin observes, the kernel folds what changed; throttled to the root's `atMost` (else the twin's) unless `--force` |
| `volter twin <vendor> serve [--port <p>] [--read-only]` | serve the twin at `http://127.0.0.1:<p>` without a world; `--read-only` refuses every write |

## `volter-world`

The operator's surface. Every verb takes the world's name and, where a config is needed, its
path. `--root <dir>` is the world root (default: the cwd). The verbs the user surface wraps are
listed first, then the ones only an operator needs.
`--env-out=<path>` selects a file that the World **writes**, never a credentials input.
Use a new disposable path. Existing foreign or modified files are refused before boot;
`--force` explicitly permits replacement and saves a private backup. Protected custody files
remain unwritable. The former `--env-file` spelling is rejected with migration guidance.

### The same verbs, addressed explicitly

`up --json` returns the instance including its unique `instance` token; ordinary output prints
that token and a guarded stop command. Retain this value from your own boot and use
`down --if-instance <token>` (also with `--purge`) to stop only that running or retained
instance. A different or absent token refuses with `World ownership mismatch`, before
teardown changes anything. A later lookup by name does not prove cleanup ownership.


```text
volter-world init <name> --repo <path> [--out <dir>] [--force] [--allow-unknown] [--acknowledge vendor=reason]... [--json]
volter-world up <config> --env-out=<path> [--name <name>] [--mode local|share|sealed] [--isolation process|colocated|worker] [--keep-state] [--app-url <url>] [--json]
volter-world down <name> [--grace-ms <ms>] [--purge] [--if-instance <token>]
volter-world status <name> [--json]
volter-world env <name> -- <command...>
volter-world log <name> [service...] [--no-follow] [--json] [--requests] [--logs]
volter-world diff <name> [--json]
volter-world seed <name> [--entry <file>] [--cwd <dir>]
volter-world reset <name> [--entry <file>] [--cwd <dir>] [--env-out=<new-path>]
volter-world branch <name> <new> [--env-out=<path>]
volter-world checkout <name>
volter-world fetch <name> [--remote <name>] --key <token>
volter-world origin <name>
volter-world activate <name>
volter-world shell <name>
volter-world serve <name> [--port <p>]
volter-world changeset create <name> <changeset> [--verifier "<service> <type>:<id> <field> <op> [value]"]...
volter-world changeset push <changeset> --key <token> [--to <remote>] [--force]
```

`log` (alias `tail`) merges twin actions; `--requests` adds the credential-shape-only request journals.
`--logs` also reads the declared services' existing process output, labeled `stdout` (or `source: "logs"`
in JSON). Those lines retain the vendor's own content and order, with no invented action, timestamp or HTTP status.
Use a service name to select its output, for example `volter-world tail <name> livekit --logs --no-follow`.

### Review and deploy

```text
volter-world changeset show|list|status <changeset>
volter-world changeset verify <changeset>
volter-world changeset approve <changeset> --as <principal> [--note <text>]
volter-world changeset rebase <changeset>
volter-world changeset replay <changeset> --into <world>
volter-world deploy <name> [<changeset>]
volter-world twin <name> <vendor> root <url> [--deploy auto|gated|hold] | credential | refresh
```

### The world itself

```text
volter-world clock <name> show | set <iso> | advance <N s|m|h|d> | shift <N s|m|h|d> | clear   the world's clock (frozen, or running after shift; its application follows it)
volter-world urls <name> [--json]        every service's URL, and each side port's as <service>.<side>
volter-world url <name> [service]        one service's URL, bare; <service>.<side> is a side port's (smtp.inspect: the SMTP twin's mailbox)
volter-world app-url <name> [--app <app>] [--set <url> | --detect <pid|port>] [--host <name>...]   the app's URL; --host: a hostname it answers as in production, routed to it inside the World (`*.example.com`: every name below, as a DNS wildcard); --app: one of several applications in the World, each with its URL (or the World's service of its name) and hostnames
volter-world doctor <name> [--verify-public]
volter-world doctor-prereqs --require local-execution
volter-world run <config> --env-out=<path> [--owner <label>] [--mode ...] [--keep] [--verbose] -- <command...>   a disposable world around one command
volter-world resources [--root <repo>] [--json]   reservations, owner labels and registered attachments
volter-world prune [<name>] [--root <repo>] [--apply] [--json]   preview or delete stopped instance files
volter-world list [--json]
volter-world outdated <name> | audit <name>   each twin's pinned, mounted and catalog version; each twin's recorded grade
volter-world fake-env <NAME...>          structurally valid fake values for env names
volter-world inspect-project [path]      read-only discovery of an app's vendors
volter-world migrate-config <config>     backed-up, atomic format-1 to format-2 migration
volter-world covers <name> --repo <path> check detected vendors and connections against a config or instance
```

### Reaching the world from elsewhere

```text
volter-world attach [<world-ref>] [--owner <label>] [--via env|direct] [--verbose] [-- <command...>]
volter-world manifest <name>
volter-world route <name> add|rm|ls [host]
volter-world reflect <name> --target-ip <ip> [--port <p>] [--resolver-port <p>]
volter-world share <name> [--service app] [--verify /path | --no-verify] [--provider cloudflare-quick|command]
volter-world unshare <name> [--service app]
```

`share` takes the tunnel's public URL from the tunnel's own answer, never its output (architecture D9): `cloudflared`
is started with `--metrics 127.0.0.1:<port>` and asked `GET /quicktunnel` for the hostname it registered; a custom
command (`--provider command`) writes its public origin, newline-terminated, to the file named by `VOLTER_SHARE_URL_FILE`.

`attach <url> [--token <t>] -- <command>` runs the command against a world served elsewhere: the
world's twin URLs, its endpoint variables and its key in the environment, the injector preloaded,
and a loopback listener for each of the world's streams (its TCP twins: smtp, PlanetScale's MySQL
wire) with the variables its client reads pointed there, so a command in any language connects to
`127.0.0.1` ([HTTP API](./http-api.md), the World's manifest).

`volter world run`, `volter-world run`, `attach --via env` and `env` suppress the
injector's informational routing banner by default, including in child processes.
Pass `--verbose` before `--` to show it. An admitted `VOLTER_TWIN_INJECT_QUIET`
environment setting is preserved unless `--verbose` overrides it (`1` hides the banner,
`0` shows it). Routing, warnings and errors are unchanged.

A config can use `runtime.environment.strip: ["*"]` to discard the caller's inherited environment,
including caller Node preloads. Declare needed variables such as `PATH` and `HOME` in
`runtime.environment.values`.
World still supplies its vendor URLs, credentials and injection to services and commands run within it.
Named filters and prefix filters such as `HERMES_*` remain available. This filters environment
inheritance; it does not isolate files or OS credentials.

`covers` checks detected SDKs, vendor endpoints and native database connections. For example,
a PlanetScale HTTP twin does not cover Prisma's separate MySQL connection. Database URL
schemes identify native connections; Prisma schema evidence and unresolved clients also appear.
Native coverage requires the app's endpoint variable to be wired to a declared service. External
endpoint discovery stays unknown until `up` records it. Unknowns fail by default; `--allow-unknown`
leaves them visible and explicitly accepts them. Configuration-driven or dynamically selected
clients can remain unknown. This static check does not prove application behavior or that two
interfaces share state; verify those with an app scenario.

`init` classifies repository evidence by usage and saves `discovery.selection`; `covers` applies
the same saved selection from the config or running instance. Excluded evidence stays visible but
does not fail coverage. Declared twins are checked for routing even when discovery does not find them.

`volter-world up --keep-state` boots over the state the World's directory already holds instead of starting it empty:
the way a World carried elsewhere with its data (a browser tab's image of it) comes up.

`volter-world up`, `run`, `env` and local `attach --via env` accept `--owner <label>` to identify
the task using the world. Commands started by `env` or local `attach --via env` inherit the world's label unless overridden;
each command has its own record. `resources` shows registered commands and identifies
untracked consumers as unknown. The label describes ownership; it does not grant access.
Finish active commands before `down`; uncertain command records also prevent
teardown. `volter-world consumers retire <world> [--consumer <id>]` resolves an uncertain
record whose runner, command and process group are all gone on this host (a runner killed
before it recorded completion), and keeps any other with its reason; it never runs on its own. `prune` only deletes files after a successful teardown, preserving source configs
and seeds. `resources` reports actual filesystem free bytes and lifecycle ownership. Its JSON schema
version 2 distinguishes observed capacity from declarations; it exposes no synthetic reserved
or admission-available totals. Legacy records are listed for migration, never subtracted from
free space or removed by inspection. Unreadable unrelated records do not block startup.
An unfinished lifecycle blocks replacement/pruning of that instance until verified teardown.
Runtime v3 accepts config `resources` with a deprecation warning; it does not turn declarations
into OS limits. Hosted Worlds with legacy declarations require reviewed migration first.
Stop a legacy installation with its pinned old runtime before upgrading all its entrypoints;
resume retained state with `volter world up`, not a fresh operator `up`/`run`. Keep old ownership
records until verified cleanup. Mixed-version ownership of one instance is unsupported.
`run`'s command runs in the caller's working directory, as `attach`'s does, whatever `--root` names. `run` cleans up when its caller is killed, provided its cleanup process survives; other registered
commands remain protected. `up` and `run --keep` require explicit teardown. Prune removes stopped
instance data separately, so command logs remain available after a failed run.
`list`, `status` and `resources` show instance creation and last observed command use,
including its source and partial coverage. Command heartbeats indicate a command's
presence, not user interaction or vendor traffic; its latest observation survives command
completion. Foreground runs contribute their start and finish times. Inspection does not
refresh use, and a new instance starts with unknown use. Legacy, activated-shell and remote
use remain unknown; an old timestamp does not prove that a world is safe to stop.

## The hosting product

Many worlds on one host, and the platform in front of it (architecture, "The hosted product"; the
guides [host worlds for a team](../guides/host-worlds-for-a-team.md) and
[self-host the platform](../guides/self-host-the-platform.md)).

### `volter-host serve`

`@volter/world-host` serves worlds under one URL by `<org>/<world>`, and provisions, rotates and
removes them through its admin endpoints ([HTTP API](./http-api.md), "The hosting product"). It
prints the admin token and, when `@volter/world-console` is installed beside it, the dashboard's URL.
A user never needs it: a served world is the same thing for one team.

| Flag | Meaning |
|---|---|
| `--dir <worlds>` | required: the directory of bare worlds, one per `<org>/<world>` (`volter world init --bare`) |
| `--host <h>` | the address to bind; `127.0.0.1` by default |
| `--port <p>` | the port to bind; one the system picks by default |
| `--url <origin>` | the origin the host is reached at when that differs from its bind address (a container behind a port map, a proxy): every world's base URL and the printed addresses use it |
| `--console-port <c>` | the dashboard's own port (it is served on a listener of its own, so no twin's script shares its origin); the host's port + 1 by default |
| `--console-url <origin>` | the origin the dashboard is reached at, as `--url` is for the host; on the same site as `--url`, or its links into the twins' screens are refused |
| `--no-origins` | no origin of its own per world: every world's pages on the host's origin (on a loopback host each world is `<world>--<org>.localhost` by default) |
| `--trust <platform origin>` | repeatable: a platform whose passes open these worlds for a person, over https or loopback http; none, and only tokens do |
| `--world-origins <domain>` | each world at an origin of its own under the domain (`<world>--<org>.<domain>`, a wildcard name and certificate pointing at the host), which a browser session and a pass need on a host reached at `--url` |

### `volter-platform serve`

`@volter/world-platform` is where people and orgs are: sign-in through one access provider, orgs,
members, invitations, tokens, the hosts it makes Worlds on, and opening a World for a person with a
pass. Its endpoints are the [platform API](./platform-api.md). It prints its address, the provider,
whether it bills, and the operator token (`<dir>/admin`).

| Flag | Meaning |
|---|---|
| `--dir <state>` | required: the platform's state — `platform.db` (SQLite: sessions, tokens, its directory for `oidc` and `github`, the enrolled hosts, the audit log), and the key it signs passes with |
| `--host <h>` | the address to bind; `127.0.0.1` by default |
| `--port <p>` | the port to bind; one the system picks by default |
| `--url <origin>` | the origin people reach it at (behind a proxy); the provider's callback is `<origin>/-/sign-in/callback`. The bind address by default |
| `--provider volter\|oidc\|github` | the access provider (below); `oidc` when `OIDC_ISSUER` is set, else `volter` |
| `--site <origin>` | the product's site: its docs and legal pages are linked, and making an org asks for its terms. Absent, no terms are asked for |
| `--mail-from <sender>` | the sender of the platform's mail (invitations it keeps, a member added, a World deleted, billing notices); a sender the Resend account behind `RESEND_API_KEY` has verified. `Volter World <noreply@volter.world>` by default |
| `--support-to <address>` | where the Help form's requests are mailed; without it the platform shows no Help form |
| `--client-ip-header <name>` | the header the deploy's own proxy sets to the client's address: `cf-connecting-ip` on Cloudflare, or `x-forwarded-for` behind a proxy that appends to it (its last entry is read). The rate limit keys a request without a token on it; unset, no request names its own address and those requests share one limit, and a platform on a public address says so when it starts |
| `--backup-bucket <bucket>` | archive the state to this S3 bucket nightly, the database as a consistent snapshot (credentials from `AWS_*`); a restore is put in place at the next start |
| `--backup-at <HH:MM>` | the backup's time of day, UTC; `00:00` by default |
| `--tick-every <seconds>` | the billing tick's period, where the platform bills; 3600 by default, 0 never |
| `--deliver-every <seconds>` | how often due webhook deliveries are attempted; 5 by default |

Its environment:

| Names | For |
|---|---|
| `VOLTER_ISSUER`, `VOLTER_CLIENT_ID`, `VOLTER_CLIENT_SECRET` | `--provider volter`: Volter Identity (`https://id.volter.ai` by default) and the client registered for this platform there |
| `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_NAME`, `OIDC_TRUST_EMAIL_DOMAINS` | `--provider oidc`: the issuer, its client, the sign-in button's name, and the domains (comma-separated) in which an address the issuer does not mark verified still counts, for an issuer that sends no `email_verified` (Microsoft Entra) |
| `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_URL`, `GITHUB_API_URL` | `--provider github`: the OAuth app, and for GitHub Enterprise Server its web address (https) and API (`<GITHUB_URL>/api/v3` by default) |
| `RESEND_API_KEY` | send the platform's mail through Resend; without it every mail is written to the log instead |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_ENDPOINT_URL` | `--backup-bucket`'s S3 (`AWS_ENDPOINT_URL` for an S3-compatible store) |
| `POLAR_ACCESS_TOKEN`, `POLAR_SERVER` | Volter's cloud only: bill through Polar with Volter's biller (`@volter/world-billing`, which is not published). Set where that biller is not installed, the platform refuses to start |

`volter-world --help` prints every flag.

## Retiring names

`volter remote serve` is the hosting product's verb under its old name; it answers for one
release beside `volter-host`.
