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

@gravitylabsllc/porthole

v0.2.2

Published

MCP server and live timeline for Porthole: query a running Android app for recomposition counts, frame timing, main-thread stalls, navigation, state, in-flight work and logs, and capture and query Perfetto system traces.

Downloads

32

Readme

@gravitylabsllc/porthole

The workstation half of Porthole: an MCP server and a live timeline for a running Android app.

Porthole is a debug-only agent that lives inside your app and answers questions about it while it runs — what recomposed and why, which frames were dropped and where the time went, what is holding the main thread, what is in flight, what your state actually contains right now. This package is what connects to it: a browser timeline for a person watching their own app, and an MCP server for a coding assistant that can read the answers and go change the code that caused them.

The agent itself is an Android library. It is not in this package, and nothing here returns data until it is running: add the Gradle plugin live.gravitylabs.porthole to your app module, install a debug build, and then come back here. Setup is in the repository README.

The timeline

npx @gravitylabsllc/porthole ui

That forwards the device port with adb, serves the timeline, opens your browser and keeps running until you stop it. It needs Node 18 or newer and adb — from ANDROID_HOME, a local.properties above the working directory, or your PATH.

The MCP server

./gradlew portholeMcpConfig writes this entry for you, merging rather than overwriting. To paste it into .mcp.json yourself:

{
  "mcpServers": {
    "porthole": {
      "command": "npx",
      "args": ["-y", "@gravitylabsllc/porthole"],
      "env": { "PORTHOLE_PORT": "8677" }
    }
  }
}

The tools it registers:

| tool | answers | | --- | --- | | findings | start here: what is wrong right now, ranked, each with the tool that shows its evidence | | recompositions | which composables recomposed, how often, and which state keys were written just before | | semantics_tree | the semantics tree with an id that stays stable across captures | | nav_state | back stack, arguments on each entry, and the deep link that got you here | | state | current values of your ViewModel state, named automatically | | inflight | open HTTP calls with the phase each is stuck in, running queries, WorkManager jobs | | frames | dropped frames, and which phase of the frame ate the time | | blocking | what held the main thread, with the stack it was stuck in | | logs | the app's own logcat output, stack traces intact, without touching adb | | timeline | the raw event stream, for ordering things relative to each other | | what_was_happening | the narrative for one instant: screen, in-flight work, main thread, state just written | | system_context | thermal state, CPU governor, busiest processes, memory pressure | | capture_system_trace | records a Perfetto trace, annotated with the app's own spans | | ask_system_trace | puts a fixed set of questions to a captured trace, to rule causes in or out | | open_timeline | opens the live timeline in the browser | | porthole_status | whether any of the above can currently reach the device |

The two binaries

porthole is the CLI, for a person:

porthole ui                                       open the live timeline
porthole capture --scenario <name> -- <command>   record a run to a trace
porthole report <trace.json>                      what the run is worth looking at
porthole compare <base> <trace>                   regressions against a baseline
porthole mcp                                      the MCP server (stdio)

porthole-mcp is that last line as its own entry point, for MCP clients that want a bare command rather than a subcommand. The two are the same server.

A bad --port/--ui-port value or an unrecognized option exits 2 rather than starting something that listens on the wrong thing and says nothing. Quoted from the built CLI itself:

$ porthole ui --port abc
--port "abc" is not a number

$ porthole ui --port 99999
--port 99999 is out of range (must be 1024-65535)

$ porthole ui --port 4.5
--port 4.5 must be a whole number

$ porthole ui --port
--port needs a port number

$ porthole ui --bogus-flag
unknown option: --bogus-flag
<usage follows>

Environment

| variable | default | what it sets | | --- | --- | --- | | PORTHOLE_HOST | 127.0.0.1 | host the forwarded device socket is reachable on | | PORTHOLE_PORT | 8677 | device port the porthole listens on | | PORTHOLE_UI_PORT | 8678 | port the timeline is served on | | PORTHOLE_TRACE_PROCESSOR | none | path to Perfetto's trace_processor, for system traces | | PORTHOLE_TRACE_TIMEOUT_MS | 60000 | how long ask_system_trace waits on trace_processor per question before giving up |

The MCP server and the timeline are independent. Run either, or both at once — they each open their own connection to the device.

More

License

Apache License 2.0. Copyright 2026 Gravity Labs.