Stream a running Android emulator or device to your browser and drive it — tap, swipe, type, hardware keys — for humans and AI agents.

Point stream-droid at a running emulator or phone and it serves a live view of the screen in the browser that you can click, drag, and type into. A device rail lists and boots AVDs (optionally headless), and everything is scriptable over a small HTTP/WebSocket API — so an AI agent can see and act on an Android app the same way a person does.
It's the Android analogue of Evan Bacon's
serve-sim: where serve-sim needed a
Swift framebuffer helper for the iOS simulator, Android already exposes
everything through adb (and, for emulators, a gRPC API). The server is
TypeScript on bun; the browser UI is React + Tailwind.
screenrecord (default, zero setup), scrcpy
(high FPS; jar auto-downloads), and emulator grpc (PNG, no adb for capture).
See capture backends.uiautomator, no Appium server. Survives layout/resolution changes.
See control & semantics.--tunnel /
--tunnel-control. See remote sharing.Watching and driving an Android screen usually means either the full Android Studio GUI (heavy, human-only) or Appium/UiAutomator2 (a driver stack aimed at scripted tests). stream-droid fills the gap serve-sim opened for iOS: a lightweight, one-command way to put a live, interactive device in a browser tab — to demo, debug, pair on, or hand to an AI agent.
adb with no extra driver server.You need bun or node ≥ 20, adb on
PATH, and a running device or emulator. The SDK emulator (to list/boot AVDs)
is optional, and the scrcpy jar auto-downloads if you use that backend. The server
runs a preflight check on startup and exits with a specific fix if something's
missing.
→ Full prerequisite matrix, physical-device notes, troubleshooting, and the security posture: docs/setup.md.
bunx stream-droid # run the published package (no clone/build)
npx stream-droid # …or with node — bun not required
# …or from a clone (building the client uses bun):
bun install
bun start # builds the client, serves http://localhost:3200
bun start builds the CSS + client bundle, then runs the server and auto-opens
your browser at http://localhost:3200.
Runs under bun or node. The
bin(bin/stream-droid.mjs) launches the TypeScript server natively under bun, or under node via tsx — sobunx stream-droidandnpx stream-droidboth work and bun isn't required to run the published tool. Building the client from a clone still uses bun.
Pass a positional emulator name to stream (and boot, if stopped) that device by default.
| Command | Meaning |
|---|---|
stream-droid <name> |
stream that emulator/AVD by default (boots it if stopped) |
-h / --help |
print usage and exit |
-a / --list |
list running streams (+ stopped AVDs), then exit |
-l / --log / --logcat |
stream the device's logcat, colourised by level (no server) |
--kill [name] |
shut down a running emulator (emulators only), then exit |
-t / --tunnel |
expose the server via a public link + QR (view-only) |
-tc / --tunnel-control |
tunnel, but the shared link can also control |
| Flag | Env | Default | Meaning |
|---|---|---|---|
--port |
PORT |
3200 |
HTTP + WS port (if busy, prompts to use the next free port; auto when headless / non-interactive) |
-d / --headless |
STREAM_DROID_HEADLESS=1 |
off | don't auto-open the browser (server still runs) |
-v / --verbose |
STREAM_DROID_VERBOSE=1 |
off | show logs — quiet by default (only errors); -v prints info/warn/debug (frames, control) + timestamps |
--serial / --emulator / --avd |
ANDROID_SERIAL |
first running device | device to stream (adb serial or AVD name) |
--capture |
CAPTURE |
screenrecord |
screenrecord, scrcpy, or grpc (emulator-only) |
--max-size |
STREAM_DROID_MAX_SIZE |
0 (native) |
downscale capture so its longer edge ≤ px (h264 backends) — cuts encode cost + bandwidth |
--bit-rate |
STREAM_DROID_BIT_RATE |
backend default | encoder bit-rate, e.g. 4000000, 3M, 800K (h264 backends) |
--scrcpy-server |
SCRCPY_SERVER_JAR |
auto-download | scrcpy-server jar; omit to auto-download the pinned v4.1 jar |
--scrcpy-control |
SCRCPY_CONTROL |
on |
off routes input via adb input even in scrcpy mode |
stream-droid # stream the running emulator, open the browser
stream-droid Pixel_9 --headless # boot + stream Pixel_9, no browser window
stream-droid --capture scrcpy # high-FPS backend (v4.1 jar auto-downloads)
stream-droid --serial emulator-5554 --port 4000
stream-droid --tunnel-control # share a controllable public link + QR
stream-droid -l Pixel_9 # tail colourised logcat, no server
Full flag/command reference: skills/drive/references/cli.md.
A bun server bridges the browser to the device: a chosen capture backend
produces the video (H.264) or PNG frames, streamed over a WebSocket; the React
client renders <video> (via jMuxer/MSE) or <canvas>; and control messages are
injected back through gRPC, the scrcpy control socket, or adb input. Coordinates
are normalized so taps stay correct across resolutions and rotation.
→ Architecture, the capture/render pipeline, and design notes (instant-preview poster, headless boot, one-pipe-per-client): docs/architecture.md.
⌘V / Ctrl+V over the device — pastes your clipboard into the focused field on the device.⌘C / Ctrl+C over the device — copies the device's clipboard to your computer. Requires --capture scrcpy with control enabled (the default; disabled by --scrcpy-control off); on the default screenrecord backend, or with scrcpy control off, there is no way to read the Android clipboard, so ⌘C behaves normally. If the device clipboard was already set before you connected, the first ⌘C only requests it — the value is available from the next press.Note: keyboard shortcuts require a physical keyboard. A remote viewer handed a shared link + QR on a mobile device cannot use the clipboard — clipboard support is desktop-only.
The stream-droid plugin ships four focused agent skills so an AI agent can work
a device through the server:
| Skill | For |
|---|---|
/stream-droid:drive |
the see & act loop — shot → ui → tap / type / swipe / key |
/stream-droid:emulators |
list / boot (headless) / kill AVDs |
/stream-droid:apps |
list packages, launch or foreground an app |
/stream-droid:share |
expose the session as a public link + QR |
The drive skill's helper starts the server for you and wraps the loop:
stream-droid-server # start the server headless (if needed)
stream-droid-check # verify prerequisites
drive shot # screen.png
drive ui internet # elements matching "internet"
drive tap:text "Network & internet"
stream-droid-server --stop # stop the background server when done
The helper scripts are plain ESM and run under bun or node ≥ 18.
Claude Code — the repo is a self-contained plugin marketplace, so add it and install straight from the Claude Code prompt:
/plugin marketplace add davidokonji/stream-droid
/plugin install stream-droid@stream-droid
The skills then surface as /stream-droid:drive, :emulators,
:apps, and :share (run /reload-plugins if they don't show up right
away). To update later: /plugin marketplace update stream-droid then
/plugin update stream-droid@stream-droid.
Any agent (skills.sh) — install from the GitHub repo with the skills.sh CLI, no separate registry:
npx skills add davidokonji/stream-droid
Manual — copy the skills/ tree into ~/.claude/skills/ (personal, every
project) or .claude/skills/ (checked into a specific repo). The sibling skills
share the drive skill's scripts, so copy the whole tree, not one folder.
Claude Code — updates are manual by default (enable per-marketplace
auto-update under /plugin → Marketplaces to skip this):
/plugin marketplace update stream-droid # refresh the catalog from GitHub
/plugin update stream-droid@stream-droid # pull the new version
/reload-plugins # load it into the session
Claude Code decides an update is available by comparing the plugin's advertised marketplace version, which advances only on a stable release — so in-progress changes on branches never prompt installed users to update. Releases are cut via the publish-stable workflow (see docs/PUBLISHING.md).
skills.sh — re-run npx skills add davidokonji/stream-droid. Manual —
re-copy the skills/ tree.
Each skill's task references live in its own references/ folder. Full publishing,
install, and update details are in docs/PUBLISHING.md.
bun install
bun start # build css + client, then run (opens browser)
bun run build # build:css + build:client
bun run check # oxlint + oxfmt --check + tsc --noEmit ← run before every PR
bun test # unit tests (bun:test), in __tests__/ folders
bun run typecheck # tsc --noEmit
Lint/format is OXC — oxlint (.oxlintrc.json) + oxfmt
(.oxfmtrc.json), not ESLint/Prettier. Run the server directly during dev
with bun run src/server.ts [name] [flags] (use -d so it doesn't pop a browser
tab). Unit tests are bun test (focused on pure logic); most behaviour is
verified against a real emulator (see AGENTS.md).
The full source map, conventions, and architecture gotchas for contributors and
agents live in AGENTS.md (CLAUDE.md is a symlink to it).
Issues and PRs welcome. Before opening a PR: run bun run check (it must exit
0 — lint clean, formatted, type-clean), follow the conventions in
AGENTS.md, and keep this README and the docs in sync when you change
flags, commands, or behaviour. Release and publishing steps are in
docs/PUBLISHING.md.