---
name: firsty-cli-setup
description: Set up the Firsty CLI (install, claim a sandbox credential, order a first eSIM)
user-invocable: true
allowed-tools: Bash(command:*), Bash(npm:*), Bash(npx:*), Bash(node:*), Bash(firsty:*)
metadata:
  author: firsty
---

# Firsty CLI Setup

Firsty is worldwide mobile data: order eSIMs and data packages over one API.
If you are running this guide, your goal is to set up the `firsty` CLI so the
user can provision eSIMs without leaving their editor.

Everything below runs in **sandbox mode**, the only mode this CLI has: it
claims a free sandbox credential automatically, talks to Firsty's staging
environment, and returns mock data. No account, no card, no login. Going live
is a human step at the end, and it moves off the CLI onto direct HTTP calls.

Requirements: Node.js >= 20 and network access.

## Step 1: Install or upgrade the CLI

Check whether the CLI exists:

```bash
command -v firsty
```

If missing, either install globally or use npx for every command:

```bash
npm install -g firsty        # then: firsty <command>
# or, without installing:
npx firsty <command>
```

If present, require version >= 0.1.1:

```bash
firsty --version
npm update -g firsty         # upgrade path for global installs
```

## Step 2: Set up credentials

```bash
firsty init --json
```

This is idempotent and safe to re-run: if working credentials already exist it
is a no-op. Otherwise it claims a fresh sandbox credential from the Firsty
developer portal, stores it in `$XDG_CONFIG_HOME/firsty/config.json`
(default `~/.config/firsty/config.json`, mode 0600), and verifies that new
credential with a live catalog call (`.data.verified`).

Success predicate: `.ok == true and .data.authenticated == true`.

- `.data.alreadyConfigured == true` means an earlier setup is still valid.
- Exit code 4 with `.error.code == "pool_exhausted"` means no sandbox
  credentials are currently available. This is a dead end for you: tell the
  user to contact sales at https://developers.firsty.app/contact-sales and stop.
- Exit code 4 with `.error.code == "rate_limited"` means this machine
  claimed too many credentials today; re-use the existing config or wait.

No step in this flow blocks on a human. If the user already has their own
credentials, pass them explicitly instead of claiming:

```bash
firsty init --client-id <6-digit-id> --client-secret <secret> --json
```

## Step 3: Verify

```bash
firsty catalog regions --json --limit 3
```

Predicate: `.ok == true and (.data | length) > 0`.

## Step 4: First run

Order an eSIM, put data on it, and read the usage:

```bash
firsty esim order --json                       # note .data.activationCode (LPA string)
firsty catalog packages --json                 # pick a .data[].planReference
firsty package order <planReference> --json
firsty esim usage --json
```

The CLI remembers the last ordered eSIM, so `--profile`/eSIM arguments are
optional after `esim order`. Suggest these to the user as next steps:

- `firsty catalog packages`: browse data plans
- `firsty esim order`: provision an eSIM (prints the activation code)
- `firsty esim usage`: check consumption
- `firsty auth token`: raw bearer token for direct curl calls

## Reference

### Output contract

Every command in this guide supports `--json` and prints exactly one JSON
payload on stdout. Success: `{"ok": true, "mode": "sandbox", "data": ...}`.
Failure: `{"ok": false, "mode": "sandbox", "error": {"code", "title",
"status?", "requestId?", "hint?", "fieldErrors?"}}`, where `fieldErrors` is
an array of `{field, detail}` on validation failures. `mode` is always
`"sandbox"`: this CLI has no live mode. Never treat sandbox output as
production data. In human
mode, a SANDBOX banner is printed to stderr; stdout stays clean for piping.

### Exit codes

| Code | Meaning |
| ---- | ------- |
| 0 | success |
| 1 | unexpected internal error |
| 2 | bad input (validation, HTTP 400/422) |
| 3 | auth (missing/rejected credentials) |
| 4 | API error (404, 409, 429 after retries, 5xx, pool exhausted) |
| 5 | network failure or timeout |

### Headless notes

The CLI never prompts when stdin is not a TTY. Destructive operations
(`firsty esim terminate`) require `--yes` in that case. Include
`.error.requestId` when reporting API errors to the user.

### Not yet in the CLI

Porting, phone numbers, webhook listening, and `firsty skills install` are
not in this version. Use the API reference at
https://developers.firsty.app/api-reference for those.

## Going live

The sandbox cannot be upgraded, and this CLI cannot reach production at all.
A human must talk to sales to activate a live account:
https://developers.firsty.app/contact-sales. A live integration then calls the API
directly over HTTP with the issued credentials. Do not build the user's
production path on the CLI. When live accounts land, `firsty login` (already
reserved) will authenticate against the production environment with the same
commands and output contract.
