# Recipes

Each of these is meant to be copied. They work from a script or an agent's tool call, with no terminal.

## Run one command on a throwaway machine

```bash
quivvo run --push . -- "npm ci && npm test"
```

`run` creates a machine, sends the current directory, runs the command there, deletes the machine and exits with the command's code. It waits for the whole command, so it's for work shorter than your own tool's timeout (often 120 seconds). For anything longer, have it start a job and answer right away:

```bash
quivvo run --push . --no-wait --timeout 1800 --json -- "npm ci && npm test"
```

That prints `machine`, `job` and the commands that come next: `quivvo jobs wait`, `quivvo jobs logs` and `quivvo rm`. The server has also been told to delete the machine, at the timeout plus 30 minutes. So if `run` is killed with no chance to clean up (SIGKILL), the machine bills until then at most, and on the Free plan it holds the one running slot. Any other signal, as most tool timeouts send, deletes it on the way out. To clean up, find it and delete it:

```bash
quivvo ls
quivvo jobs ls run-58658f99
quivvo jobs wait run-58658f99 j_8x0qvxi3k9 --timeout 100
quivvo rm run-58658f99 --yes
```

- Up to 280 seconds, it behaves like `exec`, with stdout and stderr apart. A longer `--timeout` makes it a job, up to 86400 seconds, and their output arrives interleaved.
- `--push` sends what `quivvo push` would: what git tracks, no `.git`, no `.env`.
- `--keep` leaves the machine there.

## Run something long in the background

A command that waits for its result stops at 280 seconds, and when a tool timeout (often 120 seconds) kills `quivvo exec`, the command is stopped too. Start anything that might outlast either as a job. It takes up to 86400 seconds, 3600 by default, and goes on if you disconnect.

```bash
quivvo create --name work --if-not-exists
JOB=$(quivvo exec work --background --timeout 1800 -- "npm test")
quivvo jobs wait work "$JOB" --timeout 100
```

`jobs wait` exits with the job's code. It exits 124 only when the wait ran out and the job is still running, so run it again. A job that hit its own timeout exits 1 here, and `exit_code` under `--json` holds the real 124. That means a retry loop can't spin forever.

```bash
quivvo jobs logs work "$JOB" --tail 50       # the last 50 lines
quivvo jobs logs work "$JOB" -f              # follow until it ends, then exit with its code
quivvo jobs kill work "$JOB"                 # TERM by default
quivvo jobs ls work
```

A job ends as one of:

| State | Meaning |
| --- | --- |
| `exited` | It finished. `exit_code` is its code |
| `killed` | It was signalled. `exit_code` 124 means it hit its timeout |
| `lost` | Its process went away with its exit status unknown, usually because the machine restarted or stopped. Its log is still there |

A running job keeps the machine awake and billing, so kill the ones you don't need. To keep a server up for as long as a job can run, a day, pass that as its timeout, and start it again when it ends:

```bash
quivvo exec work --background --timeout 86400 -- "npm run dev"
```

## Get a private repo onto a machine

Send your working copy. It's what git tracks, without `.git` or `.env`:

```bash
quivvo push ./api work
```

Add `--include .git` if you need to run git on the machine. To clone instead, forward a token. `--env` with no value sends your local one, and it stays out of history, where the command text would not:

```bash
quivvo exec work --env GITHUB_TOKEN -- "gh repo clone acme/api"
```

`gh` is on the `universal` template.

## Hand a task to Claude Code or Codex

The `universal` template has both installed, not signed in. `quivvo agent prompt` starts one as a run that outlives your command. It checks first that the agent has credentials, and exits 3 with the fix if not. A bare `--env KEY` whose variable isn't set on your side exits 2 before that, saying so.

### Claude Code

Pass one of these:

- `ANTHROPIC_API_KEY`, an API key.
- `CLAUDE_CODE_OAUTH_TOKEN`, a long-lived token from your Claude login. Make one with `claude setup-token` wherever you have a browser, and copy it from the output: it isn't saved anywhere.

```bash
RUN=$(quivvo agent prompt work --env CLAUDE_CODE_OAUTH_TOKEN --cwd api --name fix-test -- "fix the failing test")
quivvo agent wait work "$RUN" --timeout 100
quivvo agent result work "$RUN" --diff
```

Or sign in once on the machine with a person at the keyboard: `quivvo ssh work -- claude`. The sign-in stays on its disk.

### Codex

Pass `CODEX_API_KEY` (`OPENAI_API_KEY` works too), or sign in once on the machine with `quivvo ssh work -- codex login --device-auth`.

```bash
RUN=$(quivvo agent prompt work --provider codex --env CODEX_API_KEY --cwd api --name fix-test -- "fix the failing test")
quivvo agent wait work "$RUN" --timeout 100
quivvo agent result work "$RUN"
```

### What to know

