Skip to main content
Glama

reins

Give coding agents the reins: a tiny dev-only library and agent skill to drive, inspect and debug your web and Expo apps. Built for Lucid Coding, and the companion to Coordinator (mindmakr/coordinator).

Live site & docs → mindmakr.github.io/reins

An AI coding agent that tests your app usually clicks by pixels and guesses what happened. reins lets the app tell the agent what it can do and what state it is in. You register a few named actions ("log in as the tester", "switch to Arabic"), point at some state, and mark the panels worth looking at. The agent then calls those actions, reads the state, sees recent network calls and errors, and takes pixel-exact pictures of a panel. All of it goes through an MCP server on your machine.

reins is for development and test builds. Production builds swap it for an empty stub, and reins verify fails the build if any reins code is left.

  • One package (npm i -D reins) includes the core, React & Expo hooks, Vite/Next/Metro plugins, the MCP bridge (npx reins bridge) and the bundle verifier (npx reins verify).

  • The browser/RN core (reins) is under 5 KB min+gzip and has zero browser runtime dependencies.

  • Agent skill included: install the Claude Code plugin (or skill folder) and run /reins:setup—your agent inspects your repo and wires everything for you.

  • Inert until paired: it does nothing until it pairs with the bridge on your machine using a one-time token.

Let your agent set it up (/reins:setup)

In Claude Code, install the reins plugin:

/plugin install reins --marketplace mindmakr/reins

Or in two steps:

/plugin marketplace add mindmakr/reins
/plugin install reins@reins

Then in any new or existing Vite, Next.js, React or Expo project:

  • /reins:setup — inspects your project, installs reins, wires reins/vite, reins/next or reins/metro, adds dev-only reins.connect(), hooks up state adapters and high-value actions, wraps key screens in <ReinsPanel>, adds reins verify to your build script, and configures .mcp.json.

  • /reins:drive — drives your paired app through the 7 MCP tools (reins_list_actions, reins_run_action, reins_state, reins_network, reins_errors, reins_panel_box, reins_snap).

  • /reins:verify — runs your production build and verifies zero reins dev code shipped.

In OpenAI Codex CLI, Cursor or Antigravity: copy skills/reins/ into .agents/skills/ (or ~/.gemini/config/skills/) and ask the agent to follow the reins skill.

Related MCP server: browser-testbench

What's in reins

Everything ships in the single reins package (and the workspace packages @reins/* remain available if you prefer separate packages):

Import / CLI

What it does

reins

The core (< 5 KB min+gzip, 0 browser deps): actions, state, network and error buffers, panels, WebMCP.

reins/react

useReinsAction, useReinsState, <ReinsPanel id>.

reins/expo

React Native glue: connectExpo(), <ReinsPanel id> with testID, useReinsPanel, useReinsAction, useReinsState.

reins/adapters

zustand, redux, reactQuery, routerLocation, fromStore.

reins/vite

Vite plugin: serves /__reins/token in dev, swaps reins for reins/stub in production builds.

reins/next & reins/next/token-route

withReins(nextConfig) (Webpack + Turbopack) and the GET token route handler.

reins/metro (or reins/expo/metro)

withReins(metroConfig) for Expo and React Native.

npx reins bridge

The localhost (127.0.0.1:7357) WebSocket pairing hub and stdio MCP server.

npx reins verify <dir> (reins-verify-bundle)

Fails if __REINS_DEV_ONLY__ is found anywhere in a production build output folder.

Five-minute setup (by hand)

Vite + React

npm i -D reins

vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import reins from 'reins/vite';

export default defineConfig({ plugins: [reins(), react()] });

src/main.tsx:

import { reins } from 'reins';
import { zustand } from 'reins/adapters';
import { useAppStore } from './store';

if (import.meta.env.DEV) {
  reins.state('app', zustand(useAppStore));
  reins.connect(); // fetches the token from /__reins/token, then pairs
}

In a component:

import { useReinsAction, ReinsPanel } from 'reins/react';

export function Settings() {
  const { setLocale } = useI18n();
  useReinsAction({
    name: 'set-locale',
    description: 'Switch the UI language',
    input: { type: 'object', properties: { locale: { type: 'string', enum: ['en', 'ar', 'fr'] } }, required: ['locale'] },
    run: ({ locale }) => setLocale(locale),
  });
  return <ReinsPanel id="settings">…</ReinsPanel>;
}

Add the check to your build script: "build": "vite build && reins verify dist".

Next.js (App Router)

npm i -D reins

next.config.mjs:

import { withReins } from 'reins/next';
export default withReins({ /* your config */ });

