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

# CLI (external)

> Install the boxd CLI to manage machines from your local terminal.

The boxd CLI lets you manage machines from your local terminal without SSH keys. Recommended for automation and coding agents.

The surface is a **nested tree**. Top-level groups: `machine` (alias `m`), `snapshots` (alias `snap`), `auth`, `manage`, `env`, `config`, plus `docs` / `completions` / `version`. `remove` is the canonical delete verb everywhere (alias `rm`); common aliases are `ls` (list), `info` (get), `ssh` (connect). `boxd new` and `boxd connect` also exist at the top level as shortcuts for `boxd machine new` / `boxd machine connect`.

<Tip>
  `boxd <cmd> --help` is the always-current reference for flags and subcommands, straight from your installed binary. Running bare `boxd` in a terminal drops into an interactive REPL (`boxd (org)>`) where you type the same commands without the `boxd` prefix, with tab-completion and arrow-key pickers for missing arguments.
</Tip>

## Install

```bash theme={"theme":"github-dark"}
curl -fsSL https://boxd.sh/downloads/install.sh | sh
```

Installs the `boxd` binary to `~/.local/bin/boxd` (macOS arm64, Linux x86\_64/arm64). The [client utilities](/reference/client-utilities) — the bridge that brings your clipboard, local files, and browser into a machine (macOS Apple Silicon for now) — are built into the same binary; turn them on with `boxd config set client-utils.enable true`.

It also drops a set of [agent skills](/reference/skills) into `~/.claude/skills/` — `boxd-cli` for everyday CLI driving, plus setup skills that wire boxd into a GitHub repo end-to-end via webhooks. Re-run the installer any time to upgrade the binary and the skills together. (Claude Code only for now — see the [Skills page](/reference/skills) if you want them on another platform.)

## Authentication

```bash theme={"theme":"github-dark"}
boxd auth login                # opens browser for one-time device login
boxd auth logout               # remove stored credentials
boxd auth                      # show your identity and the orgs you belong to
```

Any command that needs auth auto-starts the login flow if no credentials are found. You can also authenticate via token:

```bash theme={"theme":"github-dark"}
boxd --token=<TOKEN> machine list      # pass token directly
BOXD_TOKEN=<TOKEN> boxd machine list   # or via env var
```

## API keys

For CI pipelines, scripts, or agent integrations where the browser login flow isn't an option, mint long-lived API keys under `boxd auth keys`. The raw value is shown **only once** at creation time.

```bash theme={"theme":"github-dark"}
boxd auth keys create NAME                            # mint a new key, raw value to stdout
boxd auth keys create NAME --expires-in-secs=86400    # with an expiry (1 day in this example)
boxd auth keys create NAME --org myorg                # fence the key to a specific org
boxd auth keys create NAME --kind org                 # userless service credential (org admin only)
boxd auth keys list                                   # id, name, prefix, last-used, expires (alias: ls)
boxd auth keys remove ID                              # revoke a key (alias: rm)
```

Every key is fenced to **one org** — it never grants account-wide access. Without `--org`, the key is fenced to your active org context. `--kind member` (default) acts as you within that org; `--kind org` is a userless service credential limited to the org's shared machines (requires org admin).

`keys create` writes the raw `bxd_…` value to stdout; the warning + id + expiry go to stderr. To put it straight into a GitHub secret without copy/paste:

```bash theme={"theme":"github-dark"}
KEY=$(boxd auth keys create "gh-actions deploy owner/repo")
gh secret set BOXD_API_KEY --repo owner/repo --body "$KEY"
```

The key never lands in shell history or scrollback.

`--json` mode is also available for scripting:

| Command                   | JSON shape                                                      |
| ------------------------- | --------------------------------------------------------------- |
| `auth keys create --json` | `{"id", "api_key", "expires_at"}` (`expires_at: 0` = no expiry) |
| `auth keys list --json`   | `[{"id", "name", "prefix", "last_used", "expires"}, …]`         |

## Managing machines

