# Quivvo CLI

`quivvo` is the command-line client for Quivvo machines. It needs Node.js 22 or later and has no dependencies.

```bash
command -v quivvo >/dev/null || npm i -g quivvo@latest
```

`npx quivvo@latest` works without installing, but leaves nothing on your PATH, so every later command has to start with it too.

Credentials live in `~/.config/quivvo/credentials.json` (mode 0600). `QUIVVO_API_KEY` and `QUIVVO_API_URL` override them. The default API URL is `https://tryquivvo.com`. The CLI talks to that and, for `ssh`, `cp`, `push` and `port-forward`, directly to the machine at `*.fly.dev`, so a restricted network has to allow both.


## How do scripts and agents use it?

- **`--json`** on any command prints one JSON document on stdout, errors included, and nothing on stderr. Warnings go inside the document. Following a stream (`exec --stream`, `jobs logs -f`) prints one JSON document per line instead.
- **Errors under `--json`** have `code`, `message`, `retryable`, `retry_after_seconds`, `status`, `exit_code`, `hint`, `docs_url`, `request_id` and `owner_action`. [Errors](https://tryquivvo.com/docs/errors) lists the codes. If `hint` names a step you may take, take it first, such as suspending a machine to make room. `owner_action` is what only the workspace's owner can do, so pass that on when nothing else works.
- **Remote exit codes pass through.** `exec`, `run`, `ssh`, `jobs wait` and `jobs logs -f` exit with the remote command's code, which can be 2, 3 or 75 too. Under `--json`, look for the `error` key rather than going by the code alone. The one difference: `jobs wait` keeps 124 for "still running", so a job that hit its own timeout exits 1 there and 124 from `jobs logs -f`.
- **No terminal.** `sh` opens no shell and waits for nobody: with no key it exits 3 and names `quivvo login --agent --no-wait --json`, and with a key it gets the machine running and prints the next `exec`. `quivvo sh dev -- uname -a` runs that command and exits with its code. `ssh` does the same, on a machine that exists.
- **Tool timeouts.** `exec` prints output as it's produced, so what ran before a kill is already on stdout, but the kill stops the command. Under `--json` without `--stream`, or with `--tail-bytes`, nothing prints until the end, so get it back with `quivvo history` and then `quivvo output`. If it might outlast your tool's timeout (often 120 seconds), use `--background`. `run` waits for the whole command too: `run --no-wait` starts a job instead.
- **Safe retries.** A `create` that fails leaves nothing behind, with one exception: when `--wait-setup` fails, the machine is there, running and billing, so you can read the script's log. Delete it with `quivvo rm`. `--idempotency-key` gets the first answer back within 24 hours, and a different request with the same key is a `conflict`. `--if-not-exists` uses the machine if it's already there, and `created` under `--json` says which. It won't run a setup script a second time. Repeating `rm` or `snapshot rm` succeeds. `fork` lists partial failures in `failed` and exits with the first failure's code, so a 5 doesn't mean nothing was made: `machines` lists the copies it did make, which are running and billing, and `hint` says how to delete them.
- **Limits.** A command that waits for its result stops at 280 seconds, which is also its default. With `--background`, the default is 3600 and the most is 86400. See [recipes](https://tryquivvo.com/docs/recipes).
- **The queue.** `quivvo queue` prints your place, how many have joined from your link, and the link. While a workspace is waiting, `whoami` shows the same, and whatever creates a machine exits 5 with the code `queued`. Pass the link to the owner and stop: retrying won't help.
- **Keys.** `quivvo login` gets a key that drives machines. `keys` and `claim` also need `keys:write`, which you tick when you approve the sign-in. It's off by default because anyone can start a sign-in and send you the link. A key with only `machines:read` can `cp` a single file off a running machine and nothing else of what opens a connection: copying a directory or uploading, `push`, `ssh` and `port-forward` need `machines:write`.
- **Secrets.** A bare `--env KEY` forwards your local value, and it stays out of history. The command text doesn't.
- **stdin.** A command gets none unless you pass `--stdin`, even from a pipe, because a stdin that never closes would hang it. When a pipe or file was given without it, the CLI warns that it was ignored. `--stdin` takes up to 1 MB.
- **Binary data.** `exec` is for text. Its plain stdout and `--stdin` carry binary bytes unchanged, but `--json` and `--tail-bytes` can't, and say so in `warnings`. Use `cp` and `push` for files.
- **`push` and `cp` differ.** `push` sends what git tracks and new files, with no `.git` and none of what usually holds a credential, tracked or not: `.env` and its variants such as `.env.*` and `prod.env`, `.ssh`, `.aws`, `*.pem`, `.netrc`, `*.tfvars`, `.mcp.json`, cloud service-account files, and the logins in `.claude` and `.codex`. That's a list of names, so it can't catch everything. It says what it left out, and `--include` sends it. `cp` sends everything you point it at, and copying into an existing directory nests, like `cp`. Bringing a directory back from a machine leaves out `.git/hooks`, because an agent had root there and the next `git commit` on your computer would run them. `.git/config`, `.envrc` and `.vscode/tasks.json` can run commands too, so read what you bring back before you work in it.

<!-- BEGIN GENERATED from `quivvo help --json`. Run `pnpm check:docs --write` to update it. -->

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | ok |
| `1` | something else went wrong |
| `2` | the command line or a request was wrong |
| `3` | not signed in, or this key isn't allowed to |
| `4` | no such machine, job, snapshot or file |
| `5` | a plan limit, or out of credit: the error says who can fix it |
| `75` | nothing changed and it may work later: run it again |
| `76` | `login`: the code expired; run it again for a new one |
| `77` | `login`: the workspace's owner denied it |
| `124` | a wait or the command's timeout ran out |
| `125` | `exec` or `run` itself failed, so the command's result is unknown |
| otherwise | `exec`, `run`, `ssh`, `jobs wait` and `jobs logs -f` exit with the remote code |

## Environment

| Variable | Meaning |
| --- | --- |
| `QUIVVO_API_KEY` | Use this key instead of the saved credentials |
| `QUIVVO_API_URL` | API to talk to (default https://tryquivvo.com) |
| `QUIVVO_DEBUG` | Log every request to stderr |
| `NO_COLOR` | Plain output |

## Commands

`<m>` is a machine's name or id. Every command also takes `--json` and `--help`. Run `quivvo <command> --help` for what each flag does, or `quivvo help --json` for all of it as data.

```text
quivvo sh [name] [--template T] [--size S] [--region R] [-- cmd...]
    Sign in if needed, create or resume a machine (default "dev"), open a shell
quivvo init [--client claude|codex|cursor ...] [--yes] [--dry-run] [--project] [--inline-key] [--no-skill] [--no-mcp]
    Set up the skill and the MCP server for the AI tools on this computer (shows a checklist first)
quivvo login [--agent] [--name N] [--scope S ...] [--no-wait] [--no-browser]
    Sign in with a device code (opens the browser unless --agent)
quivvo logout [--revoke]
    Remove the saved credentials
quivvo whoami
    Show the workspace, plan limits, and what this key may do
quivvo queue
    Show your place in the queue and your invite link
quivvo create [--name N] [--template T] [--size S | --vcpus N --mem-mib M] [--disk G] [--region R] [--snapshot S] [--setup-file PATH] [--env K[=V] ...] [--auto-suspend MIN] [--delete-after MIN] [--if-not-exists] [--idempotency-key KEY] [--wait-setup] [--no-wait]
    Create a machine (waits until it's running)
quivvo ls [--all] [--state S]
    List machines
quivvo get <m>
    Show a machine (with its daemon's status when it's running)
quivvo exec <m> [--stream] [--background] [--cwd D] [--env K[=V] ...] [--timeout SEC] [--tail-bytes N] [--stdin] [--] <cmd...>
    Run a command and print its output; exits with the command's exit code
quivvo run [--push DIR] [--timeout SEC] [--keep] [--no-wait] [--cwd D] [--env K[=V] ...] [--template T] [--size S] [--region R] [--name N] [--] <cmd...>
    also: --include PATTERN, --exclude PATTERN
    One command on a throwaway machine: create, optionally push, run, delete
quivvo ssh <m> [--print] [--cwd D] [-- cmd...]
    Interactive shell on the machine; --print shows an OpenSSH command
quivvo ssh-proxy <m> [--port N]
    Pipe stdin/stdout to the machine's port 22 (an OpenSSH ProxyCommand)
quivvo cp <src> <dst> [--exclude PATTERN ...]
    Copy files or directories to or from a machine (`m:/path` marks the machine side)
quivvo push <local-dir> <m>[:path] [--exclude PATTERN ...] [--include PATTERN ...]
    Upload a directory, honouring .gitignore (default: ~/<its name> on the machine)
quivvo jobs ls <m>
    List a machine's jobs, newest first
quivvo jobs logs <m> <job> [-f] [--tail N] [--offset BYTES]
    Print a job's output (stdout and stderr interleaved)
quivvo jobs wait <m> <job> [--timeout SEC]
    Wait for a job to end; exits with its exit code
quivvo jobs kill <m> <job> [--signal TERM|KILL|INT]
    Signal a job's process group (default TERM)
quivvo ps <m>
    List processes on a machine
quivvo history <m> [--limit N]
    Commands run with exec, newest first (works while stopped)
quivvo output <m> <exec-id>
    Print a past exec's output (the first 16 KiB and last 48 KiB of each stream)
quivvo logs <m> [--lines N]
    Boot and daemon log; of a machine that isn't running, its state, error and the provider's events
quivvo status <m>
    The machine daemon's status: uptime, idle time, sessions, memory, disk
quivvo suspend <m> [--no-wait]
    Suspend a machine (keeps memory and processes when it can; otherwise it stops)
quivvo resume <m> [--no-wait]
    Resume a suspended machine
quivvo stop <m> [--no-wait]
    Stop a machine (the disk is kept)
quivvo start <m> [--no-wait]
    Start a stopped machine
quivvo restart <m> [--no-wait]
    Power-cycle a machine
quivvo rm <m> [--yes]
    Delete a machine and its disk (asks first)
quivvo snapshot save <m> --name N [--no-wait]
    Save a machine's disk as a snapshot
quivvo snapshot ls
    List snapshots
quivvo snapshot rm <name> [--yes]
    Delete a snapshot (by name or id)
quivvo restore <m> --snapshot N [--yes] [--no-wait]
    Replace a machine's disk with a snapshot's (same id, name and URL). What's on /data now is lost, and anything installed outside /data may be too
quivvo fork <m> [--count N] [--name N] [--no-wait]
    Copy a machine's disk into new ones
quivvo port publish <m> <port> [--private]
    Serve a port on the machine's URL
quivvo port unpublish <m> <port>
    Stop serving a port
quivvo port ls <m>
    List published ports
quivvo port-forward <m> <remote-port>[:<local-port>]
    Forward a local port to a port inside the machine (private, no publishing)
quivvo desktop <m> [--stop] [--no-browser]
    Start a machine's desktop and open it in the dashboard (--stop stops it)
quivvo auto-suspend <m> <minutes>
    Suspend after this many idle minutes (0 turns it off)
quivvo resize <m> [--vcpus N --mem-mib M] [--disk G]
    Change CPU and memory (while stopped) or grow the disk (any time)
quivvo usage
    This period's cost, credit balance and per-machine usage
quivvo catalog
    Templates, sizes and regions you can create machines with
quivvo keys ls
    List API keys
quivvo keys create --name N [--scope S ...]
    Create an API key (the secret is printed once)
quivvo keys rm <id>
    Revoke an API key
quivvo feedback <message...> [--machine M] [--context TEXT]
    Tell us something went wrong or could be better
quivvo claim <CODE> [--yes] [--deny] [--scope S ...]
    Approve (or --deny) a sign-in code shown by `quivvo login --agent`
quivvo mcp init claude|cursor|codex [--project] [--inline-key]
    Write the hosted MCP server into an AI tool's config, referring to $QUIVVO_API_KEY
quivvo mcp start
    A stdio MCP server that forwards to the hosted one
quivvo agent prompt <m> [--provider claude|codex] [--cwd D] [--env K[=V] ...] [--model M] [--continue RUN] [--name KEY] [--timeout SEC] [--budget USD] [--] <text>
    Start a run; prints its id
quivvo agent wait <m> <run> [--timeout SEC]
    Wait for a run: 0 done, 1 the agent failed, 124 still running
quivvo agent result <m> <run> [--diff]
    Print what a run came back with; exits like `agent wait`
quivvo version
    Print the CLI version
```

<!-- END GENERATED -->
