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

@appduct/react-native

v0.11.1

Published

Expo native client for Appduct sessions

Readme

Appduct

Drive app tools without shipping debug UI

MIT license npm downloads PRs Welcome

@appduct/react-native is the app-side client for Appduct. Your app registers tools in JavaScript; a CLI, MCP client, or test suite invokes them once the app opens a bootstrap link and completes a pinned wss:// handshake — no debug menu required.

Requirements

You need a development build or a bare React Native app — Expo Go can't load native code, so it can't run Appduct.

Getting started

1. Install

The app-side package plus a schema library:

npm install @appduct/react-native zod

The CLI, on the machine running the host:

npm install -g appduct

2. Nothing to configure yet

No key, no pins, and no config plugin are needed for a first run, in any build type. The daemon auto-generates a key on first start, and appduct link carries its sha256/... fingerprint on the deep link for the app to trust for that session.

Wire your deep-link scheme so the OS can open the app with that link. For an Expo app that's all: appduct link reads expo.scheme straight out of app.json. Otherwise (a dynamic app.config.js, which Appduct never executes, or bare React Native) name it with appduct init --scheme <s>, --scheme, or APPDUCT_SCHEME — the CLI README has the full resolution order. To make a build trust only pins you embedded ahead of time, see Configuring trust.

By default the native module ships in debug builds only: a release build has none, so the API is inert and connect() rejects with appduct_disabled (see Build variants).

3. Import Appduct in the JS entry point

import "@appduct/react-native/auto";

/auto is the only entry that installs anything: the deep-link bootstrap listener and session recovery. To control when it installs — in __DEV__, behind a QA toggle — require() it there instead:

if (__DEV__) {
  require("@appduct/react-native/auto");
}

The default flow needs no Linking handler of your own, and sessions survive Metro reloads and network flaps — see ARCHITECTURE.md §11 for lease, resume, and reconnect rules.

If you drive bootstrap yourself and never import /auto, call restoreSession() before your own bootstrap handling — it's then the only reader of the native resume lease.

4. Define tools in app startup code

Call registerTool({ ... }) with inputSchema/outputSchema values and a handler. Zod v4 works out of the box — its JSON Schema exporter is what lets agents see a real tool shape. A library without one (zod 3, plain valibot) needs a { schema, jsonSchema } pair, and a raw JSON Schema object works with no validation library at all — see Accepted schema forms.

useAppductTool wraps registerTool in a useEffect, so registration follows the component's lifecycle, remounts and Fast Refresh included:

import "@appduct/react-native/auto";
import { useAppductTool } from "@appduct/react-native";
import { z } from "zod";

export function AppductBootstrap() {
  useAppductTool(
    {
      name: "sum",
      description: "Add two numeric values",
      inputSchema: z.object({ a: z.number(), b: z.number() }),
      outputSchema: z.object({ total: z.number() }),
      handler: async ({ a, b }) => ({ total: a + b }),
    },
    []
  );

  return null;
}

Mount it near app startup, or register from a module that loads then. The host can only invoke tools your app already registered.

The hook registers once per mount and re-registers only when the registration itself changes, routing every call through the latest render's handler — so deps is an optional override, not something each call site has to get right. See Registration is per mount, not per render.

Make inputSchema accept an object: a call's arguments are always a JSON object — see Make the input schema accept an object. A call gets 10 seconds unless the registration declares timeoutMs — see Long-running tools.

To keep a destructive tool out of some build variants, pass { enabled } rather than wrapping the hook in an if — see Gating a tool by build variant.

An agent picks a tool from one signature line and the first line of its description, so name tools by intent, set annotations (readOnlyHint, destructiveHint), declare an outputSchema, and describe each parameter — see Designing tools for agents.

5. Start the daemon and test the flow

appduct auto-spawns its daemon. link needs your app's deep-link scheme: pass --scheme (matching expo.scheme), or set scheme once in ~/.appduct/config.json:

appduct link --scheme myapp --qr

Scan the QR (or open the link) in the app, then list and invoke tools:

appduct tools
appduct invoke sum --input '{"a":2,"b":3}'

Omit the session selector when only one session is active; pass an alias or session id when several are (appduct ls).

API reference

Entry points

| Entry | Behavior | | --- | --- | | @appduct/react-native | Side-effect-free. The native module is looked up lazily, on the first native call, so importing it (even in Expo Go) never crashes. | | @appduct/react-native/auto | Same exports plus one side effect: installs the deep-link bootstrap listener and starts lease recovery — the only entry that installs anything. | | @appduct/react-native/noop | Same public API, fully inert — for compiling Appduct out of production builds. | | @appduct/react-native/metro | withAppduct(config, { include }) — swaps the real entries for /noop at bundle time. |

Exports

| Export | Signature / notes | | --- | --- | | registerTool | ({ name, description, inputSchema?, outputSchema?, annotations?, timeoutMs?, group?, handler }){ remove() }. The disposer removes only its own registration. group ("cart", or a subgroup like "checkout/payment") lets agents list your tools one area at a time — see Group tools in a large app. | | createToolGroup | (group) → a registerTool that puts every tool it registers in group. | | useAppductTool | (definition, deps?, { enabled? }). Registers once per mount, re-registering only when the descriptor changes; deps overrides that derivation. enabled defaults to true; false never registers, and removes any registration that hook owns. | | handler | (args, context). context.signal is an AbortSignal, aborted when the caller cancels or the connection drops mid-call. Forward it (fetch(url, { signal })), check signal.aborted, or listen for "abort" — ignoring it is fine, the handler replies normally. | | postEvent | (name, payload?) — pushes an app event, read by appduct events and the MCP event tools. | | addAppductListener | (kind, callback){ remove() }. Kinds "stateChange", "sessionChange", "error" — the last one is a unified channel for bootstrap-parse, connect, socket, and tool-handler failures. | | getRegisteredTools | → ToolDescriptor[], the current registry. | | getAppductState | → the client's connection state ("idle" with no session). | | restoreSession | → Promise<boolean>. Recovers the native resume lease; also on appductClient. | | connect | (input)Promise<void>. Claims a parsed bootstrap payload; rejects with AppductDisabledError (code: "appduct_disabled") when native is absent. | | parseBootstrapUrl | Parses a v2 bootstrap deep link (and its sibling pin param) for connect. | | getAppductBuildConfig | → { trust, hasEmbeddedPins, allowPrivateLanOnly }, this build's effective trust configuration. |

Platform compatibility

| Platform | Support | | --- | --- | | iOS | 15.1+ (Appduct.podspec), New Architecture | | Android | Autolinked, New Architecture | | Web | Stub only |

Going further

Made with ❤️ at Callstack

appduct is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. Callstack is a group of React and React Native geeks, contact us at [email protected] if you need any help with these or just want to say hi!

Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