```bash theme={"theme":"github-dark"}
boxd machine new myapp                              # create a machine (~2s boot)
boxd machine new                                    # auto-named
boxd machine new myapp --auto-suspend-timeout=60    # idle secs before auto-suspend (0 disables)
boxd machine new myapp --shared                     # in an org context: shared with the whole org from birth
boxd machine list                                   # list your machines (alias: ls)
boxd machine get myapp                              # detailed info (status, image, auto-suspend; alias: info)
boxd machine fork myapp myapp-v2                    # fork with a full disk copy
boxd machine fork myapp --auto-suspend-timeout=0    # fork with auto-suspend disabled
boxd machine fork shared-vm --shared                # keep a fork of a shared machine shared (default: private to you)
boxd machine rename myapp myapp2 -y                 # rename (reboots to apply the hostname unless stopped)
boxd machine remove myapp -y                        # destroy (requires -y or --confirm; alias: rm)
```

`boxd new myapp` and `boxd m ls` are the top-level / short forms. `machine new` folds in fork (`--fork`) and snapshot restore (`--from-snapshot`):

```bash theme={"theme":"github-dark"}
boxd machine new myapp-v2 --fork myapp              # same as `machine fork myapp myapp-v2`
boxd machine new fresh --from-snapshot my-workspace # create from a snapshot (fast; restores memory + disk)
```

See [Suspend & resume](/how-it-works/suspend-resume) for how the idle timers work. All commands accept `--json`.

### State control

Three categories of state transition, all `boxd machine <verb> <machine>`:

```bash theme={"theme":"github-dark"}
boxd machine start myapp       # start a stopped machine
boxd machine stop myapp        # stop (disk persists; running processes lost)
boxd machine pause myapp       # standby (warm — memory preserved, sub-ms resume)
boxd machine resume myapp      # resume a paused machine
boxd machine hibernate myapp   # cold snapshot to disk now (frees host RAM)
boxd machine wake myapp        # wake a hibernated machine
boxd machine reboot myapp      # reboot (cold — memory lost, ~2s)
```

* **pause / resume**: warm. Freezes the VMM process, keeps memory, running processes, and open sockets intact. Resume is sub-millisecond. Same mechanism as auto-suspend, but user-triggered. Status becomes `standby`.
* **reboot** / **stop** + **start**: cold. Kills the VMM process. Memory lost, takes \~2s.

Use `pause` for cost savings without losing state. Use `reboot` when you need a cold kernel/config restart.

## Running commands

```bash theme={"theme":"github-dark"}
boxd machine exec myapp -- uname -a                        # run a command
boxd machine exec myapp -- 'cd /app && npm start'          # shell constructs work
boxd machine exec myapp -e API_KEY=secret -e DEBUG=1 -- CMD  # env vars
boxd machine exec myapp --timeout 30 -- CMD                # timeout
boxd machine exec myapp --tty -- htop                      # allocate a pseudo-TTY (for interactive tools)
boxd machine exec myapp --json -- echo hello               # JSON: {"output":"hello\n","exit_code":0}
```

Exit codes are forwarded — if the remote command exits 42, `boxd machine exec` exits 42.

The remote command's stdout and stderr are surfaced separately on the local side, so shell redirects work as you'd expect:

```bash theme={"theme":"github-dark"}
boxd machine exec myapp -- gcc -v 2>/dev/null              # drop the version banner (it's on stderr)
boxd machine exec myapp -- cargo build 2>build.log         # only warnings/errors land in build.log
```

