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

@avasapp/agent-bridge

v0.1.0

Published

Let coding agents drive a running React Native app directly: seed state, flip flags, navigate and check the screen in about a millisecond per call, with no taps.

Readme

@avasapp/agent-bridge

Let coding agents drive a running React Native app directly. Seed data, flip flags, navigate and check the screen in milliseconds, without tapping through the app.

Demo

An agent hides a tab, turns on dark mode, fills in a form, seeds data, checks the screen and undoes it all

example/flows/demo.mjs driving the example app in Expo Go, all on one Mac:

| Call | Round trip | | --- | --- | | bridge.ping | 1–4 ms | | screen.findText, screen.waitFor (already there) | 3–9 ms | | screen.fill, screen.press | 20–65 ms, render included | | query.pin, store.call, router.navigate | 11–44 ms, render included | | 14 steps, no pauses | 0.8 s wall, 0.4 s of it waiting on a save |

14 steps, all local on one Mac:   0.8 s wall with no pauses
same flow from another machine:   ~57 ms per call (network)
same screen check via a11y tree:  450–970 ms

Works on Expo (dev-tools socket) and on bare React Native (CDP over Metro). Dev builds only: release builds get empty stubs.

Install

bun add -d @avasapp/agent-bridge   # or npm / yarn / pnpm

In the app

Mount the hook in a file that only runs in development. You choose every tool; adapters for common libraries are one import away.

import { cdpTransport, useAgentBridge } from '@avasapp/agent-bridge'
import { expoTransport } from '@avasapp/agent-bridge/expo'
import { queryTools } from '@avasapp/agent-bridge/tanstack-query'
import { storeTools } from '@avasapp/agent-bridge/zustand'
import { mmkvTools } from '@avasapp/agent-bridge/react-native-mmkv'
import { routerTools } from '@avasapp/agent-bridge/expo-router'
import { networkTools } from '@avasapp/agent-bridge/network'
import { router, useNavigationContainerRef } from 'expo-router'

export function AgentBridge() {
  const queryClient = useQueryClient()
  useAgentBridge({
    name: 'my-app',
    transports: [expoTransport(), cdpTransport()],
    tools: {
      ...queryTools(queryClient),
      ...storeTools({ settings: useSettingsStore, auth: useAuthStore }),
      ...mmkvTools({ storage }),
      ...routerTools(router, { navigation: useNavigationContainerRef() }),
      ...networkTools(),
      // Your own tools: any function, JSON in and out.
      'auth.signIn': (session) => signInWith(session),
    },
  })
  return null
}

Every app also gets:

  • screen.snapshot: buttons, inputs, text and testID views on screen, with positions.
  • screen.fill, screen.press: call an input's or button's own handlers, found by testID, label, placeholder or text, and return once React has rendered the result.
  • screen.waitFor, screen.findText: wait for, or check, text or a target on screen.
  • bridge.restore: undo what the agent changed, by running every *.restore tool.
  • bridge.logs, bridge.ping, bridge.tools.

Custom tools that change the screen can await settle() (from @avasapp/agent-bridge) so the next check sees the render.

Errors come back on their own: each reply carries what the app logged with console.error, threw or left unhandled since the previous reply, tagged with the call it happened during or after.

From the agent

npx agent-bridge session start            # hold one connection; call/tools/run reuse it
npx agent-bridge tools
npx agent-bridge call query.pin '[["features"], {"beta": false}]'
npx agent-bridge call screen.press '"add-plant"'
npx agent-bridge call screen.fill '["plant-name", "Fiddle leaf fig"]'
npx agent-bridge call screen.waitFor '"Name is required"'
npx agent-bridge run flows/add-plant.mjs --strict   # fail if the app logged an error
npx agent-bridge session stop             # runs bridge.restore, then disconnects

A session stops itself, restore included, after 15 minutes without calls (--idle), and reconnects if the app reloads.

import { connect } from '@avasapp/agent-bridge/client'

const app = await connect({ metro: 'localhost:8081' })
await app.call('router.navigate', '/add')
await app.call('screen.fill', 'plant-name', 'Fiddle leaf fig')
await app.call('screen.press', 'save-plant')

A flow is a module the CLI runs without a model in the loop:

export default async ({ step }) => {
  await step('flag: beta off', 'query.pin', ['features'], { beta: false })
  await step('save empty form', 'screen.press', 'save-plant')
  await step('error shown', 'screen.waitFor', 'Name is required')
  await step('undo', 'bridge.restore')
}

No device tool is needed. Pair one (such as agent-device) with the bridge for what it can't reach: system alerts, permission prompts, the keyboard, screenshots, and one real tap per flow.

Adapters

| Import | Tools | Needs | | --- | --- | --- | | @avasapp/agent-bridge/tanstack-query | query.list get set pin unpin unpinAll refetch invalidate restore | your QueryClient | | @avasapp/agent-bridge/zustand | store.list get set call restore | your stores | | @avasapp/agent-bridge/react-native-mmkv | mmkv.list keys get set delete restore | your MMKV instances | | @avasapp/agent-bridge/expo-router | router.navigate push replace back current | router and useNavigationContainerRef() from expo-router | | @avasapp/agent-bridge/network | net.log mock mocks unmock clear restore | nothing: patches fetch and XMLHttpRequest in dev |

A pin keeps seeded data in place through refetches until you unpin it. net.mock('/inbox', { status: 500 }) or { offline: true } fails a route; apps can fake a whole backend with mockRequests from the same import.

Transports

| | Expo dev-tools socket | CDP | | --- | --- | --- | | Round trip, local | 0.5 ms | 1.3 ms | | Emoji in payloads | yes | yes (escaped for you) | | Works with | Expo CLI | any RN app on Metro |

connect() tries Expo first and falls back to CDP.

Release builds

Every entry point is gated on process.env.NODE_ENV, like react/index.js: Metro inlines it and a release bundle gets empty stubs. Check a bundle in CI:

npx agent-bridge assert-absent path/to/main.jsbundle

Traps we hit

  • Run the agent on the machine with the simulator. Every call pays the network otherwise.
  • Hidden tabs stay mounted. screen.findText skips anything under an inactive RNSScreen.
  • Expo checks the debugger's Origin against the host Metro advertises and drops mismatches silently. The client reads it from the manifest.
  • Expo's socket broadcasts to every app. Calls are addressed to one device; pick it with --device when several are connected.
  • Screen checks through the accessibility tree are slow (hundreds of ms each). Check in-app, and keep one real UI check per flow.
  • screen.fill skips the keyboard. It runs the input's handlers, so validation and state are real, but autocorrect, native maxLength and uncontrolled inputs' native text are not.
  • Keep your QueryClient in state (useState(() => new QueryClient())). Created at module level, a Fast Refresh can leave the bridge holding a different client than the screen.

License

MIT © Avas Enterprises