- `agent wait` exits 0 when it's done, 1 if the agent failed, and 124 if it's still running. Run it again on 124.
- `--name` makes a retry safe: repeating it returns the same run instead of starting a second one.
- `--continue RUN` carries on from an earlier run's session. `--budget USD` stops Claude Code at a spending limit. `--timeout SEC` is an hour by default.
- `agent result --json` gives `state`, `exit_code`, `session_id`, `text`, `cost_usd`, `changed_files` and `diff_stat`.
- The prompt goes in a file on the machine, in `~/.quivvo/runs/<run>`, and stays out of command history.
- A running agent keeps the machine awake, and billing.

Everything the agent runs can read its key, as root. It runs with permission prompts off, and `claude -p` runs a repo's hooks and `.mcp.json` with no trust prompt, so only use code you trust with those keys.

## Let an agent use a browser

The `universal` template has Chromium and [Playwright MCP](https://github.com/microsoft/playwright-mcp), already set up to use it. Add the MCP server to an agent once. It's saved in the agent's settings, on the machine's disk:

```bash
quivvo exec work -- claude mcp add -s user playwright -- playwright-mcp
quivvo exec work -- codex mcp add playwright -- playwright-mcp
```

Then give it a task that needs a browser:

```bash
RUN=$(quivvo agent prompt work --env CLAUDE_CODE_OAUTH_TOKEN --cwd app -- "start the dev server, open it in the browser and check that signing up works")
```

- The browser is headless unless the machine's desktop is running, and then you can watch it there: see [watching on a desktop](#watch-it-on-a-desktop). Either way, the agent can save screenshots to a path for you to copy back: `quivvo cp work:/data/home/app/signup.png .`
- The browser keeps its profile, with cookies and sign-ins, under `~/.cache` on the persistent disk.
- `chromium` is on the PATH for other tools, such as Puppeteer, or `chromium --headless --screenshot=page.png https://example.com`. It runs without Chromium's sandbox, which won't start as root.
- A Playwright script of another version needs that version's Chromium: run `npx playwright install chromium`, or `playwright install chromium` for Python. It's installed outside `/data`, so a snapshot or fork doesn't copy it.
- Chromium needs memory. If a page crashes or a command exits 137, use a bigger size.

## Watch it on a desktop

The `universal` template has a desktop that runs only when you start it: a 1280x800 screen with a window manager, a terminal and Chromium on its right-click menu.

```bash
quivvo desktop work
```

That starts it and opens the machine's Desktop tab in the dashboard, where you can watch and take over with the mouse and keyboard. Opening the tab starts it too. With a VNC client instead, forward its port and connect to `localhost:5901`:

```bash
quivvo port-forward work 5901
```

- While it runs, a browser the agent opens through Playwright MCP appears on it, and so does any window a program on the machine opens. An agent started before the desktop keeps a headless browser until its next run.
- `xdotool` is there for an agent that drives the desktop itself, with `DISPLAY=:1`.
- An open view keeps the machine awake, and billing, like an open shell. The desktop itself doesn't: with nobody watching and nothing running, the machine suspends as usual.
- `quivvo desktop work --stop` stops it and closes its windows. A stop of the machine stops it too. It starts again the next time you open it.
- Only the machine's own tunnel reaches it, with the machine's token, so VNC asks for no password.

## Set a machine up once

```bash
quivvo create --name base --setup-file ./setup.sh --wait-setup
quivvo snapshot save base --name base-image
quivvo stop base
quivvo create --name work --snapshot base-image
```

A snapshot copies `/data` and nothing else. What your script leaves there, such as a cloned repo, `node_modules` or downloads, is on the new machine. What it installs anywhere else, such as a package from `apt-get`, stays on `base`, and nothing tells you it's missing until you need it. So keep the slow work under `/data`, and give the new machine its own script for the rest. It runs once the machine is up:

```bash
quivvo create --name work --snapshot base-image --setup-file ./install-packages.sh --wait-setup
```

The script is off the disk before its first line runs, so a snapshot doesn't copy it. Its output stays in the job's log, so don't print secrets. `--wait-setup` waits for the script and fails if it fails. `setup` on the machine shows `state`, `exit_code` and `job_id`, and the job's logs are the script's output. A failed script leaves the machine there and running, and billing, so you can see what went wrong. Delete it with `quivvo rm` before you try again: a setup script runs once, so `--if-not-exists` finds the same machine and doesn't run it again. The `stop` is for the Free plan, which runs one machine at a time.

Copies are made by the provider. If it won't make one, the answer is an error, nothing is created and the source is untouched: see [`provider_error`](https://tryquivvo.com/docs/errors#provider_error).

## Tell a coding agent about Quivvo

`quivvo init` installs the skill for the tools it finds. For a repo whose agents should all know, add this to its `AGENTS.md` or `CLAUDE.md`:

```markdown
## Cloud machines

Run anything long, untrusted or heavy on a Quivvo machine, not on this computer.

- Start: `quivvo create --name work --if-not-exists`. Run: `quivvo exec work -- "..."`. Finish: `quivvo rm work --yes`.
- Anything that might outlast a tool call: `quivvo exec work --background --timeout 1800 -- "..."`, then `quivvo jobs wait`.
- More: https://tryquivvo.com/skill.md
```