app/api/reins-token/route.ts:

export { GET } from 'reins/next/token-route';
export const dynamic = 'force-dynamic';

A client component mounted once, for example in your root layout:

'use client';
import { useEffect } from 'react';
import { reins } from 'reins';

export function Reins() {
  useEffect(() => {
    if (process.env.NODE_ENV !== 'production') reins.connect({ tokenUrl: '/api/reins-token' });
  }, []);
  return null;
}

Check the output: "build": "next build && reins verify .next/static".

Expo

npx expo install reins

metro.config.js:

const { getDefaultConfig } = require('expo/metro-config');
const { withReins } = require('reins/metro');
module.exports = withReins(getDefaultConfig(__dirname));

App.tsx:

import { connectExpo, ReinsPanel, useReinsAction } from 'reins/expo';

if (__DEV__) connectExpo();

export default function App() {
  useReinsAction({ name: 'open-cart', description: 'Open the cart screen', run: () => navigation.navigate('Cart') });
  return <ReinsPanel id="home">…</ReinsPanel>;
}

The bridge only listens on 127.0.0.1. The iOS simulator shares your machine's loopback, so that just works. For an Android emulator or a USB device, run adb reverse tcp:7357 tcp:7357. Phones on Wi-Fi cannot reach the bridge, on purpose.

Check the export: npx expo export && npx reins verify dist.

NOTE

reins/metro loads ESM with require, which needs Node 20.19 or newer, or 22.12 or newer. Metro must have package exports turned on, which is the default from Expo SDK 53.

Run the bridge and connect your agent

Add reins as an MCP server in your agent. For example, in .mcp.json (Claude Code) or your agent's MCP settings:

{
  "mcpServers": {
    "reins": { "command": "npx", "args": ["reins", "bridge"] }
  }
}

The agent starts the bridge for you. You can also start it yourself with npx reins bridge --no-mcp to watch it. The bridge writes a fresh token to .reins/token. Your dev server serves that file to the page, and the page pairs.

Options: --port, --dir, --origin <exact origin> (extra allowed page origins), --cdp <url> (for reins_snap in a browser), --no-rotate, --no-mcp, --browser-only.

How an agent uses it

Tool

What it returns

reins_list_actions

The paired app, its actions with input schemas, state sources and panels. Call this first.

reins_run_action

Runs one action: { name, input, confirm? }.

reins_state

A redacted snapshot of one state source or all of them.

reins_network

Recent fetch and XHR calls: method, URL, status, time, redacted headers and bodies.

reins_errors

Recent console errors, uncaught errors and unhandled rejections.

reins_panel_box

A panel's box in CSS pixels, with devicePixelRatio.

reins_snap

A pixel-exact PNG of a panel under .reins/snaps/, and optionally a copy on the clipboard.

A typical loop: list actions, run login-as-tester, run set-locale with ar, check reins_errors is empty, then reins_snap the checkout panel and compare it with the design.

reins_snap in a browser uses Playwright, if it is installed, over the Chrome DevTools Protocol. Start Chrome with --remote-debugging-port=9222 and the bridge with --cdp http://127.0.0.1:9222. On Android it captures the screen with adb and returns the panel's box in device pixels. Otherwise it returns the box, so the agent's own browser tool can clip a screenshot to it.

People get the same handle in DevTools once the page is paired: __reins__.actions(), __reins__.run('set-locale', { locale: 'ar' }), __reins__.state(), __reins__.network(). Each action run shows as a collapsed console group and a performance.measure entry in the Performance panel.

