Cordierite exists so developers, QA, and automation can drive registered tools and influence in-app state from a CLI or agent host - without shipping hidden debug screens, secret gestures, or admin panels inside the app. The app exposes only the tool surface you define in code; control stays on the other end of a pinned wss:// session you initiate when it makes sense (local desk, CI, VPN, or a host on the internet).
Shipping ad-hoc debug UIs in production builds is risky: they leak intent, widen attack surface, and are hard to gate consistently. Cordierite inverts that: production-capable builds can still participate in Cordierite when a trusted host is available, because trust is not “anyone on Wi‑Fi” or “whoever crafted a link” - it is TLS + SPKI pinning to identities you embed, plus short-lived session bootstrap so deep links are hints, not proof of authority. The same channel works for human operators (CLI), test automation, and agents.
- No backdoor UI: nothing extra in the app UI for attackers to discover; capability is tool APIs + transport, not mystery menus.
- Encrypted transport:
wss://end-to-end; no cleartext control traffic on the wire. - Pinned server identity: the native client matches your host’s public key (SPKI); IP, DNS, and deep-link origin are not enough to impersonate the host.
- Session bootstrap: one-time, session-bound channel after claim - appropriate for production when pins and provisioning match your threat model.
- Current local control limitation: once a Cordierite host is running, its local control API is currently unauthenticated. Any process in the same operating system that can reach the local control port may talk to Cordierite and invoke tools on the connected app. This is a known limitation today.
| Package | Role |
|---|---|
cordierite |
CLI and host tooling |
@cordierite/shared |
Shared library (CLI + React Native) |
@cordierite/react-native |
TurboModule client + optional Expo config plugin |
Clone the repo and install with your usual workspace workflow. The playground is the reference dev app.
Cordierite has two sides:
- the host side, where you run the
cordieriteCLI - the app side, where your React Native app imports
@cordierite/react-nativeand registers tools
Install the CLI where the operator, test runner, or agent will run it:
npm install cordieriteInstall the React Native package in your app:
npm install @cordierite/react-native zodGenerate a TLS private key for the host:
cordierite keygenThe command prints a sha256/... SPKI fingerprint. Copy that value into the app configuration as a trusted Cordierite pin.
For Expo, add the config plugin and the generated pin to app.json / app.config.*:
{
"expo": {
"scheme": "myapp",
"plugins": [
[
"@cordierite/react-native",
{
"cliPins": ["sha256/REPLACE_WITH_KEYGEN_OUTPUT"],
"allowPrivateLanOnly": true
}
]
]
}
}For bare React Native, configure the equivalent native keys:
- iOS
Info.plist:CordieriteCliPinsand optionallyCordieriteAllowPrivateLanOnly - Android
<application>meta-data:com.callstackincubator.cordierite.CLI_PINSand optionallycom.callstackincubator.cordierite.ALLOW_PRIVATE_LAN_ONLY
Your app also needs a URL scheme, and that scheme must match the one you pass to cordierite host --scheme ....
After changing native configuration, rebuild the app. For Expo, use a development build or bare native app. Expo Go is not enough.
Import @cordierite/react-native in the app entry point so the package installs its deep-link bootstrap listener as soon as the app starts:
import "@cordierite/react-native";This import should happen in the JS entry file or another module that is guaranteed to load during app startup.
Register tools from app startup code. A small example:
import "@cordierite/react-native";
import { useEffect } from "react";
import { registerTool } from "@cordierite/react-native";
import { z } from "zod";
export function CordieriteBootstrap() {
useEffect(() => {
const sumTool = registerTool({
name: "sum",
description: "Add two numbers inside the app.",
inputSchema: z.object({
a: z.number(),
b: z.number(),
}),
outputSchema: z.object({
total: z.number(),
}),
handler: async ({ a, b }) => ({
total: a + b,
}),
});
return () => {
sumTool.remove();
};
}, []);
return null;
}Mount that component near app startup, or register tools from another early-loading module. Cordierite only exposes the tools you register.
Run the host with the private key from cordierite keygen and your app scheme:
cordierite host --tls-key ./dev-key.pem --scheme myappThe host prints a bootstrap deep link and session details. Open that deep link in the app. On macOS simulator you can also use --open.
Once the app claims the session, use the returned session_id with the CLI:
cordierite tools --session-id <session_id>
cordierite invoke sum --session-id <session_id> --input '{"a":2,"b":3}'If setup is correct, the app connects over pinned wss://, the host lists the registered tools, and invoke returns the tool result.
- CLI / host: any modern JavaScript runtime that can run the published package and open TLS sockets.
- React Native: iOS and Android with New Architecture; web is a safe stub only.
cordierite 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 hello@callstack.com 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! 🔥