# Set up PreviewAPK

You are an AI coding agent. Your human asked you to set them up with
PreviewAPK — a cloud Android phone you can drive (push an APK, start a
session, read the screen, tap and type, read the app's logs) so you can
run and verify Android apps instead of building blind.

Follow these steps in order. The human's only jobs are to approve one
browser prompt and to answer one short question at the end. **The human
never pastes an API key into this chat** — the key is minted by the
portal and saved directly on this machine by the setup command.

Reference: the full agent skill lives at
https://previewapk.siteport.site/skills/previewapk/SKILL.md — read it
before driving a phone.

## Step 0 — Check before installing anything

Setup is safe to re-run and resumes where it left off. First check
whether this machine is already set up:

- Is the PreviewAPK CLI present (`previewapk` on PATH, or a downloaded
  `previewapk.py`)?
- Is `PREVIEWAPK_API_KEY` set, or does `~/.previewapk/credentials.json`
  exist?

Run `previewapk status` (or `previewapk devices`). If it answers,
setup is already done — skip to Step 4. Never reinstall over a working
setup, and never mint a second key over a working one: `previewapk
setup` itself checks first and reports `{"step": "ready", …}` without
minting anything.

## Step 1 — Install the CLI (only if missing)

Download the single-file, stdlib-only CLI:

```
curl -o previewapk https://previewapk.siteport.site/cli/previewapk.py
chmod +x previewapk
```

No dependencies, no build step — any `python3` runs it.

## Step 2 — Start setup and show the human the code

Run:

```
previewapk setup --no-wait
```

It prints exactly one JSON line:

```json
{"step": "sign_in", "code": "K7QX-4M2P", "url": "https://previewapk.siteport.site/portal/agent-grant", "expires_in": 600}
```

Show the human the URL and the code, in plain words:

> Open https://previewapk.siteport.site/portal/agent-grant and sign in
> (or create your account). Check the page shows the code **K7QX-4M2P**,
> then click **Approve**.

Rules while you wait:

- Do not open the grant page yourself, and do not ask the human to
  paste a key — approval in the browser is the whole flow.
- The code is single-use and expires in 10 minutes. It is shown
  identically here and on the page; that comparison is the defense
  against phishing, so quote it exactly.
- A label helps the owner recognize the key later: `previewapk setup
  --label "Claude Code on laptop"` — the label is shown on the
  approval page.

**Accounts note:** approval requires an existing PreviewAPK account —
the grant flow creates no side door around signup or the Terms. While
PreviewAPK is in private preview, signup is limited; if the human has
no account yet, the grant page leads them to sign-in / early access
first, and setup completes once they have one and approve a code.

## Step 3 — Poll until it resolves

Re-run setup (no flags). It resumes the pending code and polls the
token endpoint every few seconds:

```
previewapk setup
```

It prints the `sign_in` line again, then exactly one final step:

- `{"step": "ready", "account": "…", "key_saved":
  "~/.previewapk/credentials.json", "devices": ["Device 1"]}` — the
  key was delivered **once** by the portal and saved to
  `~/.previewapk/credentials.json` (file permissions 0600). Never
  print, echo, or read back that file's contents.
- `{"step": "no_slots", …}` — signed in fine, but the account has no
  device slot yet (early-access / paywall state). Tell the human
  plainly and point them at the portal; do not treat it as an error
  and do not retry.
- `{"step": "denied", …}` — the human declined. Stop. Explain that
  setup needs their approval, and mention the manual route (copy a
  key from the portal's Developers card into `PREVIEWAPK_API_KEY`)
  without pushing.
- `{"step": "expired", …}` — the code aged out (10 minutes). Run
  `previewapk setup --no-wait` again for a fresh code and repeat
  Step 2.

From now on every CLI command uses the saved key; the MCP server
reads the same file when `PREVIEWAPK_API_KEY` is unset, so one setup
covers both clients. The environment variable still wins when set.

**Zero-install route:** the same key also works with the hosted MCP
endpoint — no client file at all. Register
`https://previewapk.siteport.site/mcp` as a streamable-http MCP
server with header `Authorization: Bearer <the saved key>` (Claude
Code: `claude mcp add --transport http previewapk
https://previewapk.siteport.site/mcp --header "Authorization: Bearer
<key>"`). The hosted server issues a session id at connect and your
working-device selection rides on it, exactly like the local server's.
Prefer the hosted route when the agent's MCP client supports remote
servers; keep the local file for stdio-only clients. Over hosted MCP,
push_apk takes the APK as base64 (`apk_b64`) instead of a file path.
The key is a normal account key — the owner can view or revoke it in
the portal under API keys; revoking it unpairs this machine, and
setup handles a revoked saved key by returning to `sign_in`.

## Step 4 — Ready behavior

With `step: ready`:

1. Run `previewapk devices` and report, in one line, what the account
   has (device names, profiles, attached apps).
2. Ask **one short question**: what do you want to use the phone for?
3. Then help with exactly that, under the SKILL.md rules: select the
   working device, push → start → observe → drive → verify → report →
   stop. Links never drive; one driver per session; a fresh phone
   every session; file a field report at the end unless the account
   turned diagnostic sharing off.

## The endpoints (if you are integrating instead of using the CLI)

- `POST /portal/api/setup/start` — body `{"label": "…"}` (optional).
  Returns `{code, url, expires_in}`. Rate-limited per IP.
- `POST /portal/api/setup/token` — body `{"code": "…"}`. Returns
  `{"status": "pending" | "denied" | "expired" | "claimed"}`, or —
  exactly once, on the first poll after approval —
  `{"status": "approved", "key": "…", "email": "…", "devices": […]}`.
  An unknown code returns HTTP 404 with the standard error envelope
  (`code: "unknown_code"`).
- `GET|POST /portal/agent-grant` — the human approval page. Requires a
  signed-in portal session.
