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

@synccalm/agent

v0.1.0

Published

Claude-facing control layer for Android emulators over MCP — build, launch, inspect, click, type, test and verify your app from a conversation.

Readme

@synccalm/agent

Let Claude drive an Android emulator. An MCP server that turns a running emulator into something a model can operate directly — build the app, launch it, read the screen, tap, type, run a flow, and assert the result.

@synccalm/agent
        │
        └── Claude-facing control layer
            ├── build      gradle assemble → APK
            ├── launch     install + start + report the first screen
            ├── inspect    read the screen as structured elements
            ├── click      tap by label, id or accessibility name
            ├── type       enter text into a field
            ├── test       run a scripted flow, step by step
            └── verify     assert what is (or isn't) on screen

Why the screen is read, not looked at

inspect returns the accessibility tree, not a picture:

{
  "app": { "package": "com.example.app", "activity": ".MainActivity" },
  "elements": [
    { "id": "n15", "class": "EditText", "hint": "Email",
      "resourceId": "com.example.app:id/email", "editable": true,
      "bounds": [126, 63, 1080, 252], "center": [603, 158] },
    { "id": "n22", "class": "Button", "text": "Sign in",
      "clickable": true, "center": [540, 1420] }
  ]
}

That matters more than it first looks. It is exact rather than inferred, it costs a fraction of the tokens a screenshot does, it works on a headless emulator, and every element comes with the selector you use to act on it. screenshot is still there for when appearance itself is the question.

Install

npm install -g @synccalm/agent

Register it with Claude Code:

claude mcp add synccalm-agent -- npx -y @synccalm/agent mcp

Or add it to .mcp.json by hand:

{
  "mcpServers": {
    "synccalm-agent": {
      "command": "npx",
      "args": ["-y", "@synccalm/agent", "mcp"]
    }
  }
}

Then ask Claude to check the setup — it will call doctor, which reports the SDK location, attached devices and the AVDs available to boot.

Requirements

  • Node 18+
  • Android SDK with platform-tools (adb) and emulator, found via ANDROID_HOME, ANDROID_SDK_ROOT, or the default install path
  • At least one AVD, created in Android Studio's Device Manager

Addressing elements

Every screen-facing tool takes the same selector fields, so there is one addressing scheme to learn rather than one per tool:

| Field | Matches | | --- | --- | | text | the visible label — exactly first, then case-insensitively | | textContains | a substring of the label | | resourceId | pkg:id/name, the bare name, or a substring | | contentDesc | the accessibility label, for icon-only controls | | className | a widget class substring, e.g. EditText | | index | the nth match, when a selector legitimately matches several |

Two behaviours are worth knowing:

Ambiguity is an error, not a guess. A selector matching four elements returns all four and asks you to narrow it, rather than silently tapping the first. Add index when several matches are expected.

Taps route to the clickable ancestor. Matching a TextView inside a list row taps the row, which is what a person would have hit.

Running a flow

test replays a user journey in a single call instead of a dozen round trips, and stops at the first failure — every step after a broken one would be acting on the wrong screen anyway.

{
  "name": "sign-in",
  "steps": [
    { "action": "launch", "package": "com.example.app" },
    { "action": "type", "className": "EditText", "index": 0, "text": "[email protected]" },
    { "action": "type", "className": "EditText", "index": 1, "text": "hunter2" },
    { "action": "tap", "text": "Sign in" },
    { "action": "verify", "text": "Welcome back", "timeoutMs": 8000 }
  ]
}

The report names the step that failed, how long each took, and what was on screen when it did.

In a type step, text is the content to enter — target the field with resourceId, className or index, not text.

Tools

Environmentdoctor, use_device, launch_emulator App lifecyclebuild, install, launch, stop_app, list_packages Screeninspect, screenshot Interactionclick, type, scroll, press_key Assertionsverify, test Diagnosticslogs

build detects React Native, Expo-prebuilt and native Android projects, runs the Gradle assemble task, and returns the APKs it produced. On failure it returns the part of the Gradle output that explains why, not the whole log.

CLI

The same code paths are drivable from a terminal, which is the quickest way to check a setup:

synccalm-agent doctor
synccalm-agent emulator Medium_Phone_API_36.0
synccalm-agent launch com.example.app
synccalm-agent inspect
synccalm-agent click "Sign in"
synccalm-agent screenshot shot.png

Known limitations

Screens that never go idle cannot be dumped. uiautomator waits for the window to stop changing before it captures, and gives up after about twelve seconds. A spinner, a video, a progress bar or live-refreshing text will defeat it — inspect then reports exactly that, and screenshot still works. Booting through launch_emulator turns off window animations, which removes the common transition-related case.

Typing is ASCII-only. Android's input text bridge cannot deliver non-ASCII characters; anything dropped comes back in warnings rather than failing silently.

Canvas-drawn UIs expose no tree. Apps rendering through a SurfaceView — Flutter, games, video players — have nothing for inspect to read. Use screenshot and coordinate taps.

iOS is not implemented yet. The device layer is deliberately separated from the control layer so a simctl-backed driver can slot in behind the same tools.

Programmatic use

const { adb, actions, verify, flow } = require('@synccalm/agent');

const serial = await adb.resolveSerial();
await actions.click(serial, { text: 'Sign in' });

const result = await verify.verify(serial, { text: 'Welcome back', timeoutMs: 8000 });
const report = await flow.runTest(serial, [{ action: 'tap', text: 'Settings' }]);

License

MIT