> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boat.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI Reference

> Complete reference for all box CLI commands and global flags.

## Installation & updates

```bash theme={null}
# Install (run once)
curl -fsSL https://box.ascii.dev/install | sh

# Check for and apply updates
box self-update
```

The CLI auto-checks for updates on each run. Suppress with `--no-update`.

***

## Global flags

These flags work with any command:

| Flag                 | Description                                                                                                          |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `--api-url <URL>`    | Override the API endpoint (env: `BOX_API_URL`)                                                                       |
| `--org <ID_OR_NAME>` | Run this command against an organization wallet (env: `BOX_ORG`). Does not change the sticky `box org switch` scope. |
| `--json`             | Output machine-readable JSON instead of human text                                                                   |
| `--no-update`        | Skip the automatic update check                                                                                      |

`--json` is enabled automatically whenever output is piped or redirected, so scripts get JSONL without passing the flag.

Wherever a command takes a box ID, two aliases also work: `current` (the last box created in this shell) and `self` (the box you are running inside, when using the CLI from within a box).

***

## Shell completion

`box completions <shell>` prints a completion script for bash, zsh, fish, or PowerShell. It completes subcommands and your live box IDs (plus `current` and `self`), with a 15 second cache so repeated tabs are instant.

```bash theme={null}
# bash: add to ~/.bashrc
eval "$(box completions bash)"

# zsh: add to ~/.zshrc, after compinit
eval "$(box completions zsh)"

# fish
box completions fish > ~/.config/fish/completions/box.fish

# PowerShell: add to $PROFILE
box completions powershell | Out-String | Invoke-Expression
```

***

## Authentication

### `box onboard`

Sets this machine up: signs you in through the browser, saves your token, then asks only for what your account still needs: the third-party safety answer, and plan checkout.

```bash theme={null}
box onboard
box onboard --google
box onboard --email you@example.com
```

