# CLI

Source: https://phonebox.dev/docs/interfaces/cli

> Install the phonebox CLI, set its environment, and use every command, its output and its exit codes.



The `phonebox` CLI drives phones from a terminal. It suits coding agents, which already work in one, and quick checks by hand. Every command calls the [REST API](/docs/interfaces/rest), so keys, limits and billing work as they do there.

## Install [#install]

The CLI needs Node.js 20 or later. One command installs it and sets you up:

```bash
curl -fsSL https://phonebox.dev/install | sh
```

The script installs the package with npm and runs [`phonebox setup`](#setup). To install without setting up:

```bash
npm install -g https://phonebox.dev/downloads/phonebox-0.1.0.tgz
phonebox --help
```

Run the CLI as the installed `phonebox` command. The package ships only from phonebox.dev, so an `npx` call that doesn't name the file would fetch whatever the npm registry holds under that name, and run it with your key in its environment. For a one-off run without installing anything, name the file:

```bash
npx -y --package=https://phonebox.dev/downloads/phonebox-0.1.0.tgz phonebox --help
```

Inside a project, `npm install https://phonebox.dev/downloads/phonebox-0.1.0.tgz` adds the package, which is also the [TypeScript SDK](/docs/interfaces/sdk). The package's SHA-256 checksum is published next to it, at `https://phonebox.dev/downloads/phonebox-0.1.0.tgz.sha256`.

## Environment [#environment]

| Variable           | Meaning                                                                                                           |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `PHONEBOX_API_KEY` | Your API key. It wins over the key that `phonebox setup` saved; without either, commands fail with a usage error. |
| `PHONEBOX_PHONE`   | A phone ID. Commands that act on a phone use it when you leave the ID out.                                        |
| `PHONEBOX_URL`     | Another API origin. Leave it unset to use `https://phonebox.dev`.                                                 |

```bash
export PHONEBOX_PHONE=ph_7kx2m6q4v3ta
phonebox look
```

## Commands [#commands]

`<id>` is a phone ID, which you can leave out when `PHONEBOX_PHONE` is set. A first argument that starts with `ph_` but isn't a valid phone ID, or an argument that the command doesn't take, is a usage error, so a mistyped ID never falls back to the phone in `PHONEBOX_PHONE`. Durations such as `--idle`, `--max`, `--expires` and `--timeout` take seconds or forms like `90s`, `10m` and `1h30m`. Every command prints its own help with `--help`, as in `phonebox tap --help`.

### Setup [#setup]

```text
phonebox setup [--no-wait] [--no-open] [--email] [--amount 10] [--client NAME]
phonebox login [--no-wait] [--no-open] [--email] [--client NAME]
phonebox logout
phonebox token
```

| Command  | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setup`  | Signs you in, adds credit and confirms the account, skipping what is already done. It prints a link and a code: open the link, sign in with Google, or with an emailed code when you pass `--email`, check that the page shows the same code, and click Connect. `--client` names this terminal on that page and on its key. It then prints a checkout link when the project has no credit, for $10 or `--amount`. On a terminal it opens each link in your browser unless you pass `--no-open`, and waits up to 15 minutes for you. `--no-wait` prints the current step and exits at once, which is how agents use it. |
| `login`  | The sign-in part of `setup` alone. Its last line has the step `signed_in`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `logout` | Forgets the saved key. The key stays valid until you revoke it under [API keys](/app/keys).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `token`  | Prints the key in use, as in `export PHONEBOX_API_KEY=$(phonebox token)`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |

Setup saves an Agent key in `~/.config/phonebox/credentials.json`, or under `$XDG_CONFIG_HOME`, readable only by you. Its progress goes to stderr, and its last line on stdout is one JSON object whose `step` is `sign_in`, `add_credits` or `ready`:

```text
{"step":"sign_in","url":"https://phonebox.dev/connect?code=K7QM-2XHD","user_code":"K7QM-2XHD","expires_in":900,"next":"Ask your user to open the URL, sign up or in, and confirm the code. Then run phonebox setup again."}
```

### Phones [#phones]

```text
phonebox ls [--status STATUS]
phonebox create [--name NAME] [--country CC] [--idle 10m] [--max 1h] [--idempotency-key KEY] [--no-wait]
phonebox start <id> [--idle 10m] [--max 1h] [--no-wait]
phonebox park <id> [--no-wait]
phonebox rm <id> --yes
phonebox status <id> [--wait [--timeout 10m]]
phonebox live <id> [--expires 1h]
phonebox account
```

`--country` takes one of the two-letter codes that `phonebox account` lists under `countries`, such as `US` or `DE`. Leave it out for any country.

| Command   | What it does                                                                                                                                                                                                                                                                                                                                                              |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ls`      | Lists the project's 50 newest phones, leaving out failed and deleted ones, or only those in one status with `--status`, such as `--status failed`. To see more, page through [List phones](/docs/api-reference/phones#list-phones) with its `cursor`.                                                                                                                     |
| `create`  | Creates a phone and waits until it is ready, for up to 10 minutes. The moment the phone exists, before the wait, it prints the phone's ID on stderr. `--no-wait` prints the new phone at once instead of waiting. `--idempotency-key` makes a repeat of the same create return the same phone, and a repeat that finds that phone failed or deleted exits with its error. |
| `start`   | Starts a parked phone, with its apps, files and sign-ins kept, and waits until it is ready, for up to 5 minutes. On a running phone, it renews the session.                                                                                                                                                                                                               |
| `park`    | Parks a phone and waits until it has stopped. Parked phones cost nothing.                                                                                                                                                                                                                                                                                                 |
| `rm`      | Deletes a phone permanently, with its apps, files and sign-ins. It needs `--yes` and an Admin key.                                                                                                                                                                                                                                                                        |
| `status`  | Shows a phone's status and session. `--wait` waits until the phone is ready, for up to 10 minutes or `--timeout`, only reading it, and exits with code 3 when time runs out. `--timeout` without `--wait` is a usage error. Wait this way for a phone that is still creating or starting, because `start` renews a running phone's session.                               |
| `live`    | Creates a [live view link](/docs/using-phones/live-view) that lets a person watch and control the phone in a browser. Anyone with the link controls the phone until it expires, so share it only with your user, and never paste it into logs or shared chats.                                                                                                            |
| `account` | Shows your balance, limits and price.                                                                                                                                                                                                                                                                                                                                     |

### The screen [#the-screen]

```text
phonebox look <id> [--screenshot FILE.jpg] [--json]
phonebox screenshot <id> <FILE> [--png]
```

| Command      | What it does                                                                                                                                                                                                                                                                                                              |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `look`       | Prints the screen in the [text form](/docs/using-phones/observe#the-text-form), after a first line that names its snapshot, such as `snapshot snp_4f2k7m3q6z3a`, which a tap by ref needs. `--json` prints the full observation instead, with its `snapshot_id`. `--screenshot` also saves a full-size JPEG to that file. |
| `screenshot` | Saves a screenshot to the file, as JPEG, or as PNG with `--png`.                                                                                                                                                                                                                                                          |

### Actions [#actions]

```text
phonebox tap <id> (X Y | --text T [--nth N] | --id R | --desc D | --ref N --snapshot S) [--long] [--double]
phonebox type <id> <TEXT> [--clear] [--submit]
phonebox key <id> <NAME|CODE>
phonebox back <id>
phonebox home <id>
phonebox recents <id>
phonebox swipe <id> X1 Y1 X2 Y2 [--ms 300]
phonebox scroll <id> <up|down|left|right> [--text T]
phonebox open <id> <PACKAGE|URL>
phonebox wait <id> --text T [--gone] [--timeout 20s]
phonebox act <id> <JSON|->
```

| Command                   | What it does                                                                                                                                                                                                                                                          |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tap`                     | Taps a point or an element. `--long` long-presses and `--double` double-taps. `--nth` goes only with `--text`, and the CLI refuses it with any other target as a usage error. [Targets](/docs/using-phones/actions#targets) explains how each form finds its element. |
| `type`                    | Types into the focused field. `--clear` empties it first, and `--submit` presses Enter afterwards.                                                                                                                                                                    |
| `key`                     | Presses a key by name, such as `enter`, `delete`, `tab` or `escape`, or by its Android key code.                                                                                                                                                                      |
| `back`, `home`, `recents` | Press Back, go to the home screen, or open the recent apps.                                                                                                                                                                                                           |
| `swipe`                   | Swipes between two points, in `--ms` milliseconds.                                                                                                                                                                                                                    |
| `scroll`                  | Scrolls the screen, or the element with the text `--text`.                                                                                                                                                                                                            |
| `open`                    | Opens an app by its package name, or a URL or deep link when the argument has a scheme such as `https:`.                                                                                                                                                              |
| `wait`                    | Waits until the text appears, or with `--gone` until it disappears. `--timeout` can be up to 30 seconds, and the default is 10. It exits with code 3 when time runs out.                                                                                              |
| `act`                     | Runs a batch of [actions](/docs/using-phones/actions), given as JSON or read from stdin with `-`. The JSON is either `{"actions": […], "observe": …}` or just the array of actions.                                                                                   |

Each of these commands but `act` sends one action with `"observe": "none"` and prints its result. `act` prints the whole batch result and exits with code 0 even when an action failed, so check `completed` and each result's `ok`.

```bash
echo '[{"type": "tap", "target": {"text": "Sign in"}}, {"type": "wait_for", "target": {"text": "Welcome"}}]' | phonebox act -
```

### Apps and files [#apps-and-files]

```text
phonebox apps <id> [install PACKAGE | install ./app.apk [--replace] | rm PACKAGE | installs]
phonebox uploads [ls | rm UPLOAD]
phonebox files <id> ls PATH | push LOCAL REMOTE | pull REMOTE LOCAL | rm PATH
```

| Command   | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `apps`    | Lists the installed apps. `apps install ./app.apk` installs your own APK (an argument ending in `.apk` is always a file, and one that isn't there is a usage error): it uploads the file, waits while Phonebox reads it, installs it and waits until the phone lists the app, printing each step on stderr. `--replace` uninstalls the app first, which deletes its data: use it for a build signed with another key, or an older `versionCode`. `apps install PACKAGE` starts an install from Phonebox's app library, which never reaches Google Play, `apps rm PACKAGE` uninstalls an app, and `apps installs` lists the recent installs, each `running`, `succeeded` or `failed`. |
| `uploads` | Lists the project's [uploads](/docs/api-reference/uploads) of your own APKs, or deletes one with `rm`. An upload is deleted 7 days after its last install.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `files`   | Lists a directory, uploads a local file (`push`), downloads a file (`pull`) or deletes one, under `/sdcard`. When the `push` target ends with `/`, the file keeps its name.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

### The device and recordings [#the-device-and-recordings]

```text
phonebox device <id>
phonebox location <id> [LAT LNG [--keep-timezone] | reset]
phonebox reboot <id> [--no-wait]
phonebox reset <id> --yes [--no-wait]
phonebox record <id> start [--name NAME] | stop REC | ls | pull REC FILE.mp4 | rm REC
phonebox library [QUERY] [--cursor N]
```

| Command    | What it does                                                                                                                                                                                                                                                              |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device`   | Shows the phone's brand, model, Android version, screen size and carrier, with its location, locale and timezone.                                                                                                                                                         |
| `location` | Shows the phone's location. With `LAT LNG`, sets it, and the timezone found there unless `--keep-timezone` is given. `location <id> reset` goes back to the default location and its timezone.                                                                            |
| `reboot`   | Restarts a stuck phone, keeping everything on it, and waits until it is ready, for up to 5 minutes. A phone can reboot once every 10 minutes.                                                                                                                             |
| `reset`    | Wipes the phone's apps, files, signed-in accounts and clipboard, keeping its ID, and waits until it is ready. It can't be undone: it needs `--yes` and an Admin key.                                                                                                      |
| `record`   | Records the phone's screen: `start` begins a recording and prints it with its `rec_` ID, `stop` ends one, `ls` lists the phone's recordings, `pull` saves a ready recording's video to an MP4 file, and `rm` deletes one. Recordings are deleted 7 days after they start. |
| `library`  | Searches Phonebox's app library, the store and system apps that `apps <id> install PACKAGE` installs. `--cursor` reads the next page, from `next_cursor`.                                                                                                                 |

## Output [#output]

* A command that succeeds prints one line of JSON on stdout: the phone, the action's result, the list, and so on.
* Each line on stderr is one JSON value. A `create` that is about to wait prints `{"created":"ph_…","status":"creating"}` there first, so stderr can hold that line and then an error. `apps install ./app.apk` prints `{"uploading":…}`, `{"processing":"upl_…"}`, `{"ready":"upl_…"}` and `{"installing":…}` there as it goes.
* `look` prints its snapshot line and the text form instead, unless you add `--json`.
* `setup` and `login` print their progress as plain sentences on stderr, and `token` prints the key alone.
* A command that fails prints `{"error": {…}}` on stderr, with the fields `status`, `code`, `message`, `retryable`, `next`, `request_id` and `details`. `status` is the HTTP status, and a usage error has the code `usage`.
* When the answer isn't Phonebox's own error, such as a gateway's `504`, or the API can't be reached or doesn't answer in time, the code is `internal_error`. After a command that changes the phone, such as `tap` or `act`, look before you run it again, because it may have run.

```text
{"error":{"status":422,"code":"ambiguous_target","message":"Several elements on the screen match the target.","retryable":false,"next":"Add nth to the target, or tap by ref from the returned observation.","request_id":null,"details":{"candidates":[{"ref":3,"type":"Button","text":"Add to cart","desc":null,"id":"com.example.shop:id/add_to_cart","center":[860,620]},{"ref":5,"type":"Button","text":"Add to cart","desc":null,"id":"com.example.shop:id/add_to_cart","center":[860,900]},{"ref":7,"type":"Button","text":"Add to cart","desc":null,"id":"com.example.shop:id/add_to_cart","center":[860,1180]}],"snapshot_id":"snp_4f2k7m3q6z3a"}}}
```

The CLI waits for phones the way the API asks: `create`, `start`, `park` and `status --wait` poll in requests of about 50 seconds each until the phone gets there. A wait ends early with the phone's own failure when the phone can't get there, for example when a start fails and the phone parks again.

## Creating phones safely [#creating-phones-safely]

A new phone starts billing as soon as it exists, which is before it is ready. When `create` is going to wait, it first prints the new phone's ID on stderr:

```text
{"created":"ph_7kx2m6q4v3ta","status":"creating"}
```

The line appears only for a phone on its way to ready, whose status is `creating` or `starting`. If the wait is cut short, for example because your shell stops the command, the phone still exists: keep that ID, and `phonebox status` with it and `--wait` waits until the phone is ready.

An error from a create tells you whether a phone was made:

* Phonebox's own error that names no phone in `details.phone`, such as `insufficient_credits`, `running_limit_reached` or a `capacity_unavailable` that names no phone, is a refusal when no `created` line came before it: it created nothing, and you can send the create again.
* `internal_error`, which the CLI also prints when no answer came from Phonebox, such as after a timeout, a dropped connection or a gateway's `504`, says nothing about the create, and neither does a stopped shell. The phone may exist, so run `phonebox ls` and use the newest phone with the name you gave it before you create another.
* A phone is gone only when its `status` is `failed` or `deleted`, so read the error's `retryable` and the phone's status, never `details.phone` alone. A create whose new phone failed answers `retryable: false`, and a repeat that finds the phone failed or deleted exits with that phone's error. Create the next phone with a new idempotency key only then.

`--idempotency-key` makes a create safe to repeat. Choose one key for each phone you want, for example the task's name and the time, and keep it for as long as you may need to repeat that create:

```bash
key="signup-test-$(date +%s)"
phonebox create --name signup-test --idempotency-key "$key" --no-wait
```

The key must be 8 to 128 characters, letters, digits, `_`, `.`, `:` and `-`, starting with a letter or digit. The CLI refuses any other key, and an empty one, such as an unset variable, with a usage error, before it sends anything. If the command fails without a phone, run it again with the same key and the same flags within 24 hours: you get the phone the first call created, as it is now, instead of a second phone. If that phone has failed, or you deleted it, a repeat only returns it again, and `create` exits with its error: create the next phone with a new key.

## Exit codes [#exit-codes]

| Code | Meaning                                                                                                                                                                                                                                            |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0    | The command succeeded.                                                                                                                                                                                                                             |
| 1    | The API returned an error, or an action failed.                                                                                                                                                                                                    |
| 2    | The command was used wrongly, for example with a malformed phone ID, an argument the command doesn't take or a malformed duration such as `--max 2hours`, or there is no key: run `phonebox setup`. Nothing was done on the phone.                 |
| 3    | A wait timed out: `wait` ran out of time, `setup` waited 15 minutes for you, or a phone didn't reach its status in time. After a `create` or `start` that timed out, the phone exists, so read it with `phonebox status` and never create another. |
| 130  | You interrupted the command with Ctrl-C.                                                                                                                                                                                                           |
