# CLI commands

This page lists every `speedwave` subcommand. For what the CLI is and when to reach for it over Desktop, see the [CLI feature overview](/docs/features/cli/).

## Command list

| Command | Purpose |
| --- | --- |
| `speedwave [--project <name>]` | Start the project's containers and open an interactive Claude session |
| `speedwave init [name]` | Register the current directory as a project |
| `speedwave login [--project <name>]` | Sign in to Anthropic |
| `speedwave logout [--project <name>]` | Delete the project's Claude credentials |
| `speedwave check` | Run prerequisite and security checks |
| `speedwave update [--project <name>]` | Rebuild images and recreate containers |
| `speedwave self-update` | Replace the CLI binary with the latest release |
| `speedwave plugin install <path.zip>` | Install a signed plugin |
| `speedwave plugin list` | List installed plugins and their status |
| `speedwave plugin remove <slug>` | Remove a plugin |
| `speedwave plugin enable <id> --project <name>` | Enable a plugin for a project |
| `speedwave plugin disable <id> --project <name>` | Disable a plugin for a project |
| `speedwave --help` / `-h` / `help` | Print usage and exit |

An unknown command prints `unknown command: '<x>'. Run 'speedwave --help' for usage.` An extra argument after a valid command prints `unexpected argument: '<x>'`. `--help`, `-h`, and `help` work without the runtime running, so you can check the command list before Desktop is installed or while a setup is broken.

## How the CLI picks a project

Most commands act on one project, and `--project <name>` applies to bare run, `login`, `logout`, and `update`. The CLI resolves the target project in this order:

1. The `--project <name>` flag, if you pass it. Both `--project <name>` and `--project=<name>` work, from any directory, and the name must match a registered project.
2. The active project from `active_project` in `~/.speedwave/config.json`, set by the Desktop project switcher.
3. The first configured project, if none is marked active.

If none of those resolve, the command errors with `No project configured`.

Your working directory never selects the project. The one exception is `speedwave init`, which registers the directory you are standing in.

A bare `speedwave` run also builds any container images your project is missing before it starts, printing `Built N container image(s) for this app version` when it does. After a build, it syncs Claude skills and commands into the project (warning that skills may be stale if the sync or the build lookup fails) and then prunes superseded image tags, warning on failure without stopping the run. This is separate from the explicit [`speedwave update`](#update-containers) command.

## Register a project

`speedwave init` registers the current directory as a project. With no name, Speedwave derives one from the directory name:

```bash
speedwave init          # registers as "acme"
speedwave init my-app   # registers under a name you choose
```

Project names are at most 63 characters, start with a lowercase letter or digit, and use only `a-z`, `0-9`, `_`, `.`, and `-`. Uppercase or other characters are rejected, and Speedwave suggests a lowercase form. If the directory is already registered, `init` prints the existing name and exits without error. It does not need the runtime running.

## Sign in and out

`speedwave login` starts the project's containers, then execs `claude auth login --claudeai` directly inside the container, so OAuth starts at once. There is no `/login` prompt to type: the CLI prints `Starting Anthropic sign-in. Follow the prompt, then close the terminal when done.` and the process exits with that command's exit code. Claude Code stores the resulting credentials in the project's claude-home mount, so they survive restarts.

Before starting the session, `login` also forces Anthropic to be the project's active LLM provider. If a custom provider (for example a local model in LM Studio) is already configured, Speedwave migrates that configuration first so it is not erased, then switches the active provider to Anthropic. If this provider switch fails, `login` exits with an error and status 1 before the session ever starts. The exec itself unsets any provider override environment variables and points `ANTHROPIC_BASE_URL` at Speedwave's proxy, so a stale local-provider setting can't leak into the sign-in.

`speedwave logout` deletes the project's Claude credential files, `.claude/.credentials.json` and `.claude.json`, from its claude-home directory. It does not need the runtime running, it is safe to run twice, and it prints `No Claude credentials found` when there is nothing to remove. `logout` clears Claude's sign-in only; to remove a plugin's stored credentials and config, use Desktop, which owns that cleanup. See [Using plugins](/docs/plugins/using-plugins/).

## Check prerequisites and security

`speedwave check` runs the operating-system prerequisite checks and the compose security check, prints an OK or FAIL line per rule, and exits 0 when everything passes or 1 when something fails. It is diagnostic only and does not start containers; it reports permission problems but does not fix them. See [SecurityCheck and its rules](/docs/security/securitycheck/) for what the security check validates.
**Caution:** Before most commands run, the CLI audits your installed plugins' signatures. If a plugin no longer matches what was signed, the command prints the affected plugins to standard error and exits with code 2 before doing anything else. Recover with `speedwave plugin remove <slug>`, which skips the audit on purpose. The audit also skips `--help`, `self-update`, `init`, `plugin install`, and `plugin list`.

## Update containers

`speedwave update` rebuilds the built-in images whose inputs changed and recreates the containers. On success it prints `Updated N containers (M images rebuilt)`.

Before the update runs, Speedwave writes a snapshot to `~/.speedwave/snapshots/<project>/snapshot.json`, holding the compose file and the enabled plugin manifests from before the update. If the update fails after the containers are torn down, Speedwave restores that snapshot and prints `Rolled back to the previous container state.` If the rollback itself fails, Speedwave tells you to run `speedwave` to start the containers manually.

## Update the CLI

`speedwave self-update` downloads the latest `speedwave` binary from GitHub Releases and replaces the one you are running. When the version changes, it then rebuilds your container images, printing `Rebuilding container images...` and then `Container images rebuilt successfully.`

If you run `self-update` from inside the Desktop `.app` bundle, it refuses and tells you to update through Desktop instead, which keeps the CLI and Desktop versions aligned.

Separately, other CLI commands also check in the background for a newer release, at most once a day, caching the result at `<data-dir>/update-check.json`. When a newer version is available, it prints `Update available: speedwave <cur> -> <new>. Run: speedwave self-update` to standard error without blocking or delaying the command you ran. This check is skipped for `--help`, for `self-update` itself, and whenever you run from inside the `.app` bundle, since Desktop users update through the app.

## Manage plugins

```bash
speedwave plugin install ./acme-plugin.zip
speedwave plugin list
speedwave plugin remove <slug>
speedwave plugin enable <service_id> --project acme
speedwave plugin disable <service_id> --project acme
```

`install` verifies the plugin's Ed25519 signature, extracts it, and registers it, printing `Plugin '<name>' (<slug>) installed successfully`. If the image build is deferred, it prints a message to standard error noting the build will retry on the next launch, but still exits 0. `list` prints each plugin as `<name> (<slug>): <version>` with `[verified]` or `[UNVERIFIED: <reason>]`, or `No plugins installed`, and skips the startup audit. `remove` prints `Plugin '<slug>' removed` and is the recovery command for a plugin that fails verification. `enable` and `disable` both require `--project <name>`: `enable` requires a verified plugin and rejects an unverified one, while `disable` does not require verification, so you can always turn a bad plugin off.