npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

portholejs

v0.4.0

Published

A window into your Android emulator — stream and control AVDs from the browser

Readme

Porthole

A window into your Android emulator — npx portholejs.

Porthole boots or attaches to Android phone and TV AVDs, streams them to a browser, and forwards touch, keyboard, D-pad remote, screenshots, logcat, and file drops through one local tool. It also has experimental support for the Amazon Fire TV Vega simulator. It is the Android sibling of serve-sim.

Quick Start

Prerequisites:

  • Node.js 20 or newer
  • Android Studio or Android SDK command line tools
  • emulator and adb installed under the SDK
  • Chrome, Edge, or another browser with WebCodecs for H.264 playback

Porthole finds the SDK from ANDROID_HOME, then ANDROID_SDK_ROOT, then the platform defaults:

  • macOS: ~/Library/Android/sdk
  • Linux: ~/Android/Sdk
  • Windows: %LOCALAPPDATA%\Android\Sdk
npx portholejs

With no running emulator, Porthole prompts for an AVD. To boot a specific AVD:

The scrcpy-server component

Porthole mirrors and controls the emulator by speaking the scrcpy protocol. At runtime it pushes a small server jar (scrcpy-server, Apache-2.0, by Genymobile) to the emulator over adb and connects to its video and control sockets — no scrcpy CLI or native binary is involved.

The jar is not bundled in this repository or in the npm package. It is downloaded automatically during npm install (a postinstall step) from the official Genymobile/scrcpy GitHub release, pinned to v3.1 and verified against a SHA-256 checksum. If the download was skipped (for example, an offline install), fetch it later with:

node node_modules/portholejs/scripts/download-scrcpy-server.mjs

porthole doctor reports whether the jar is present. To bump the pinned version, update SCRCPY_VERSION and SCRCPY_SHA256 in scripts/download-scrcpy-server.mjs and keep the engine's scrcpy options class in sync (see src/engine/scrcpy-engine.ts).

npx portholejs start Pixel_8_Pro_API_34
npx portholejs start Pixel_8_Pro_API_34 Television_1080p_API_34

For a headless agent workflow:

npx portholejs start Android_TV_1080p --detach -q
npx portholejs remote dpad_down
npx portholejs screenshot -q
npx portholejs kill -q

Things to try

  • Test a TV app without touching a remote. Walk the D-pad focus to any element by its text and press OK, then read where leanback focus landed as JSON:

    porthole focus-on "Library" --select
    porthole focused -q
  • Record a bug repro as a real video while you drive the phone from the CLI. The swipe below opens the notification shade:

    porthole record repro.mp4 --duration 30s
    porthole scroll down
    porthole swipe 0.5 0.05 0.5 0.7
  • Catch visual regressions in one line. Save a baseline, then the assert exits non-zero and writes a diff image the moment the screen drifts:

    porthole screenshot -o good.png
    porthole assert-screen good.png --threshold 0.02
  • Stream a phone and a TV side by side from one command. Both devices share the same browser tab with a device picker:

    porthole start Pixel_8 Tv_1080p
  • Let an agent run the whole loop. Add the MCP server to Claude Code (below) and ask it to "open my app on the TV emulator, navigate to Settings, and check nothing crashed". It will drive the D-pad, read the UI as JSON with dump_ui, and pull get_crashes on its own.

  • If anything misbehaves, diagnose your setup in seconds:

    porthole doctor

CLI Reference

| Command | Purpose | | ------------------------------------ | --------------------------------------------------- | | porthole start [avd...] | Boot or attach one or more emulators and serve them | | porthole list | List AVDs, running emulators, and known sessions | | porthole kill [avd] | Stop emulators Porthole booted and detached servers | | porthole tap <x> <y> | Touch normalized phone coordinates from 0..1 | | porthole swipe <x1> <y1> <x2> <y2> | Swipe between normalized phone coordinates | | porthole longpress <x> <y> | Long-press normalized phone coordinates | | porthole scroll <direction> | Scroll phone content up, down, left, or right | | porthole key <keycode> | Send an Android keycode down/up pair | | porthole remote <button> | Press a TV D-pad/media button | | porthole text "<string>" | Type text into the active session | | porthole screenshot [-o file.png] | Save a PNG screenshot | | porthole assert-screen <png> | Compare the current screen with a PNG baseline | | porthole record <out.mp4> | Record the current H.264 stream to MP4 | | porthole focused | Print the currently focused UI node | | porthole focus-on <text> | Move TV D-pad focus to matching text | | porthole dump-ui [--filter text] | Dump the Android UI hierarchy | | porthole wait-for <text> | Wait until UI text appears | | porthole open-url <url> | Open a URL or Android deep link | | porthole stop-app <package> | Force-stop an app | | porthole clear-app <package> | Clear app data | | porthole rotate <orientation> | Rotate a phone emulator | | porthole emu <args...> | Pass through to adb emu | | porthole doctor | Diagnose Node/SDK/adb/AVD/session problems | | porthole mcp | Run the MCP server over stdio |

Common options:

| Option | Meaning | | ----------------------- | ------------------------------------------------------- | | -p, --port <port> | Preview/control port, default 3200 | | -d, --device <serial> | Target a running emulator serial; comma-list on start | | --host <host> | Bind address, default 127.0.0.1 | | --no-preview | Do not open the browser automatically | | --detach | Start the preview server in the background | | --mjpeg | Force MJPEG screenshot polling | | --vega | Attach to the Amazon Fire TV Vega virtual device | | -q, --quiet | Emit one JSON object/array on stdout | | --max-size <px> | Maximum scrcpy stream dimension | | --max-fps <fps> | Maximum scrcpy FPS | | --bitrate <bps> | Scrcpy video bitrate | | --wipe-data | Wipe emulator data before boot | | --no-snapshot | Disable loading/saving emulator snapshots | | --cold-boot | Alias for --no-snapshot | | --gpu <mode> | Pass emulator GPU mode | | --keep-alive | Leave a Porthole-booted emulator running on exit |