It asks which sign-in method to use (GitHub, Google, or a 6-digit code emailed to you), and `--google` / `--email` answer that question up front. See [Quickstart](/box/quickstart#sign-in-methods).

Safe to re-run. On a reinstall or a second machine it recognises the account you already have and skips each step that account has finished:

| Your account already has         | What `box onboard` does                                                                         |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| An active plan                   | Prints your plan and usage. **No Stripe Checkout**, no second subscription.                     |
| A plan with depleted usage       | Points you at the billing dashboard to top up or change tier, instead of starting another plan. |
| Answered what your Boxes are for | Does not ask that question again, whichever way you answered.                                   |
| A past subscription              | Offers checkout without promising a 7-day trial, which Stripe grants once per customer.         |

The answer is recorded on the environment when you give it, so both answers are remembered, not just "a platform of mine". A plan is never treated as an answer, since dashboard checkout does not ask the question.

See [Reinstalling, or a second machine](/box/quickstart#reinstalling-or-a-second-machine).

### `box login [key]`

Sign in with an existing API token, or start a browser sign-in flow if no key is given. GitHub is the default; Google and email also work.

```bash theme={null}
box login
box login --google
box login --email you@example.com
box login --key-stdin <<< "$BOX_API_KEY"
box login box_abc123...
```

`--key-stdin` reads the token from standard input instead of the command line, keeping it out of the process arguments that any other user on the host can read. Prefer it for non-interactive sign-in; see [Use in Code](/box/use-in-code#authenticate). Passing the token positionally still works. The two are mutually exclusive. `--key-stdin` needs a box CLI newer than 0.1.211.

Without a key the CLI prints a URL, opens it, and waits up to six minutes for you to finish in the browser. `--email` sends a code to that address; omit the address and the browser page asks for it. Disposable email domains are refused.

Every method lands on the same account only if it is already **connected** to it. A method you have never used before starts a new account, even when the email matches; add connections from [Dashboard > Account](https://box.ascii.dev/box/dashboard?tab=account) instead. See [Quickstart](/box/quickstart#sign-in-methods).

Create production API keys with `box api-key create` or from the dashboard API Keys page. See [API Keys](/box/api-keys) for key management and [Use in Docker](/box/use-in-production) for Docker and hosted-worker setup.

### `box api-key`

Create and manage API keys for SDKs, CI, and other projects.

```bash theme={null}
box api-key create my-project --ttl 90d --preset ci
box api-key create my-project --ttl 7d --box bx_123 --actions box.read,exec
box api-key list                # scope, expiry, last used
box api-key list --all          # also platform-managed per-box machine keys
box api-key usage <id>          # 30-day requests and live resource count
box api-key usage <id> --verbose  # plus Boxes/Agents split and created list
box api-key rotate <id>         # old secret stops working, new one shown once
box api-key revoke <id>
```

`--ttl` defaults to 90d and cannot exceed 365d. `--box` and `--env` are repeatable. `--preset` is `read-only`, `full-box`, `ci`, or `admin`. `--actions` and `--preset` cannot be combined.

Secrets are shown once at `create` and `rotate` and can never be retrieved again. `list` shows prefix, last four, scope, expiry, 30-day request total, and live resource count. `usage` still works after revoke. Revoke does not delete Boxes or Agents.

Create calls the dedicated `POST /api/box/v1/api-keys/scoped` endpoint and requires a browser session or an admin-scoped token. During rollout it may be temporarily unavailable; `api-key list` remains available. Rotate and revoke stay session-gated. See [API Keys](/box/api-keys).

### `box webhook`

Register account-wide lifecycle webhooks for automation.

```bash theme={null}
box webhook create https://example.com/hooks/box --event ready --event error
box webhook list
box webhook rotate <id>
box webhook remove <id>
```

Supported events are `box.ready`, `box.error`, `box.archived`, and `box.hydrated`. Omit `--event` to subscribe to all four. The signing secret is shown only at create/rotate time. See [Webhooks](/box/webhooks).

### `box logout`

Sign out and clear the local token.

```bash theme={null}
box logout
```

### `box status`

Show API health, the signed-in account and plan, and the local config path.

```bash theme={null}
box status
```

***

## Box lifecycle

### `box new`

Create a new box.

```bash theme={null}
box new [--ttl SECONDS]
box new --type small
box new --type large
box new --type xlarge
box new --no-auto-stop
box new --env KEY=VALUE --env OTHER=VALUE
box new --no-env
box new --setup-file ./setup.sh
box new --environment users
box new --from web-stack
box --org acme new
box org switch acme
box new
box new --personal
```

| Flag             | Default               | Maximum                       |
| ---------------- | --------------------- | ----------------------------- |
| `--type`         | `default`             | `small`, `large`, or `xlarge` |
| `--ttl`          | `3600` (1 hour)       | `2592000` (30 days)           |
| `--no-auto-stop` | off                   | no automatic stop             |
| `--env`, `-e`    | none                  | 100 variables, 64KB total     |
| `--environment`  | your default (`base`) | one named environment         |
| `--no-env`       | off                   | none                          |
| `--setup-file`   | none                  | 64KB script file              |
| `--from`         | none                  | one named snapshot            |
| `--personal`     | off                   | bills you, not the active org |

`--type` picks the machine size: `small` runs at half rate, `default` at 1x, `large` at 2x, and `xlarge` at \$0.20 per running hour. `xlarge` requires the effective \$100 plan or higher and a bare-metal operator allocation. A fork inherits the source box's size unless you pass `--type`, and `box resume --type` moves an existing box between sizes. See [Machine Capabilities](/box/machines) for the specs of each size and [Billing & Limits](/box/billing) for what they cost.

<Note>
  On the free trial, `--no-auto-stop` and a `--ttl` above 2 hours are refused, and `--type large` is not available. See [On the free trial](/box/billing#on-the-free-trial).
</Note>

`--env` sets per-box environment variables on top of your dashboard secrets; per-box values win on name conflicts. See [Environments](/box/environments).

`--environment <name>` picks which environment the box starts from: its repositories, secrets, and credentials. Omit it to use your default. An unknown name is rejected before the box is created, so a typo costs you nothing. Note that `--environment` and `--env` are unrelated: one picks a template, the other sets a variable on this one box. See [Environments](/box/environments).

`--no-env` creates a box that receives none of the secrets attached to your account, and confines the box to itself so it can't act on your account or other boxes. Use it for boxes you give to your own users. SSH, SCP, desktop, snapshots, and public URLs still work. See [Environments](/box/environments). For more than the occasional box, prefer an environment marked safe for third parties, which applies the same protection to every box that uses it and survives forks and resumes.

`--setup-file` reads a local shell script (UTF-8, up to 64KB) and runs it on the box in the background after the box is ready, and it never delays `ready`. Watch the outcome as `setupStatus` (`pending`, `running`, `done`, `failed`) and `setupError` in `box info`. It combines with `--from`.

`--personal` bills you even when an org wallet is active. Day to day, run `box org switch` once and every later `box new` follows it. Override one create with `box --org <org> new` or `box --org personal new`. See [`box org`](#box-org).

`--from <name>` starts the box from a named snapshot you saved with `box snapshot <id> <name>`, so the stack is already installed. It does not carry the source box's environment: pass `--environment`, or take your default. Note this is unrelated to `box env set-file --from`, which reads a local file. See [Snapshots & Copies](/box/snapshots).

Returns the box ID, IP, and initial state.

For a long uninterrupted workflow, disable auto-stop:

```bash theme={null}
box new --no-auto-stop
```

See [Long-Running Tasks](/box/long-running-tasks).

### `box list`

List your up/running boxes with their current state and ID. By default, `box list` is equivalent to `box list --filter r`.

```bash theme={null}
box list
box list --filter s
box list --filter sr
box list --all
```

Use `--filter` with state group letters:

| Filter | Includes                                                |
| ------ | ------------------------------------------------------- |
| `r`    | Up/running boxes: `ready`, `cloning`, `idle`, `running` |
| `s`    | Stopped boxes: `stopped`                                |
| `p`    | Pending boxes: `init`, `provisioning`                   |
| `t`    | Stopping boxes: `stopping`                              |
| `e`    | Error boxes: `error`                                    |

Combine letters, for example `box list --filter sr` lists stopped and up/running boxes. Use `--all` to include every state.

### `box extend <id>`

Change the auto-stop timer for an existing Box.

```bash theme={null}
box extend bx_f7k2q9hd --hours 12
box extend bx_f7k2q9hd --ttl 2592000
box extend bx_f7k2q9hd --no-auto-stop
```

Use `--hours` or `--ttl` for a timed extension. Use `--no-auto-stop` when the Box should keep running until you stop it yourself.

### `box info <id>`

Get details for a specific box: state, IP, desktop availability, TTL remaining.

```bash theme={null}
box info bx_f7k2q9hd
```

### `box stop <id>`

Pause a running box. Creates a snapshot then stops billing. The box enters `archiving` → `archived`.

```bash theme={null}
box stop bx_f7k2q9hd
box stop bx_f7k2q9hd --force
```

<Note>Snapshotting takes a moment. The box is not yet stopped when the command returns; poll `box info` to confirm `archived` state.</Note>

**If a stop is refused.** Stopping saves the disk first. If that save is failing, we refuse the stop and leave the box running rather than discard your work, retry automatically, and email you. **You are not billed for the time your box spends in that state**: the meter pauses by itself from the first failed attempt. See [When a stop is refused](/box/snapshots#when-a-stop-is-refused).

`--force` stops the box anyway and permanently loses everything written since the last successful snapshot. It is irreversible, so only use it after a stop has already failed.

<Warning>`--force` discards unsaved work. Check `box info` for the last snapshot time first, so you know exactly what you are giving up.</Warning>

### `box delete <id>`

Permanently delete a box **and its snapshots**. The box is force-stopped without a final snapshot, then every snapshot chain it exclusively owns is deleted from storage.

```bash theme={null}
box delete bx_f7k2q9hd
box delete bx_f7k2q9hd --yes
box delete self --yes              # from inside a box
```

<Warning>
  `box delete` destroys data. A deleted box cannot be resumed, forked or recovered, and there is no deleted-boxes list to restore from.

  **If you want the data usable later, `box stop` it instead of deleting it.** A stopped box is free, keeps its disk, and `box resume` brings it back where you left it.
</Warning>

The command asks for confirmation. Pass `--yes` to skip the prompt; in scripts (`--json`, or no terminal attached) `--yes` is required and the command refuses without it rather than hanging.

Deletion is not instant: the machine has to be torn down before its data can be removed. The box disappears from `box list` right away, the command prints the deletion operation id (`bdop_…`), and then follows that operation until it finishes.

**What survives.** Snapshot data shared with something else is kept, because deleting it would break whatever else reads it:

* a box you **forked** from this one, or a **resume** of it, restores from the same physical snapshot objects
* a **named snapshot** saved from this box (`box snapshot <id> <name>`) keeps its data; remove it with `box snapshot rm <name>` if you want those bytes gone too

Storage is released once the last box or named snapshot using a chain is gone.

### `box deletion status <operation-id>`

Check back on a deletion you started earlier, here or through the API. Statuses are `pending`, `processing`, `blocked` and `completed`. `blocked` is not a dead end: an attempt hit something it could not finish yet, most often a snapshot another box still reads, and it is retried automatically with backoff.

```bash theme={null}
box deletion status bdop_0123456789abcdef0123456789abcdef
```

See [Data retention and deletion](/box/data-retention).

### `box resume <id>`

Resume a stopped box from its last snapshot. The box is usable in a few seconds regardless of data size; remaining file content streams in the background. See [Snapshots](/box/snapshots).

```bash theme={null}
box resume bx_f7k2q9hd
box resume --no-env bx_f7k2q9hd
box resume --type large bx_f7k2q9hd
box resume -e KEY=VALUE bx_f7k2q9hd
box resume --ttl 7200 bx_f7k2q9hd
box resume --no-auto-stop bx_f7k2q9hd
```

Requires the box to have a completed snapshot (i.e. it was stopped cleanly via `box stop`).

Use `-e`/`--env` to set per-box environment variables on resume, with the same semantics as `box new -e`: repeat for multiple values, per-box values override dashboard variables with the same name, and the set you pass **replaces** the box's current per-box variables.

Use `--environment <name>` to move the box onto a different environment as it resumes. Omit it and the box keeps the environment version it already had; a resume never silently moves a box to a newer version. An unknown name is rejected before anything changes. If the new environment withholds something the box currently holds, that secret is scrubbed off the disk during the resume and does not come back. See [Environments](/box/environments).

Use `--no-env` to resume a stopped box after dropping your account secrets and scrubbing owner secrets inherited from the snapshot. This is one-way: the box stays no-env afterward.

Use `--type` to resume onto a different machine size (`small`, `default`, `large`, or `xlarge`). Omit it to keep the box's current size. Shrinking is refused if the box holds more data than the smaller machine can take, and the box is left untouched. See [Machine Capabilities](/box/machines).

Use `--ttl <seconds>` to set the resumed box's lifetime, or `--no-auto-stop` to switch auto-stop off. Omit both and the box keeps the setting it already had, so a resume never quietly shortens or extends it.

Resume behaves like a server reboot: systemd services you enabled start again automatically. Processes you ran by hand (dev servers, background jobs, tunnels, desktop sessions) do not survive; restart them, or make them a systemd service.

For `xlarge`, resume with the same explicit bare-metal operator pin. The effective billing plan must be \$100 or higher.

### `box fork <id>`

Clone a box from its latest snapshot into a new independent box.

```bash theme={null}
box fork bx_f7k2q9hd
box fork --no-env bx_f7k2q9hd
box fork --type small bx_f7k2q9hd
box fork -e TENANT_ID=acme bx_f7k2q9hd
box fork --ttl 600 bx_f7k2q9hd
box fork --no-auto-stop bx_f7k2q9hd
```

Returns the new box ID asynchronously (HTTP 202). Requires the source box to have a completed snapshot.

Forking copies the snapshotted filesystem into a new Box; enabled systemd services start automatically, like on resume. Processes the source box ran by hand are not forked; start them again in the fork if needed.

Use `--type` to give the fork a different machine size from its source. The source box is never modified.

Use `--ttl <seconds>` or `--no-auto-stop` to set the fork's lifetime. A fork does **not** inherit its source's: it defaults to 1 hour, so forking a box with auto-stop switched off does not silently produce another box nothing will stop.

Use `--environment <name>` to point the fork at a different environment. Omit it and the fork inherits exactly the environment version its source is on, so a fork never picks up configuration the source never had. The source box is never modified either way. See [Environments](/box/environments).

Use `--no-env` to fork into a no-env box. A fork of a no-env source is always no-env.

Use `-e`/`--env` to set per-box environment variables on the fork, with the same semantics as `box new -e`. The set you pass **replaces** the per-box variables the fork would inherit from the source box; omit it to inherit them unchanged.

### `box events <id>`

Read the agent's work on a Box: prompts and responses. By default it streams **every conversation** on the box; each event carries a `conversationId` (visible with `--json`). Add `--follow` to keep polling for new events, or `--convo` to watch a single conversation.

<Note>
  These are the agent's conversations, not the Box's lifecycle. A Box that has never been prompted returns an empty list even though it started, stopped and resumed. For lifecycle state use `box info`.
</Note>

```bash theme={null}
box events bx_f7k2q9hd
box events bx_f7k2q9hd --follow
box events bx_f7k2q9hd --convo <conversationId>          # just one conversation
box events bx_f7k2q9hd --convo current                    # this shell's current conversation
```

| Flag           | Description                                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `--follow`     | Continue polling and printing new events until interrupted                                                                 |
| `--convo <id>` | Only stream this conversation (or `current`). Repeat for several. Omit to stream all conversations. Alias `--conversation` |

### `box conversations [id]`

List a box's conversations, most recently prompted first: id, last activity, prompt count, whether a turn is running, the last harness and model, and a preview of the last prompt. Alias `box convos`.

```bash theme={null}
box conversations
box convos bx_f7k2q9hd
box conversations bx_f7k2q9hd --json      # one JSON line per conversation
```

```
ID                                    When         Prompts  Running  Harness/model      Last prompt
8f1c2b7a-3d4e-4f5a-9b0c-1d2e3f4a5b6c  just now     1        yes      pi/claude-sonnet-5 Investigate the flaky CI job  [box current, this shell]
2c0d9e4b-7a1f-4c3e-8b5d-6e7f8a9b0c1d  4 mins ago   2        no       claude             Now add tests
```

Two markers can follow the last prompt: `box current` is the conversation a bare `box prompt` from anywhere would continue (the box's most recently prompted one); `this shell` is the one a bare `box prompt` from this shell continues. Copy an id into `box prompt --resume <id>`, `box steer --convo <id>`, `box events --convo <id>` or `box interrupt --convo <id>`.

### `box steer <id> <message>`

Send a message to a turn that is **already running**, without stopping it. The agent takes it into account and keeps what it was doing. This is the third option next to `box prompt` (which waits for the turn to end) and `box interrupt` (which throws it away).

```bash theme={null}
box steer "Skip the integration tests, the unit tests are enough"
box steer bx_f7k2q9hd "Also update the changelog when you are done"
box steer bx_f7k2q9hd --convo <conversationId> "Focus on the auth module first"
box steer --convo current "Stop after the first failure"
```

Omit `--convo` and it steers the conversation this shell last prompted, the same one a bare `box prompt` continues. Steering a conversation with nothing in flight is refused with `no_running_turn`; use `box prompt` for that.

Claude Code, Codex, pi and Prime Agent take the message into the running turn natively, so nothing is interrupted; if a harness accepts the message but its turn ends without acting on it, Box continues it immediately as its own turn on the same session, so a steer is always acted on. OpenCode has no mid-turn primitive, so Box stops that turn and immediately continues the same conversation with your instruction, keeping all of its memory. The JSON output's `native` field says which path ran, and the steer appears in `box events` as a `steer` event carrying the settled `mode` (`native`, `native-continued`, `fallback`, `late`). See [Integrated agents](/box/integrated-agents).

| Flag           | Description                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `--convo <id>` | Steer this conversation (or `current`). Omit to steer the one this shell last prompted. Alias `--conversation` |

### `box interrupt <id>`

Interrupt running agent work in a Box. By default it stops **every conversation** on the box; scope it to one with `--convo` and the others keep running.

```bash theme={null}
box interrupt bx_f7k2q9hd
box interrupt bx_f7k2q9hd --convo <conversationId>       # stop just one conversation
box interrupt bx_f7k2q9hd --convo current
```

| Flag           | Description                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| `--convo <id>` | Interrupt only this conversation (or `current`). Omit to interrupt the whole box. Alias `--conversation` |

***

## Access

### `box ssh <id> [command]`

SSH into a box, or run a command non-interactively. Manages the key at `~/.ssh/ascii_box_ed25519` automatically.

```bash theme={null}
box ssh bx_f7k2q9hd
box ssh bx_f7k2q9hd "cd /home/user/my-repo && npm test"
box ssh bx_f7k2q9hd -- bash -lc "cd /home/user/my-repo && npm test"
box ssh bx_f7k2q9hd -- bash -s < ./setup.sh
```

### `box exec <id> [command...]`

Run a command in a box over the Boat API, without an SSH session or key setup. `box exec` exits with the remote command's exit code, so it drops straight into scripts and CI.

```bash theme={null}
box exec bx_f7k2q9hd "npm test"
box exec bx_f7k2q9hd --cwd my-repo --timeout 120 -- npm run build
```

| Flag               | Default            | Description                                                          |
| ------------------ | ------------------ | -------------------------------------------------------------------- |
| `--cwd <dir>`      | box work directory | Working directory, relative to the box work directory (`/home/user`) |
| `--timeout <secs>` | `30`               | Synchronous timeout, 1–600 seconds                                   |
| `--detach`         | off                | Run in the background and print the process id                       |
| `--status <pid>`   | none               | Show status and logs of a process started with `--detach`            |

Wait for the box to reach `ready` before running `box exec`; earlier calls are refused with a retryable `box_starting` error.

For anything that may run longer than the 600 second synchronous cap, detach and poll instead:

```bash theme={null}
pid="$(box exec bx_f7k2q9hd --detach "npm run build" | jq -r .processId)"
box exec bx_f7k2q9hd --status "$pid"
```

`--detach` starts the command in the background and answers with a JSON object; take `processId` from it, as above. Remember that `--json` turns itself on whenever output is piped or captured, so inside a script you always get JSON here even without the flag. Output goes to `~/.ascii/processes/<pid>.log` on the box. `--status <pid>` prints whether it is still running, the exit code once finished, and a tail of stdout/stderr. If the box's agent restarts (stop/resume), the status degrades to `lost` but the log files stay on disk. See [Long-Running Tasks](/box/long-running-tasks).

### `box env`

Manage the templates new boxes start from: repositories, secrets, and which of your credentials a box may use. Every change mints a new immutable version. Running boxes stay on the version they started with until `box env upgrade`. See [Environments](/box/environments).

<Warning>
  `--environment` and `--env` are different. `box new --environment staging` picks which environment the box uses. `box new --env KEY=value` sets one variable on that single box, on top of what the environment gives it.
</Warning>

```bash theme={null}
box env list
box env info base
box env new staging
box env rename staging prod
box env default prod
box env rm staging
```

| Command                            | Description                                                                            |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| `box env list`                     | Your environments, with the default marker, latest version, and live boxes per version |
| `box env info <name>`              | One environment in full: toggles, variables, secret files, repos, versions             |
| `box env new <name>`               | Create an environment. Starts with everything injected                                 |
| `box env rename <name> <new-name>` | Rename. Boxes stay pinned to their versions                                            |
| `box env default <name>`           | Make it the environment new boxes use when none is named                               |
| `box env rm <name>`                | Soft delete. Pinned boxes keep running; new boxes cannot use it                        |

#### `box env set <name>`

Change what the environment injects.

```bash theme={null}
box env set base --safe-for-third-parties true
box env set base --github true --secrets false
```

| Flag                                     | Description                                                                   |
| ---------------------------------------- | ----------------------------------------------------------------------------- |
| `--safe-for-third-parties <true\|false>` | Master switch. `true` injects nothing of yours and overrides every flag below |
| `--github <true\|false>`                 | Your GitHub access: token and repository clones                               |
| `--secrets <true\|false>`                | Your environment variables and secret files                                   |
| `--box-credentials <true\|false>`        | The in-box Boat CLI credentials, so the box can manage its own lifecycle      |
| `--agents-credentials <true\|false>`     | Your agent provider logins (Claude, Codex, and so on)                         |

#### Contents

```bash theme={null}
box env set-var base STRIPE_KEY=sk_live_123
box env rm-var base STRIPE_KEY
box env set-file base backend/.env --from ./local.env
cat ./local.env | box env set-file base backend/.env
box env rm-file base backend/.env
box env add-repo base octocat/hello-world --branch develop
box env rm-repo base octocat/hello-world
```

`set-file` reads contents from `--from <local-file>`, or from stdin when `--from` is omitted. Paths are relative to the box work directory (`/home/user`).

#### `box env upgrade <name>`

Move this environment's boxes onto its latest version. Live boxes get the new configuration pushed immediately, with any secret the new version withholds scrubbed off the machine first. Stopped boxes pick it up when they resume. Nothing upgrades on its own.

```bash theme={null}
box env upgrade base
```

<Warning>
  A secret the new version withholds is deleted from the box's disk, not hidden. Re-pinning to the older version does not bring it back.
</Warning>

### `box host <id> <port>`

Expose a running service inside a Box on a stable HTTPS URL without opening an interactive SSH session.

```bash theme={null}
box host bx_f7k2q9hd 3000
box host bx_f7k2q9hd 3000 --title "Login preview"
box host bx_f7k2q9hd 3000 --public
box host bx_f7k2q9hd 3000 --json
```

The command opens the Box firewall, registers the HTTPS subdomain, and prints the URL. Calling it again for the same Box and port returns the same URL.

<Warning>
  The service you expose must listen on `0.0.0.0`, not only on `localhost` or `127.0.0.1`.
</Warning>

| Flag              | Default | Description                                                                   |
| ----------------- | ------- | ----------------------------------------------------------------------------- |
| `--title <title>` | none    | Set the display title for the hosted port.                                    |
| `--private`       | on      | Require the generated `_token` query parameter to access the URL.             |
| `--public`        | off     | Clear any saved access token and return a URL that does not require `_token`. |

### `box scp`

Copy files to/from a box. Use `bx_<id>:/path` as the remote address.

```bash theme={null}
# Download a file
box scp bx_f7k2q9hd:/home/user/output.zip ./output.zip

# Upload a file
box scp ./local-file.txt bx_f7k2q9hd:/home/user/

# Copy a directory recursively
box scp --recursive ./my-project bx_f7k2q9hd:/home/user/
```

| Flag          | Description                  |
| ------------- | ---------------------------- |
| `--recursive` | Copy directories recursively |

On macOS and Linux, recursive copies to a new directory stream one archive over SSH when `tar` is available on both ends, avoiding per-file network round trips. The new directory becomes visible only after a complete transfer. Files, existing-directory merges, and unsupported trees use the installed OpenSSH `scp`.

### `box forward <id>`

Forward one TCP port from a box to your local machine.

```bash theme={null}
box forward bx_f7k2q9hd --remote 8080
box forward bx_f7k2q9hd --remote 3000 --bind 0.0.0.0
```

| Flag              | Default        | Description            |
| ----------------- | -------------- | ---------------------- |
| `--remote <port>` | required       | Remote port on the box |
| `--local <port>`  | same as remote | Local port to bind     |
| `--bind <addr>`   | `127.0.0.1`    | Local bind address     |

#### `--reverse`: reach your machine from the box

`--reverse` sends the tunnel the other way. A port on your machine becomes
reachable inside the box at `127.0.0.1:<remote>`, so an agent in the box can
call an MCP server, a local model, or a webhook receiver that never leaves your
laptop.

```bash theme={null}
# your app on localhost:7777 answers at 127.0.0.1:7777 inside the box
box forward bx_f7k2q9hd --reverse --local 7777

# a local model on 11434 shows up inside the box on 11500
box forward bx_f7k2q9hd --reverse --local 11434 --remote 11500
```

| Flag              | Default       | Description                                                                                                  |
| ----------------- | ------------- | ------------------------------------------------------------------------------------------------------------ |
| `--local <port>`  | required      | Port on your machine to expose                                                                               |
| `--remote <port>` | same as local | Port the box listens on                                                                                      |
| `--bind <addr>`   | `127.0.0.1`   | Bind address **inside the box**. Anything other than loopback needs `GatewayPorts` enabled on the box's sshd |

Both directions run in the foreground until Ctrl+C. If the tunnel drops because
the box moved (`box stop` then `box resume` brings it back on a new IP), the CLI
refetches the connection and redials up to five times before giving up. A
failure at startup, such as the port already being taken on the far side, is
reported straight away instead of being retried.

Nothing is exposed publicly by either direction: the tunnel rides the same SSH
session as `box ssh`, and the box side listens on loopback only. For a public
HTTPS URL for a port inside the box, use [`box host`](#box-host-id-port).

### `box desktop <id>`

Open the box's desktop streaming URL in your browser.

```bash theme={null}
box desktop bx_f7k2q9hd
box desktop bx_f7k2q9hd --vnc
box desktop bx_f7k2q9hd --vnc --public
```

By default the desktop streams over Moonlight (60fps WebRTC). On restrictive or
low-bandwidth networks where WebRTC is choppy or won't connect, use `--vnc` for
a VNC stream that tunnels over plain HTTPS and is far more tolerant of poor
connections. The viewer also offers a one-click switch to VNC if the default
stream struggles.

| Flag       | Default | Description                                                         |
| ---------- | ------- | ------------------------------------------------------------------- |
| `--vnc`    | off     | Stream over VNC instead of Moonlight. More stable on poor networks. |
| `--public` | off     | With `--vnc`, return a URL that does not require an access token.   |

<Note>
  The first `--vnc` on a box takes a few seconds while it prepares the VNC
  stack (the CLI shows a spinner); subsequent opens are instant.
</Note>

### `box browser <id>`

Return a browser-only stream from the same running Box.

```bash theme={null}
box browser bx_f7k2q9hd
box browser bx_f7k2q9hd --profile /data/chrome-profile
```

| Flag               | Default                  | Description                                                                     |
| ------------------ | ------------------------ | ------------------------------------------------------------------------------- |
| `--profile <path>` | a managed dir on the box | Absolute path on the box used as the Chrome user data dir (the Chrome profile). |

`--profile` lets you keep several signed-in Chrome profiles on one box and pick
one per stream. The directory is created and given to the browser user for you.
It must be an absolute path outside system directories, and it must not live
under `/home/user`, which the browser sandbox masks with an empty directory.
Because the profile lives on the box filesystem, snapshots and forks carry it
along. Switching profiles restarts Chrome and leaves the stream up. Boxes
created before this feature return `chrome_profile_unsupported`; recreate the
box on a current image to use the flag.

The stream is 1920x1080 at 60 fps. It shows Chrome with tabs and an address bar, uses an isolated streaming process and identity, does not expose the desktop clipboard bridge, does not create a separate Box, and cannot fall back to the desktop or VNC. Window-close chrome is hidden. Mute, reconnect, and fullscreen stay on the viewer. Closing the last tab opens a new tab and leaves the stream up. Running the command again leaves the live stream up. Opening the new URL in a second tab replaces the first tab and keeps the same Chrome session. New boxes receive the browser-view files at provision. Treat the URL as a secret.

This is display and input confinement, not isolation from the Box itself. Code already running in the Box, especially code with root access, can inspect or modify the browser process and its data.

***

## Snapshots

### `box snapshots [id]`

List snapshots across your boxes, or for one box.

```bash theme={null}
box snapshots
box snapshots bx_f7k2q9hd
```

### `box snapshot <id> <name>`

Save a box's disk under a name, so `box new --from <name>` can deploy it as many times as you like. Reuse a name to replace it.

```bash theme={null}
box snapshot bx_f7k2q9hd web-stack
box snapshot current web-stack     # from inside a box
box snapshot rm web-stack          # remove it and release its storage
```

Saving from a running box takes a fresh capture first, so it can run for minutes; the CLI polls until the snapshot settles at `ready`. You can keep up to 10 named snapshots. See [Snapshots & Copies](/box/snapshots).

### `box snapshot latest|tree|pull`

Inspect or download a snapshot. Works while the box is stopped.

```bash theme={null}
box snapshot latest bx_f7k2q9hd
box snapshot tree <snapshotId>              # files and sizes
box snapshot pull <snapshotId> -o ./restore # download and reassemble
```

`pull` writes `home_user/` (your `/home/user`) and `docker/` (named volumes). See [Snapshots](/box/snapshots).

### `box snapshot delete <snapshotId>`

Permanently delete one ordinary filesystem snapshot. Refused with `409` while a later incremental snapshot or an active restore still depends on it. Named snapshots are removed by name with `box snapshot rm <name>` instead.

```bash theme={null}
box snapshot delete <snapshotId>
box snapshot delete <snapshotId> --yes
```

See [Data retention and deletion](/box/data-retention).

***

## AI agents

### `box prompt [id]`

Send a natural-language prompt to an AI agent running inside the box. Omit the ID to target the current box of your shell (the last `box new` there), or the box you are running inside. Omit `--provider` to use the default harness and model selected on the Agents page of the dashboard.

```bash theme={null}
box prompt "Fix the failing tests in this repo"
box prompt bx_f7k2q9hd --provider claude "Fix the failing tests in this repo"
box prompt bx_f7k2q9hd --provider codex --model gpt-5.4-mini --reasoning-effort xhigh "Refactor auth module"
box prompt --attach ./error.png --attach ./trace.log "Why does this crash?"
```

| Flag                 | Required | Values                                                                                                                                                            |
| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--provider`         | no       | `claude`, `codex`, `pi`, `opencode`, `prime`, `kimi`. Default: the Agents page default harness                                                                    |
| `--model`            | no       | Provider-specific model name. Default: the model selected for that harness on the Agents page                                                                     |
| `--reasoning-effort` | no       | Provider/model-specific thinking level such as `none`, `low`, `medium`, `high`, `xhigh`, or `max`. Which levels a model accepts varies; some have none            |
| `--new`              | no       | Start a new conversation instead of continuing this shell's current one                                                                                           |
| `--resume <id>`      | no       | Continue a specific conversation by id (or `current` for this shell's)                                                                                            |
| `--attach <path>`    | no       | Attach a file (image, PDF, any file). Repeat for multiple. Alias `--image`. Saved on the box under `~/attachments`; images are also sent to vision-capable models |

Run `box prompt --help` to fetch the current provider, model, and reasoning-effort list from the Boat API.

#### Conversations (running agents in parallel)

A box runs **many conversations at once**, each with its own history, so one box can drive several independent agent tasks (different features, different users, a demo) in parallel rather than queuing them one behind another.

```bash theme={null}
box prompt --new "Start on the billing refactor"     # conversation A
box prompt --new "Investigate the flaky test"        # conversation B, runs alongside A
box prompt --resume <A> "Now add tests"              # continue a specific conversation
box prompt "quick follow-up"                          # continues THIS shell's current conversation
box conversations                                     # list them all, with ids to resume
```

* **`--new`** starts a fresh conversation and makes it this shell's current one. Every prompt prints its conversation id right under `queued:` (`conversation: <id>`, with `(new)` when `--new` created it) and carries it as `conversationId` in `--json`.
* **`--resume <id>`** continues a specific conversation; `--resume current` targets this shell's current one.
* **No flag** continues this shell's current conversation. The "current conversation" is scoped to your shell, exactly like the `current` box id, so two shells (or two people) prompting the same box each keep their own thread and don't disturb each other.
* **[`box conversations`](#box-conversations-id)** lists every conversation on the box with its id, prompt count, running state and last prompt, so a thread from another shell or another day can be resumed by id.
* Conversations run in parallel up to a per-box limit that scales with the box's memory; beyond it, further prompts queue and start as turns finish. Continue a conversation on a different `--provider` and its history carries across the switch.

How harnesses, conversations, the agent lifecycle, and parallel runs fit together, with diagrams, is on [Integrated agents](/box/integrated-agents).

***

## Account

### `box limits`

Show remaining machine starts, compute time, and credits. Create, fork, and resume each count as one start.

```bash theme={null}
box limits
box limits --json
```

Human output prints remaining starts per minute, hour and day, remaining compute in hours, and remaining credit-pack dollars. `--json` is the same payload the API returns, including `starts.minute.remaining`, `creditBalanceHours`, and `packBalanceDollars`.

### `box usage <id>`

Show the machine time one box has consumed and what it costs, over its whole life or a window. Works on running and stopped boxes; a running box's figure keeps growing until it stops, and `running` in the JSON says whether it still is (false once stopped, or while a refused stop holds the meter paused).

```bash theme={null}
box usage bx_f7k2q9hd
box usage bx_f7k2q9hd --since 2026-09-01 --until 2026-10-01
box usage bx_f7k2q9hd --json
```

| Flag             | Default      | Description                                     |
| ---------------- | ------------ | ----------------------------------------------- |
| `--since <time>` | box creation | Count from this time, ISO 8601 or Unix seconds  |
| `--until <time>` | now          | Count up to this time, ISO 8601 or Unix seconds |

Human output prints billable machine time (the box type's multiplier applied), the cost at list price, and the window. `--json` is the API payload: `seconds`, `dollars`, `secondsPerDollar`, `boxType`, `billingMultiplier`, `since`, `until` and `running`. See [Per-box usage](/box/billing#per-box-usage).

### `box dashboard`

Open your web dashboard in the browser, already signed in. The link carries a single-use code that expires in 15 minutes and cannot call the API (never your Boat token), so it is safe in a terminal, a log, or a message. If you signed in with a service API key, the link opens the dashboard signed out: creating, rotating, or revoking API keys requires the browser sign-in flow.

```bash theme={null}
box dashboard
```

### `box org`

Show, switch, create, transfer, or delete the **organization wallet** that new boxes bill to. One shared plan and balance. `box org switch` is sticky; `--org` (or `BOX_ORG`) overrides it for one command.

```bash theme={null}
box org                     # show the active wallet
box org list                # organizations you belong to
box org switch acme         # bill new boxes to acme
box org switch personal     # back to your own account
box --org acme new          # bill one create to acme without switching
box --org personal new      # bill one create to your account while an org is sticky
box org create "Acme"
box --org acme org transfer alice
box --org acme org delete --yes
```

`box org transfer` and `box org delete` target the invocation scope (`--org` / `BOX_ORG` if set, otherwise the sticky wallet). Deleting an org with `--org` does not clear a different sticky wallet.

### `box data-retention`

Show or enable zero data retention, which queues every archived box for permanent deletion.

```bash theme={null}
box data-retention status
box data-retention enable
box data-retention enable --yes
```

Enabling requires a browser sign-in session (`box login` without a key), not an API key. See [Data retention and deletion](/box/data-retention).

### `box self-update`

Check for and install the latest CLI release on your current channel.

```bash theme={null}
box self-update
```

Local configuration lives at `~/.config/ascii/box/config.json`. `box status` prints its path along with the resolved API URL, account and plan.