If the browser supports WebMCP (navigator.modelContext or document.modelContext), reins also registers each action as a WebMCP tool. Read-only actions get readOnlyHint. Consequential ones get destructiveHint and a required confirm input.

State adapters

import { reins } from 'reins';
import { zustand, redux, reactQuery, routerLocation } from 'reins/adapters';

reins.state('cart', zustand(useCartStore));
reins.state('store', redux(store));
reins.state('queries', reactQuery(queryClient));
reins.state('route', routerLocation()); // or routerLocation(() => router.state.location)
reins.state('anything', () => ({ step, total }));

Pairing also adds location, localStorage, sessionStorage and cookies. Storage and cookies list key names only. Their values are redacted unless allowlisted.

Companion: Coordinator (Lucid Coding)

Coordinator (mindmakr/coordinator) asks every open question up front, agrees a testable bar, and runs multi-step work through isolated builders and independent blind critics until the bar is met.

reins is built to be Coordinator's hands and eyes inside your app:

  • Builders wire features along with useReinsAction, reins.state and <ReinsPanel id>.

  • Blind visual & code critics (who never see the builder's reasoning) use reins_run_action to switch locales (en, ar RTL) and themes, check reins_state, reins_network and reins_errors, and attach reins_snap PNGs as durable proof on the Coordinator run dashboard.

Install both in Claude Code:

/plugin install coordinator --marketplace mindmakr/coordinator
/plugin install reins --marketplace mindmakr/reins

Read the Lucid Coding Manifesto.

Security model

reins gives an agent real control of a running app, so the limits are enforced in code and covered by tests:

  1. Kept out of production. The core only turns on when __DEV__, import.meta.env.DEV or NODE_ENV !== 'production' says it is a dev build. The Vite, Next and Metro plugins swap reins for an empty stub in production builds. The core carries the marker __REINS_DEV_ONLY__, and reins verify (reins-verify-bundle) fails your build if it finds it. A staging build keeps reins only when built with REINS_ENABLE=1.

  2. Off until paired. Until the bridge accepts its token, the core adds no listeners and patches nothing. The bridge makes a random 192-bit token, writes it to .reins/token (with its own .gitignore) and renews it after each pairing. It listens on 127.0.0.1 only, which cannot be changed. It rejects WebSocket origins that are not localhost or on your --origin list. Clients with no Origin, like React Native, still need the token, and --browser-only refuses them.

  3. Redacted by default. Request and response bodies, Authorization, Cookie and Set-Cookie headers, storage values, cookie values, sensitive query values and sensitive state keys (token, password, secret, session and so on) are replaced with [redacted]. Allowlist keys with reins.connect({ allow: { headers, bodyKeys, storageKeys, cookies, queryKeys, stateKeys } }). httpOnly cookies cannot be read by page code at all.

  4. Consequential actions need confirmation. An action marked consequential: true is refused unless the call says confirm: true.

  5. Everything is logged. Every call shows in the browser console. The bridge appends every agent call, pairing, refusal and DevTools or WebMCP call to .reins/audit.log, with arguments redacted.

  6. Lazy. connect() waits for load and an idle moment before it does anything.

  7. No eval. Only actions the app registered can run.

See SECURITY.md for what reins does not protect against and how to report a problem.

Prior art and how reins differs

  • Playwright MCP drives a browser from the outside through the accessibility tree. reins works from inside the app: the agent calls the app's own named actions and reads its real state. They work well together, and reins_snap can use Playwright to take its pictures.

  • Chrome DevTools MCP gives an agent DevTools: network, console, performance and screenshots. It sees everything, unredacted, and it knows nothing about your app's own flows. reins exposes only what the app chooses to, redacted, and it also works in React Native.

  • WebMCP is a proposed browser API for pages to offer tools to agents. reins registers its actions there when the browser supports it, and it also reaches agents that are not in the browser through the bridge.

  • React DevTools shows a person the component tree and its state. reins gives an agent a small, stable interface chosen by the app instead of the whole tree.

Contributing

See CONTRIBUTING.md. For coding agents adding reins to an app, see AGENTS.md and skills/reins/SKILL.md.

Licence

MIT © Asif Raja, by mindmakr

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers