kuri-android
Drive Android devices and emulators through the kuri android
subcommand. Implementation lives in kuri-mobile/src/android/ and
the main kuri binary forwards kuri android … to the
kuri-mobile binary.
When to use this skill
- Enumerate attached Android devices / running emulators.
- Send taps, swipes, long-presses, key events, or text to a phone.
- Capture a PNG screenshot with
screencap -p.
- Dump the UI tree (via
uiautomator dump) and act on element refs.
- Launch / terminate Android apps by package name.
- List installed packages.
Do not use this skill for:
- iOS — use the
kuri-ios skill instead.
- Running arbitrary JavaScript on-device (no on-device driver in v1).
Prerequisites
adb on $PATH and an adb server reachable on 127.0.0.1:5037.
Install on macOS with brew install android-platform-tools then
run adb start-server once.
- A connected device with USB debugging enabled, or a running emulator.
kuri-mobile built and either on $PATH or next to the kuri
binary (zig-out/bin/kuri-mobile).
Build
cd kuri-mobile
zig build
cp zig-out/bin/kuri-mobile ../zig-out/bin/
zig build test # unit tests: adb framing, uitree parser, usbmuxd plist
Typical flow
# 1. Confirm adb is reachable and a device is listed
adb start-server
kuri android list-devices
# emulator-5554 device
# 2. Launch an app, wait, screenshot
kuri android launch com.android.chrome
sleep 3
kuri android screenshot chrome.png
# 3. Read the screen — start here, not with uitree
kuri android state
# app mCurrentFocus=Window{... com.android.settings/.Settings}
# screen Physical size: 1080x2400
# @e4 LinearLayout id=search_action_bar text=Search Settings tap=540,178
# @e19 LinearLayout id= text=Network & internet Mobile, Wi-Fi, hotspot tap=540,666
# @e12 RecyclerView id=recycler_view text= tap=540,1326 *scrollable
# Full tree when you need static labels too; --interactive trims it
kuri android uitree # everything meaningful (72 rows on Settings)
kuri android uitree --interactive # only what can be acted on (14 rows)
# 4. Interact — prefer selectors over raw coordinates
kuri android tap --label "Network & internet"
kuri android tap --id search_action_bar
kuri android tap --class Button --index 1 # 2nd Button on screen
kuri android swipe 100 1500 100 500 250 # scroll up
kuri android type --clear "hello world" # replaces the field's contents
kuri android press back
Coordinates shift between devices and after any layout change; a selector
survives both. tap 540 1200 is still there for when you genuinely have a
point rather than an element.
Full command surface
| Command |
Purpose |
kuri android list-devices |
Enumerate via host:devices |
kuri android state |
Foreground app + screen size + every actionable element (alias: snapshot) |
kuri android uitree [--interactive] |
Flat element list from uiautomator dump |
kuri android find <selector> |
Matching elements with tap-ready centroids; non-zero exit on no match |
kuri android wait-for-ui --label <t> |
Block until an element appears (--absent to invert) |
kuri android notifications [--open] |
Read posted notifications; --open pulls the shade down |
kuri android current-activity |
Package/activity holding focus |
kuri android screen-info |
Physical size and density |
kuri android logcat [--last N] [--predicate T] |
Bounded log read |
kuri android getprop <name> / dumpsys <section> |
Raw system state |
kuri android tap <x> <y> / tap <selector> |
Tap a point or a resolved element |
kuri android double-tap <x> <y> |
Double tap |
kuri android long-press <x> <y> [ms] |
Long press, default 800 ms |
kuri android swipe <x1> <y1> <x2> <y2> [ms] |
Swipe / scroll (alias: scroll) |
kuri android gesture <x,y> <x,y> ... |
Multi-point drag via input motionevent (alias: drag) |
kuri android touch <down|up|move> <x> <y> |
Raw motion phase |
kuri android type <text...> [--clear] |
Type text; --clear replaces the field |
kuri android press <button> |
home|back|menu|enter|tab|space|del|recents|volumeUp|volumeDown|power|dpadUp|dpadDown|dpadLeft|dpadRight|dpadCenter |
kuri android keyevent <KEYCODE_*> |
Raw keycode |
kuri android wait <ms> |
Sleep; needs no device |
kuri android batch <action> ... |
Several actions over one adb session |
kuri android screenshot [path.png] |
PNG from exec:screencap -p |
kuri android launch <package> |
monkey -p <pkg> -c LAUNCHER 1 |
kuri android terminate <package> |
am force-stop |
kuri android openurl <url> |
VIEW intent (alias: navigate) |
kuri android list-apps / uninstall <pkg> / clear <pkg> |
Package management |
kuri android tools --json is the machine-readable version of this table and
is generated from the same source, so it cannot drift from the dispatcher.
Global flag: --serial <id> — target a specific device. Omit when
exactly one device is attached.
Selectors
find and tap accept --label, --id, --class, --desc and --index,
AND-ed together so each one narrows the match:
--label searches text, resource-id and content-desc at once
--id takes either the short btn_login or the full
com.example.app:id/btn_login; listings print the short form
--index N picks the Nth match (0-based) when a selector is ambiguous —
find prints the ordinal in its first column
--interactive restricts to elements that can actually be acted on
Element flags
state and uitree mark what an element's label cannot tell you:
*clickable, *long-clickable, *scrollable, *checked / *unchecked,
*password, *focused, *selected, *disabled.
Native Zig surfaces (honesty)
adb wire protocol is re-implemented in Zig. We open a libc
TCP socket to 127.0.0.1:5037, speak the 4-hex-digit length
framing, issue host:devices, host:transport:<serial>, shell:
and exec: services, and read framed or stream responses. We
never shell out to the adb binary at runtime.
- UI tree parser is a Zig XML scanner that turns
uiautomator dump
XML into a stable @e<n> element list with bounds, text,
content-desc, resource-id and the interactivity/state attributes.
It tracks nesting, so a clickable row whose label lives in child
TextViews is named from those children — that layout is everywhere
on Android and such rows are otherwise unaddressable by label.
Attribute values are XML-decoded, so "Network & internet" matches the
string actually on screen rather than Network & internet.
- Device-side commands (
screencap, uiautomator dump, input,
monkey, am, pm) are Android OS binaries that the device's
shell runs — we just frame the requests over adb from Zig.
Intentional limits
- ASCII-only text typing. Non-ASCII needs an IME workaround, not
bundled.
- No on-device driver, so no
run_code JavaScript sandbox.
- No bundled emulator image — you provide the device or the emulator.
Common errors and what they mean
| Message |
Fix |
could not reach adb server on 127.0.0.1:5037. Is 'adb start-server' running? |
Run adb start-server. |
no device attached. Plug in a phone with USB debugging enabled, or boot an emulator. |
Connect a device or boot an emulator; confirm with adb devices. |
adb returned FAIL — see the warning log above for details. |
Check the preceding warn-level log line; usually device state (unauthorized, offline). |
unknown button name; see 'kuri-mobile android' for the supported list. |
Use one of the documented button names. |
1---2name: kuri-android3description: Use kuri-android to drive Android devices and emulators from the CLI via a native Zig adb wire-protocol client. Read the screen with `state` (foreground app plus every actionable element, with tap-ready coordinates), tap by selector (--label/--id/--class/--desc/--index) rather than raw coordinates, swipe (scroll), double-tap, long-press, type text with --clear to replace a field, press hardware/navigation keys, take PNG screenshots, dump the UI tree, read notifications, launch/terminate apps by package, list installed packages, and list attached devices. Talks adb directly over TCP 127.0.0.1:5037 — never shells out to the `adb` binary at runtime. Trigger phrases include "tap on android phone", "screenshot the emulator", "list connected android devices", "dump the android ui tree", "what's on the android screen", "launch chrome on android".4---56# kuri-android78Drive Android devices and emulators through the `kuri android`9subcommand. Implementation lives in `kuri-mobile/src/android/` and10the main `kuri` binary forwards `kuri android …` to the11`kuri-mobile` binary.1213## When to use this skill1415- Enumerate attached Android devices / running emulators.16- Send taps, swipes, long-presses, key events, or text to a phone.17- Capture a PNG screenshot with `screencap -p`.18- Dump the UI tree (via `uiautomator dump`) and act on element refs.19- Launch / terminate Android apps by package name.20- List installed packages.2122Do **not** use this skill for:2324- iOS — use the `kuri-ios` skill instead.25- Running arbitrary JavaScript on-device (no on-device driver in v1).2627## Prerequisites2829- `adb` on `$PATH` and an adb server reachable on `127.0.0.1:5037`.30 Install on macOS with `brew install android-platform-tools` then31 run `adb start-server` once.32- A connected device with USB debugging enabled, or a running emulator.33- `kuri-mobile` built and either on `$PATH` or next to the `kuri`34 binary (`zig-out/bin/kuri-mobile`).3536## Build3738```sh39cd kuri-mobile40zig build41cp zig-out/bin/kuri-mobile ../zig-out/bin/42zig build test # unit tests: adb framing, uitree parser, usbmuxd plist43```4445## Typical flow4647```sh48# 1. Confirm adb is reachable and a device is listed49adb start-server50kuri android list-devices51# emulator-5554 device5253# 2. Launch an app, wait, screenshot54kuri android launch com.android.chrome55sleep 356kuri android screenshot chrome.png5758# 3. Read the screen — start here, not with uitree59kuri android state60# app mCurrentFocus=Window{... com.android.settings/.Settings}61# screen Physical size: 1080x240062# @e4 LinearLayout id=search_action_bar text=Search Settings tap=540,17863# @e19 LinearLayout id= text=Network & internet Mobile, Wi-Fi, hotspot tap=540,66664# @e12 RecyclerView id=recycler_view text= tap=540,1326 *scrollable6566# Full tree when you need static labels too; --interactive trims it67kuri android uitree # everything meaningful (72 rows on Settings)68kuri android uitree --interactive # only what can be acted on (14 rows)6970# 4. Interact — prefer selectors over raw coordinates71kuri android tap --label "Network & internet"72kuri android tap --id search_action_bar73kuri android tap --class Button --index 1 # 2nd Button on screen74kuri android swipe 100 1500 100 500 250 # scroll up75kuri android type --clear "hello world" # replaces the field's contents76kuri android press back77```7879Coordinates shift between devices and after any layout change; a selector80survives both. `tap 540 1200` is still there for when you genuinely have a81point rather than an element.8283## Full command surface8485| Command | Purpose |86|---|---|87| `kuri android list-devices` | Enumerate via `host:devices` |88| `kuri android state` | Foreground app + screen size + every actionable element (alias: `snapshot`) |89| `kuri android uitree [--interactive]` | Flat element list from `uiautomator dump` |90| `kuri android find <selector>` | Matching elements with tap-ready centroids; non-zero exit on no match |91| `kuri android wait-for-ui --label <t>` | Block until an element appears (`--absent` to invert) |92| `kuri android notifications [--open]` | Read posted notifications; `--open` pulls the shade down |93| `kuri android current-activity` | Package/activity holding focus |94| `kuri android screen-info` | Physical size and density |95| `kuri android logcat [--last N] [--predicate T]` | Bounded log read |96| `kuri android getprop <name>` / `dumpsys <section>` | Raw system state |97| `kuri android tap <x> <y>` / `tap <selector>` | Tap a point or a resolved element |98| `kuri android double-tap <x> <y>` | Double tap |99| `kuri android long-press <x> <y> [ms]` | Long press, default 800 ms |100| `kuri android swipe <x1> <y1> <x2> <y2> [ms]` | Swipe / scroll (alias: `scroll`) |101| `kuri android gesture <x,y> <x,y> ...` | Multi-point drag via `input motionevent` (alias: `drag`) |102| `kuri android touch <down\|up\|move> <x> <y>` | Raw motion phase |103| `kuri android type <text...> [--clear]` | Type text; `--clear` replaces the field |104| `kuri android press <button>` | `home\|back\|menu\|enter\|tab\|space\|del\|recents\|volumeUp\|volumeDown\|power\|dpadUp\|dpadDown\|dpadLeft\|dpadRight\|dpadCenter` |105| `kuri android keyevent <KEYCODE_*>` | Raw keycode |106| `kuri android wait <ms>` | Sleep; needs no device |107| `kuri android batch <action> ...` | Several actions over one adb session |108| `kuri android screenshot [path.png]` | PNG from `exec:screencap -p` |109| `kuri android launch <package>` | `monkey -p <pkg> -c LAUNCHER 1` |110| `kuri android terminate <package>` | `am force-stop` |111| `kuri android openurl <url>` | VIEW intent (alias: `navigate`) |112| `kuri android list-apps` / `uninstall <pkg>` / `clear <pkg>` | Package management |113114`kuri android tools --json` is the machine-readable version of this table and115is generated from the same source, so it cannot drift from the dispatcher.116117Global flag: `--serial <id>` — target a specific device. Omit when118exactly one device is attached.119120### Selectors121122`find` and `tap` accept `--label`, `--id`, `--class`, `--desc` and `--index`,123AND-ed together so each one narrows the match:124125- `--label` searches text, resource-id and content-desc at once126- `--id` takes either the short `btn_login` or the full127 `com.example.app:id/btn_login`; listings print the short form128- `--index N` picks the Nth match (0-based) when a selector is ambiguous —129 `find` prints the ordinal in its first column130- `--interactive` restricts to elements that can actually be acted on131132### Element flags133134`state` and `uitree` mark what an element's label cannot tell you:135`*clickable`, `*long-clickable`, `*scrollable`, `*checked` / `*unchecked`,136`*password`, `*focused`, `*selected`, `*disabled`.137138## Native Zig surfaces (honesty)139140- `adb` **wire protocol** is re-implemented in Zig. We open a libc141 TCP socket to `127.0.0.1:5037`, speak the 4-hex-digit length142 framing, issue `host:devices`, `host:transport:<serial>`, `shell:`143 and `exec:` services, and read framed or stream responses. We144 never shell out to the `adb` binary at runtime.145- **UI tree parser** is a Zig XML scanner that turns `uiautomator dump`146 XML into a stable `@e<n>` element list with bounds, text,147 content-desc, resource-id and the interactivity/state attributes.148 It tracks nesting, so a clickable row whose label lives in child149 `TextView`s is named from those children — that layout is everywhere150 on Android and such rows are otherwise unaddressable by label.151 Attribute values are XML-decoded, so "Network & internet" matches the152 string actually on screen rather than `Network & internet`.153- Device-side commands (`screencap`, `uiautomator dump`, `input`,154 `monkey`, `am`, `pm`) are Android OS binaries that the device's155 shell runs — we just frame the requests over adb from Zig.156157## Intentional limits158159- ASCII-only text typing. Non-ASCII needs an IME workaround, not160 bundled.161- No on-device driver, so no `run_code` JavaScript sandbox.162- No bundled emulator image — you provide the device or the emulator.163164## Common errors and what they mean165166| Message | Fix |167|---|---|168| `could not reach adb server on 127.0.0.1:5037. Is 'adb start-server' running?` | Run `adb start-server`. |169| `no device attached. Plug in a phone with USB debugging enabled, or boot an emulator.` | Connect a device or boot an emulator; confirm with `adb devices`. |170| `adb returned FAIL — see the warning log above for details.` | Check the preceding warn-level log line; usually device state (unauthorized, offline). |171| `unknown button name; see 'kuri-mobile android' for the supported list.` | Use one of the documented button names. |