PTY execs (`--tty`) are an exception — the kernel TTY layer merges stderr into stdout (that's how terminals work), so everything arrives on local stdout and `2>` filters won't separate them.

<Note>
  The `--` separator is required between the machine name and the command. Without it, tokens like `sudo` or `apt` get parsed as flags and the command errors out.
</Note>

## Interactive access

`boxd connect` drops you straight into an interactive shell on the machine over the boxd API — no SSH config, no host keys, no extra credentials. Auto-resumes paused or hibernated machines on demand. Exit codes forward; type `exit` or Ctrl-D to come back to your local shell.

```bash theme={"theme":"github-dark"}
boxd connect myapp             # drops you into a shell (top-level shortcut)
boxd machine connect myapp     # the full path
boxd machine ssh myapp         # `ssh` is the alias of connect
```

Requires an interactive terminal — `connect` refuses to run in scripts, pipes, or other non-TTY contexts. For automation, use `boxd machine exec` instead:

```bash theme={"theme":"github-dark"}
boxd machine exec myapp -- uptime    # one-shot, scriptable
```

## Editor & SSH integration

The CLI keeps a managed block of `Host` entries in `~/.ssh/config` **automatically** — every `machine new`, `fork`, `list`, `remove`, and `rename` refreshes it, so your SSH config stays in sync as machines come and go. There's no separate command to run.

After the first sync, `<vmname>.boxd` is reachable to plain `ssh`, `scp`, `rsync`, Cursor / VS Code Remote-SSH, JetBrains Gateway, Zed Remote, etc. — without per-machine hand-rolling:

```bash theme={"theme":"github-dark"}
ssh myapp.boxd                                      # plain SSH (alias carries HostName + Port)
cursor --remote ssh-remote+myapp.boxd /home/boxd    # Cursor (Remote-SSH)
code   --remote ssh-remote+myapp.boxd /home/boxd    # VS Code
# JetBrains Gateway / Zed: pick `myapp.boxd` from their host picker
```

Each machine is reachable on a dedicated SSH **port** (10000–30000 range) on the shared proxy IP, so each stanza carries a `HostName`, a `Port`, and a `User` line. The `<vmname>.boxd` alias bakes the port in for you — connecting to the bare `<vmname>.boxd.sh` hostname (port 22) instead reaches the proxy, not the machine (the old `ssh boxd.sh` management shell there is retired — use this CLI). The managed block is bracketed with `# BEGIN boxd` / `# END boxd`; nothing outside the markers is ever modified.

## Copying files

Paths after `:` are relative to `/home/boxd` unless starting with `/`. Uploads stream automatically — no inherent file-size cap.

```bash theme={"theme":"github-dark"}
boxd machine cp ./local.txt myapp:/home/boxd/remote.txt    # upload
boxd machine cp myapp:/home/boxd/remote.txt ./local.txt    # download
boxd machine cp myapp:/path/file -                         # download to stdout
echo data | boxd machine cp - myapp:/path/file             # upload from stdin
boxd machine cp -r ./dir myapp:dir                         # recursive
```

## Proxy management

Every machine gets `https://name.boxd.sh` forwarding to its default port. Publish more ports — HTTPS subdomains or raw TCP/UDP forwards — all under `boxd machine proxy`.

```bash theme={"theme":"github-dark"}
boxd machine proxy list --vm myapp                       # list published ports (alias: ls)
boxd machine proxy add api --vm myapp --port 3001        # HTTPS subdomain: api.myapp.boxd.sh → 3001
boxd machine proxy set-port --vm myapp --port 3000       # change the default proxy's target port
boxd machine proxy set-port --vm myapp --port auto       # auto-detect the listening port
boxd machine proxy remove api --vm myapp                 # remove a subdomain proxy (alias: rm)
```

Subdomain proxies are HTTPS-only. To expose a database, an SSH daemon, or any non-HTTP service, use a raw port forward (below).

## Exposing raw TCP/UDP ports

Raw forwards live under the same `machine proxy` group — pass `--raw`. boxd opens a raw TCP or UDP port on the machine's public proxy and forwards it straight to a port inside the machine, for anything that isn't HTTP (databases, game servers, custom protocols). The public port is allocated for you; connect on the machine's existing `name.boxd.sh` endpoint at that port. See [Port forwarding](/how-it-works/port-forwarding) for the concept.

```bash theme={"theme":"github-dark"}
boxd machine proxy add --vm myapp --port 5432 --raw            # forward a public port -> :5432 (TCP)
boxd machine proxy add --vm myapp --port 9999 --raw --udp      # UDP instead
boxd machine proxy add --vm myapp --port 7777 --raw --tcp --udp # both protocols on one allocated public port
boxd machine proxy list --vm myapp                            # raw forwards show as kind tcp/udp
boxd machine proxy remove 5432 --vm myapp                     # remove a raw forward by its machine port
```

Up to **3** raw forwards per machine. Re-adding a port you already forwarded keeps the same public port and just updates the protocol set. Forwards are owner-only and removed automatically when the machine is destroyed.

## Snapshots

A snapshot captures a running machine's memory + disk at a moment in time, replicated across a few workers. Create a machine from it near-instantly with `machine new --from-snapshot`.

```bash theme={"theme":"github-dark"}
boxd snapshots save myapp my-workspace              # save memory + disk as a named snapshot (machine keeps running)
boxd snapshots list                                 # name, version, status, size, used (alias: ls)
boxd snapshots remove my-workspace -y               # delete a snapshot and its replicas (alias: rm)
boxd snap save myapp my-workspace                   # `snap` is the short alias
```

Re-saving an existing name captures a new version (latest wins). Snapshots are fenced to the org context that created them and never cross an org boundary.

## Checkpoints

A checkpoint captures a running machine's memory + disk held on the machine's own worker, restorable **in place** (the machine reboots into that exact state — same name, URL, and ports). Unlike snapshots they're per-machine, not replicated, and never become a reusable image — they live and die with the machine. Use them as quick "save points" before a risky change.

```bash theme={"theme":"github-dark"}
boxd machine checkpoint save myapp before-migration    # capture memory + disk (machine keeps running)
boxd machine checkpoint list myapp                     # name, status, size, created, available (alias: ls)
boxd machine checkpoint restore myapp before-migration # reboot the machine into the checkpoint (-y to skip confirm)
boxd machine checkpoint remove myapp before-migration  # delete a checkpoint (-y; alias: rm)
```

Up to 10 checkpoints per machine. `available: no` means the checkpoint's worker is no longer the machine's worker (e.g. after a migration) — it can't be restored until they line up again.

## Integrations

Connect third-party accounts — **GitHub**, **Linear**, and **Slack** — under `boxd manage integrations`, and every personal machine in that org can use them. See [Integrations](/integrations/overview) for the full model.

```bash theme={"theme":"github-dark"}
boxd manage integrations                       # list connected integrations + what you can connect (alias: integ; ls)
boxd manage integrations connect linear        # connect via browser OAuth — prints a URL, polls until you approve
boxd manage integrations disconnect linear     # revoke at the provider + remove from boxd
boxd manage integrations mcp linear            # install Linear's MCP into all agents (claude, codex, opencode)
boxd manage integrations mcp slack claude      # ...or only specific agents
boxd manage integrations mcp linear --disable  # remove the MCP from every agent
boxd manage integrations --shared connect slack # connect the ORG's shared credential (org admin)
```

`connect` prints an authorize URL for you to open in a browser — a human has to approve it. Integrations are fenced to one org: your own connection serves your private machines in the active org; `--shared` addresses the org's connection for its shared machines (admin only).

## Env vars & secrets

Set environment variables and secrets once; boxd injects them into every machine you own in that org. Plain vars are cleartext and readable back; `--secret` seals a value at rest (write-only). See [Env vars & secrets](/reference/env-secrets) for the full model (scopes, naming, injection).

```bash theme={"theme":"github-dark"}
boxd env set DATABASE_URL postgres://…   # cleartext env var
boxd env set OPENAI_API_KEY sk-… --secret # sealed, write-only — never readable back
boxd env list                            # NAME / VALUE / SCOPE — secrets show (sealed) (alias: ls)
boxd env remove DATABASE_URL             # delete (alias: rm; --scope disambiguates in an org context)
boxd env scope OPENAI_API_KEY shared --from private  # move a secret to another org scope (value preserved)
```

To put a literal **.env file** on **one** machine (rather than an account-wide var), use `boxd env push`:

```bash theme={"theme":"github-dark"}
boxd env push myapp --file .env --dest ~/myapp/.env   # upload + chmod 600
```

## Billing

Every machine is 2 vCPU / 8 GB RAM / 100 GB disk, billed by usage — there are no fixed plans or shapes to pick. Your balance, usage, and payment live under `boxd manage billing`.

```bash theme={"theme":"github-dark"}
boxd manage billing              # your (or the active org's) balance and usage
boxd manage billing --open       # open the billing portal in your browser
```

See [Pricing](/reference/pricing) for the rate card.

## Organizations

If you belong to an [organization](/organizations/overview), your active **context** decides which org a new machine is billed to and which machines `list` and `connect` see. Sharing a machine opens it to every member of the org.

```bash theme={"theme":"github-dark"}
boxd auth                        # identity + the orgs you belong to (active context marked)
boxd auth switch acme            # work in the "acme" org context (multi-word names need no quotes)
boxd auth switch personal        # back to your personal context
boxd machine share myapp         # share a machine with the whole org (every member can reach it)
boxd machine unshare myapp       # stop sharing — private to you again (org keeps paying)
```

* The active org context is a **per-device** selection. `boxd auth switch` writes it locally; it is **not** synced to the web console or your other devices — each surface keeps its own. `personal` = your default org.
* In an org context, `boxd machine new` creates a machine **billed to the org but private to you**; add `--shared` to make it visible to the whole org from birth.
* `boxd manage billing` follows the active context — in an org it shows the org's balance and usage.
* **Share / unshare is owner-only.** Any member can `connect` to a *shared* machine; a private machine (personal or org-billed) is reachable only by its owner.
* An org has three roles: **owner**, **admin**, and **member**. Owners and admins invite and remove members at [`/app/organizations`](https://boxd.sh/app/organizations) — see [Organizations](/organizations/overview#managing-members).

<Warning>
  Sharing a machine **wipes its in-VM agent logins** (Claude Code, Codex, OpenCode) the instant it goes shared, so a teammate with a shell can never read your personal tokens. Unsharing restores Claude automatically; Codex/OpenCode need a one-time re-login. Forking a shared machine gives you a **private** fork with your logins intact. See [Share a VM](/organizations/share-a-vm) for the full handoff.
</Warning>

## Per-machine settings

Auto-suspend and auto-hibernate timeouts live under `boxd machine config` (you can also set them at creation with `--auto-suspend-timeout` / `--auto-hibernate-timeout`).

```bash theme={"theme":"github-dark"}
boxd machine config list myapp                        # all settings for a machine
boxd machine config get myapp auto-suspend.timeout    # read one
boxd machine config set myapp auto-suspend.timeout 300    # suspend after 5 min idle (0 = off)
boxd machine config set myapp auto-hibernate.timeout 0    # never hibernate
```

## CLI settings

`boxd config` persists laptop-side CLI settings (kept in `config.toml`).

```bash theme={"theme":"github-dark"}
boxd config list                              # api-url, client-utils.enable
boxd config get api-url                       # read one
boxd config set api-url https://boxd.sh       # persist the API server (flag/env still override)
boxd config set client-utils.enable true      # enable the clipboard/files/browser bridge for this device
```

## Shell completions

Tab-completes commands, flags, and your machine/snapshot/org/proxy/key names (fetched live). Set up automatically by the installer and on `boxd auth login`; to (re)install manually:

```bash theme={"theme":"github-dark"}
boxd completions --install       # auto-detects your shell, idempotent
boxd completions zsh             # or print the raw script yourself
```

## Open the docs

```bash theme={"theme":"github-dark"}
boxd docs                        # opens https://docs.boxd.sh in your browser
```

## Global flags

| Flag                         | Description                                                                            |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| `--json`                     | Output as JSON (the only visible global besides `--help`)                              |
| `--api-url` / `BOXD_API_URL` | API server URL (hidden; default `https://boxd.sh`)                                     |
| `--token` / `BOXD_TOKEN`     | Auth token, overrides stored credentials (hidden)                                      |
| `--org`                      | Run one invocation in a specific org context (hidden; persist with `boxd auth switch`) |