Quiet schemas are intentionally simple: list -q returns { "devices": [...], "sessions": [...] }; single-device start --detach -q returns the session record with url, pid, serial, avdName, port, and profile; multi-device detached starts return { "sessions": [...] }; input commands return { "ok": true, "session": ... }; screenshot -q returns { "ok": true, "path": "...", "session": ... }. Pass -d <serial> to port-scoped commands when a server is streaming more than one emulator.

Fire TV Vega (experimental)

Porthole can stream and control Amazon's Vega OS (Fire TV) virtual device — likely the only browser window into the Vega simulator today:

porthole start --vega

This attaches to a running Vega Virtual Device (or boots one headless via the vega CLI), streams it to the browser at ~3 fps (screenshot polling — Vega has no H.264 stream), and drives the full Fire TV remote (D-pad, OK, Back, Home, Menu, media keys) plus porthole remote, screenshot, and assert-screen headlessly.

Requirements:

  • The Vega SDK installed (~/vega/sdk/..., or point PORTHOLE_VEGA_BIN / PORTHOLE_VDA_BIN at the vega and vda binaries)
  • Developer mode enabled on the virtual device (remote input is injected through the on-device inputd-cli, exposed by the developer shell service)

Not yet available on Vega: H.264/WebCodecs streaming, touch/gesture input (TV profile), dump-ui/focused/focus-on, and MP4 recording. The exploration notes live in docs/VEGA_EXPLORATION.md.

MCP Setup

.mcp.json:

{
  "mcpServers": {
    "porthole": {
      "command": "npx",
      "args": ["portholejs", "mcp"]
    }
  }
}

Claude Code:

claude mcp add porthole -- npx portholejs mcp

Useful MCP tools: list_devices, boot_device, wait_for_boot, attach_device, tap, swipe, long_press, scroll, key, remote, type_text, screenshot, assert_screen, start_recording, stop_recording, dump_ui, get_focused, focus_on, find_element, wait_for, open_url, stop_app, clear_app, get_crashes, read_logcat, and install_apk.

Agent Skill

Porthole ships an agent skill (skills/porthole/) that teaches agents the full workflow: doctor triage, boot/attach, semantic UI assertions, TV navigation, and cleanup.

Install it by copying the skill directory into your skills folder — for the current project:

mkdir -p .claude/skills
cp -r node_modules/portholejs/skills/porthole .claude/skills/

or for all your projects:

mkdir -p ~/.claude/skills
cp -r node_modules/portholejs/skills/porthole ~/.claude/skills/

From a clone of this repository, copy skills/porthole instead. Claude Code picks the skill up automatically; ask it to "test my app on the Android emulator" and it will follow the Porthole workflow.

GitHub Action

Porthole ships an in-repo GitHub Action for CI smoke tests. It wraps reactivecircus/android-emulator-runner, starts npx --yes portholejs against the booted emulator, and runs your script while the emulator is alive.

jobs:
  tv-smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - id: porthole
        uses: giolaq/porthole@main
        with:
          avd-name: porthole-tv
          api-level: "35"
          profile: tv
          script: |
            mkdir -p artifacts
            "$PORTHOLE_CLI" focus-on "Library" --select -q -p "$PORTHOLE_PORT"
            "$PORTHOLE_CLI" screenshot -o artifacts/library.png -q -p "$PORTHOLE_PORT"

The action exposes url, serial, and port outputs, and also sets PORTHOLE_URL, PORTHOLE_SERIAL, PORTHOLE_PORT, and PORTHOLE_CLI for the smoke script. See docs/github-action-example.yml for a fuller focus-on / assert-screen workflow that uploads screenshot artifacts on failure.

Browser UI

The preview shows device tabs, stream status, screenshots, copy-to-clipboard, stream stats, logcat, drag-and-drop APK install/file push, phone hardware keys, and a TV D-pad remote. Switching tabs re-subscribes to that device's stream without reloading the page. TV sessions reject touch input server-side.

Video uses WebCodecs by default. Browsers without VideoDecoder automatically fall back to /stream.mjpeg, and --mjpeg or ?video=mjpeg forces that mode. MJPEG is implemented with shared adb screencap polling at roughly 3 fps, re-encoded server-side to downscaled JPEG (pure JS, max 800 px) to keep per-frame payloads small; it is a compatibility fallback, not a high-frame-rate stream.

When serving on a LAN with --host 0.0.0.0, Porthole prints a tokenized URL. Non-local requests must present that token.

Two capability notes:

  • porthole emu / POST /api/emu is a raw passthrough to the emulator console (adb emu) — including commands like kill. Treat it as operator-level access; anyone who can reach the (token-protected) API can use it.
  • porthole focused / get_focused reads D-pad focus, which is a TV/leanback concept. On phone profiles it usually returns null unless a view holds keyboard focus — that is expected, not an error.

Troubleshooting

| Problem | Fix | | ----------------------------- | --------------------------------------------------------------------------- | | SDK not found | Set ANDROID_HOME or ANDROID_SDK_ROOT to your Android SDK | | adb devices shows offline | Porthole runs adb reconnect offline; if it persists, restart the emulator | | Port 3200 is taken | Pass -p 3201 or another free port | | Blank video | Use a WebCodecs-capable browser and check DevTools for decoder errors | | TV taps do nothing | Use porthole remote <button>; TV profiles reject touch |

Development

npm install
npm run build
npm run test
npm run lint
npm run typecheck

Integration tests that require a booted emulator are gated behind PORTHOLE_EMU=1.