The whole bridge-and-deploy dance on the left. One command on the right.
Shipping an app shouldn't require a Windows-Linux peace treaty.
Same question, same dead ends, for years. Every workaround adds another moving part.
ADB in WSL2
The canonical years-long thread for making ADB see a phone from WSL2.
Open since 2020ADB device list empty using WSL2
The failure mode is familiar: the phone is plugged in, but WSL sees nothing.
82k+ viewsVisible, then gone
USB bridging works until it does not, then the deploy loop collapses again.
Flaky pathOne job, done well: get your APK onto the phone, fast.
Detects your framework
Flutter, React Native, Expo, Capacitor, Cordova or native Gradle - runapk finds the project, app ID, launch activity and APK, and builds each with the right toolchain.
Builds cross-OS from WSL
Drives Windows adb.exe and the flutter/gradlew.bat toolchain from WSL, converts paths, and skips the usbipd, kernel and udev detour.
Recovers safely
Restarts wedged ADB and handles safe retries. Reinstall flows that can delete data require explicit approval.
Ready for agents
Ships as .agents/skills/runapk with JSON envelopes, NDJSON events, typed errors and documented exit codes.
Inspect and drive
Capture screenshots, tap, swipe, stream filtered logs, reverse ports and pull crash traces from the same CLI.
One small binary
Rust, no runtime, no background service. Install it once and run it from any Android project.
Readable by humans, parseable by machines.
Add --json to any command and runapk emits stable envelopes; live commands stream NDJSON events, then a final result. Exit codes are stable too, so a caller always knows what happened.
$ runapk status --json { "schema": "runapk.result.v1", "ok": true, "command": "status", "project": "/path/to/project", "serial": "DEVICE_SERIAL", "data": {}, "error": null }
The questions you're probably about to ask.
Do I still need usbipd, a custom kernel or udev rules?
No. runapk talks to your phone through Windows adb.exe over the WSL-to-Windows bridge, so you skip the whole USB-passthrough detour. Plug the phone into Windows the way you normally would.
How does it know what to build and launch?
It auto-detects the framework (Flutter, React Native, Expo, Capacitor, Cordova or native Gradle) from the project root or its android/ folder, then the application ID, APK and launch activity, and builds each with the right toolchain.
Will it wipe my app data?
Not without asking. Normal installs are incremental; any reinstall flow that could delete data requires explicit approval first.
What happens when ADB wedges?
runapk restarts the ADB server and retries safely, so a stuck daemon doesn't break your deploy loop.
Can I drive it from scripts, CI or a coding agent?
Yes. Add --json for stable result envelopes, live commands stream NDJSON events, errors are typed and exit codes are documented. It also ships as an .agents/skills/runapk skill.
Does it do more than deploy?
Yes - capture screenshots, tap, swipe, stream filtered logs, reverse ports and pull crash traces, all from the same CLI.
How do I install it?
Run cargo install runapk. It's a single Rust binary with no runtime and no background service - install once, run it from any Android project.
Stop fighting the bridge and start shipping.
Cargo, or build from source. No runtime to install.
From crates.io
# requires Rust toolchain $ cargo install runapk
From source
$ git clone https://github.com/SylvainM98/runapk $ cd runapk && cargo build --release $ cp target/release/runapk ~/.local/bin/