CLI reference

One stdlib-only Python file. No dependencies, no build step — if a machine has python3, the CLI works on it, which makes it a good fit for agent shells and CI jobs.

Install

curl -o previewapk https://previewapk.siteport.site/cli/previewapk.py
chmod +x previewapk
export PREVIEWAPK_API_KEY=<your key>

Configuration

Env varMeaning
PREVIEWAPK_API_KEYYour account key — or run previewapk setup once and the CLI reads the key it saved at ~/.previewapk/credentials.json (the env var wins when both exist)
PREVIEWAPK_DEVICEDefault device id, so you can omit --device
PREVIEWAPK_HOSTBase URL override (default https://previewapk.siteport.site)

Global flags work in any position: previewapk --device dev1 status --json and previewapk status --device dev1 --json are the same call. --json prints the raw API response — use it in scripts and let agents parse it.

Commands

CommandDoes
push <apk> [--run] [--driver agent|user]Uploads and attaches the APK. On a running device (you hold the wheel) the build is hot-swapped in live — installed and relaunched, no reboot. With --run, starts a session immediately (default driver agent). Prints the app label/version; with --run, where to fetch the session's share link.
start [--driver agent|user]Starts a session on the device's current app.
stopStops the session; the session link dies.
devicesList the account's devices — name, id, profile, Android version, status, attached app. The starting point on multi-device accounts.
use <name>Select the working device (the cd): later commands talk to it without --device. use alone shows the current selection; start and push also select the device they name. Without a selection, commands refuse — nothing is guessed.
statusOne line: name, status, attached app — plus the session's share link while live.
linksThe session link (view-only — the one link there is), the session status, and the current driver.
lookA compact numbered snapshot of the screen: snapshot id, foreground app, keyboard state, and the useful elements (ref, type, text, id, flags, center) in reading order. The cheap default way to read the screen — tap an element from it with tap --ref N --snapshot <id> (the server re-checks the element first; stale_snapshot if the screen moved on).
wait --text "…" [--gone] [--timeout 30]Wait until an element (--text, --desc, or --id) appears — or disappears, with --gone. Exit code 3 on timeout. Use after anything that loads instead of sleeping blind.
wait --for driver [--timeout 300]Wait until the human hands the wheel back to the agent (after request-human or any takeover). Exit 0 on handback — and prints the human's handback note if they left one; exit 3 on timeout.
request-human "reason"Ask the human for a step only a human should do (password, 2FA, payment, CAPTCHA): records the request in the portal (the device card shows "Your agent needs you") and prints the exact message to relay to them. Then wait --for driver.
finish --summary "…" [--verdict passed|failed|mixed] [--end-session]File the session wrap-up and print the exact message to relay to the human. The counts in it (duration, actions, errors) are composed by the server from the session's record — never from memory. Default: the session keeps running; --end-session stops it after filing.
act <batch.json>Run a batch of up to 20 actions in order from a JSON file (or stdin): {"actions": […], "observe": "none|ui|screenshot|both"}. Stops at the first failure, prints per-action results, and is never auto-replayed — a dropped connection exits 2 (outcome unknown; look, then resubmit from the first unconfirmed action).
record start [--name "…"] · record stop · record status · record pull [file.mp4] [--id rec_…]Record the screen to an MP4 (screenrecord; one recording caps at 180s). stop pulls the MP4 to the server and reports its size and duration; pull downloads it (resumable — a cut transfer picks up where it stopped). Recordings stay downloadable after the session ends. A recording in progress when the session stops or the phone reboots is saved automatically.
device timezone <IANA>Set the device timezone (e.g. America/New_York) — the reply is the timezone the device itself reports after the set.
device locale <tag> [--package …]Set the session app's language (BCP 47, e.g. de-DE) — the Android per-app locale. Relaunch the app if it doesn't switch live.
device location <lat> <lon> · device location-clearMake GPS report a mock fix — for testing location-aware flows — and clear it again. The fix lasts until cleared or the session ends.
device push <local> [remote] · device pull <remote> [local] · device ls [path] · device rm <path>Move files in the device's shared storage (/sdcard): seed fixtures, pull back what the app produced. 10 MB cap per file; paths can't escape shared storage.
device rebootRestart the phone — same phone, same data (app, files, settings survive), app relaunched to the foreground. Under a minute. The way to test cold-start behavior mid-session.
setup [--no-wait] [--label "…"]Agent-led setup: prints a short code and the portal grant URL; the human approves while signed in, the portal mints a normal API key, and setup saves it to ~/.previewapk/credentials.json (0600) — never printed. --no-wait prints one JSON line ({"step": "sign_in", …}) and exits; re-running setup polls until ready / denied / expired. Full walkthrough: /setup.md.
feedback --worked "…" --broke "…" --wish "…" [--rating 1-5]File a field report — what worked, what broke, what you wish it had, what you worked around. Read and tallied; field reports set the roadmap. Refused (honestly) when the account turned diagnostic sharing off in the portal's Privacy settings.
tap <x> <y>Tap at fractional coordinates (0–1). Out-of-range values (pixels) are refused with an explanation — never silently clamped.
tap --text "Log in"Tap an element by its label — resolved from the UI hierarchy (searches text, then desc). --desc and --id select on those fields; --index N picks among matches. Prefer selectors over coordinates whenever the target has a label. When nothing matches, the error lists the labels that are on screen. tap --field N taps the Nth input field — the reliable way into form fields whose hint text the hierarchy cannot see.
logsThe app's debug log for this session. --lines N, --since SEC, --errors, --grep TEXT, and --follow to stream live. After the session ends it prints the saved tail.
treePrint the screen's UI hierarchy as an indented outline (elements, labels, centers, state flags). --json prints the raw tree.
swipe <x1> <y1> <x2> <y2> [ms]Swipe between fractional points.
key <code>Android keyevent (3 Home, 4 Back, 66 Enter, 67 Delete).
text "<string>"Type into the focused field. Fails honestly when nothing is focused — tap the field first.
shot [file.png]Save a screenshot of the current screen — the real screen, so it matches the hierarchy (default previewapk-shot.png).
handoff agent|userPass the wheel mid-session. (Handing back from the portal can carry a note for the agent — the CLI prints it on the next status/wait.)
relaunchBring the app back to the foreground — after Back exits to the launcher, or the app closed. About 2 seconds, no reboot.

The agent loop, in shell

python3 previewapk push app.apk --run
for i in $(seq 1 20); do
  python3 previewapk status --json | grep -q '"live"' && break
  sleep 5
done
python3 previewapk tap 0.5 0.62
python3 previewapk text "demo@example.com"
python3 previewapk shot after-login.png
python3 previewapk handoff user      # hand the human the wheel
python3 previewapk links             # paste the session link in the PR/chat
python3 previewapk finish --summary "login flow verified end to end" --verdict passed
                                     # file the wrap-up; relay its message verbatim

Exit codes are honest: a refused tap (driver gating) or a stopped device exits non-zero and prints the API's error message, so scripts can branch on failure instead of guessing.