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 var | Meaning |
|---|---|
PREVIEWAPK_API_KEY | Your 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_DEVICE | Default device id, so you can omit --device |
PREVIEWAPK_HOST | Base 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
| Command | Does |
|---|---|
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. |
stop | Stops the session; the session link dies. |
devices | List 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. |
status | One line: name, status, attached app — plus the session's share link while live. |
links | The session link (view-only — the one link there is), the session status, and the current driver. |
look | A 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-clear | Make 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 reboot | Restart 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. |
logs | The 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. |
tree | Print 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|user | Pass 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.) |
relaunch | Bring 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.