stream-droid
    Preparing search index...

    stream-droid

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

    CI npm version license: MIT docs agents

    stream-droid — one command opens a live Android device in your browser; a console with an app bar, per-device cards and a device stage; drive it by hand or by agent; share a link that's view-only by default

    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.

    • Live screen streaming of any Android emulator or physical device to the browser.
    • Full input — click = tap, drag = swipe, keyboard = type, buttons for Back / Home / Recents.
    • Three capture backendsscreenrecord (default, zero setup), scrcpy (high FPS; jar auto-downloads), and emulator grpc (PNG, no adb for capture). See capture backends.
    • Semantic control — target UI elements by resource-id or text via uiautomator, no Appium server. Survives layout/resolution changes. See control & semantics.
    • Emulator management — a devices rail of per-device cards lists running / stopped AVDs and boots them, with visible Headless / Cold boot toggles on each card (no host window = headless; adb/stream only).
    • Remote sharing — open a public link + QR straight from the in-app Share button (view-only or with control), or via --tunnel / --tunnel-control. See remote sharing.
    • Instant preview (poster frame), a ● LIVE indicator, and a responsive UI that collapses to a ☰ drawer on mobile.
    • Agent-ready — an installable agent skill plus HTTP/WebSocket APIs for AI-driven automation.
    • One-command CLI — stream by name, list streams, kill an emulator, tail logcat, or open a tunnel.

    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.

    • For humans — see the device, click around, share a link so someone remote can watch or take over.
    • For agents — a stable loop of screenshot → read elements → act, with element-level targeting that doesn't break when the layout shifts, over plain 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 — so bunx stream-droid and npx stream-droid both 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 — shotuitap / 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 /pluginMarketplaces 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 OXCoxlint (.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.

    MIT © David Okonji. Inspired by serve-sim.