reins Hands and eyes inside your app for coding agents. Built for Lucid Coding.
- < 5 KB min+gzip
- Zero bytes in prod
- Web + Expo
- MCP + WebMCP
Stop making agents click by pixels and guess what happened. Register named actions, expose redacted state, and mark the panels worth snapping—all in one package (npm i -D reins) with an agent skill that wires it for you.
- Inside the app, not outside. The agent calls your app's real functions and reads its real stores instead of scraping DOM nodes.
- Inert until paired. Nothing is patched and no listener is added until the app presents the one-time token from
.reins/token. - Guaranteed out of production. Build plugins swap
reinsfor a no-op stub, andreins verifyfails the build if__REINS_DEV_ONLY__leaks.
Why not just let the agent click by pixels?
Browser automation tools see the DOM from the outside. reins lets your app declare a small, stable contract from the inside—and pairs cleanly with Playwright when you want both.
| Capability | Pixel clicking / Raw DevTools MCP | Browser WebMCP alone | reins |
|---|---|---|---|
| Reaching a screen state (e.g. Arabic + logged in + 3 cart items) | Clicks through 6 screens, types passwords, waits on spinners, breaks when copy changes. | Runs registered page tools if the browser supports navigator.modelContext. |
One reins_run_action call with schema-validated JSON input. Also registers on WebMCP automatically. |
| Reading app state & network | Dumps raw DOM or unredacted network traffic, including auth cookies and tokens. | No built-in state snapshots, network ring buffer or error log. | Named snapshots (zustand, redux, reactQuery) + network & error buffers, redacted by default. |
| Visual panel snapshots & DOM/CSS layout | Full-page screenshots or brittle CSS selector crops; guesses clipping from pixels. | Not supported. | <ReinsPanel id="checkout"> gives pixel-exact PNG snaps + reins_dom computed CSS and automatic horizontal-clip / vertical-clip / zero-size flags. |
| Performance & lifecycle timeline | Manual trace files or wall-clock guesses. | Not supported. | reins_perf reports TTFB, FCP, DOM interactive/load, component mount times & render counts, API latency stats, and a unified millisecond timeline. |
| React Native & Expo | Requires separate native UI hierarchy scrapers. | Browser only. | First-class reins/expo and reins/metro over 127.0.0.1:7357. |
| Production safety | Test hooks or globals often ship to production by accident. | Up to you to strip. | Build plugins swap reins for a stub + reins verify fails CI if any dev marker remains. |
One package. Or let your agent wire it in one command.
Everything—core, React & Expo hooks, state adapters, Vite/Next/Metro plugins, the MCP bridge, and the bundle verifier—ships in a single dev dependency: npm i -D reins.
Recommended Let the agent set it up
Install the reins plugin in Claude Code (or add the skill folder to Codex / Cursor / Antigravity) and run /reins:setup. The agent inspects your stack, installs reins, wires the bundler plugin, connects dev mode, wraps key screens in <ReinsPanel>, and registers the MCP server.
/plugin marketplace add mindmakr/reins
/plugin install reins@reins
/reins:setup
On Claude Code 2.1.275+: /plugin install reins --marketplace mindmakr/reins
Install the single npm package + MCP server
Prefer to wire it by hand? Install reins once and add npx reins bridge to your project's .mcp.json:
npm i -D reins
{
"mcpServers": {
"reins": { "command": "npx", "args": ["reins", "bridge"] }
}
}
1. Bundler plugin & dev connect vite.config.ts + src/main.tsx
// 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();
}
Build script gate: "build": "vite build && reins verify dist"
2. Register actions & panels src/Settings.tsx
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>;
}
Also exposes window.__reins__ in browser DevTools and registers WebMCP tools when paired.
1. Next config & token route next.config.mjs + route.ts
// next.config.mjs
import { withReins } from 'reins/next';
export default withReins({ /* your Next config */ });
// app/api/reins-token/route.ts
export { GET } from 'reins/next/token-route';
export const dynamic = 'force-dynamic';
Works with both Webpack and Turbopack. Build gate: "build": "next build && reins verify .next/static"
2. Client bootstrap app/Reins.tsx
'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;
}
In components, import useReinsAction, useReinsState and ReinsPanel from reins/react.
1. Metro config metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withReins } = require('reins/metro');
module.exports = withReins(getDefaultConfig(__dirname));
For Android emulators or USB devices: adb reverse tcp:7357 tcp:7357. Export gate: npx expo export && npx reins verify dist.
2. Connect & wrap views 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>;
}
<ReinsPanel> renders a native View with testID={id} and measures window coordinates in device pixels.
Nine MCP tools. Seven hard security limits.
Give agents instruments, not just screenshots—with boundaries enforced in code, not just prompts.
The 9 MCP tools
reins_list_actionsReturns the paired app, its registered actions with JSON schemas, state sources and mounted panels. Always call this first.reins_run_actionRuns one named action:{ name, input, confirm? }. Also records a[reins] <name>performance measure.reins_stateReads a redacted snapshot of one state source (Zustand, Redux, React Query, route, storage) or all of them.reins_networkRecentfetchcalls: method, URL, status,startMs, duration in ms, and redacted headers/bodies.reins_errorsRecentconsole.errorcalls, uncaught exceptions and unhandled promise rejections.reins_panel_boxBounding box of a<ReinsPanel id>in CSS pixels withdevicePixelRatioand viewport dimensions.reins_snapSaves a pixel-exact PNG of a panel to.reins/snaps/(via Playwright CDP in browsers oradb screencapon Android).reins_perfPage lifecycle timings (TTFB, FCP, DOM interactive, load), component/panel mount times and render counts, API call latency stats, and a unified millisecond timeline.reins_domInspects a panel or CSS selector's DOM subtree, bounding boxes, computed CSS styles (direction,display,flexDirection,overflowX), and automatic layout issue flags (horizontal-clip,vertical-clip,zero-size).
Security model (enforced in code)
- Kept out of production. Core turns on only in dev builds. Bundler plugins alias
reinstoreins/stub, andreins verifyfails the build if__REINS_DEV_ONLY__is found. - Off until paired. Before the bridge accepts its 192-bit token from
.reins/token, reins patches nothing and adds no listeners. - Loopback & origin locked. The bridge binds to
127.0.0.1only (no host flag) and refuses non-localhost WebSocketOriginheaders. - Redacted by default. Request/response bodies,
Authorization/Cookieheaders, storage values and sensitive state keys (token,password,secret,session) become[redacted]unless allowlisted. - Consequential actions require confirmation. Any action marked
consequential: trueis refused unless called withconfirm: true. - Full audit log. Every pairing, refusal, MCP call, DevTools call and WebMCP call is written to
.reins/audit.logwith redacted arguments. - Lazy & no eval. Waits for
load+ idle before pairing, and can only run functions explicitly registered by the app.
The companion to Coordinator
Coordinator (mindmakr/coordinator) runs multi-step engineering work through builders and independent blind critics until every behaviour meets a hard bar. reins gives those builders and blind visual critics hands and eyes inside your running app.
- 1. Agree the bar before the build (Principle #3). Coordinator writes each unit's bar as testable given / when / then behaviours referencing your app's
reinsactions and<ReinsPanel id>names. - 2. Give agents instruments, not just screenshots (Principle #2). The builder and critic read
reins_state,reins_network,reins_perf(lifecycle, component mounts & render counts, API latency timeline), andreins_dom(computed CSS & overflow clipping flags) alongsidereins_snapPNGs. - 3. Blind critic & measurement on the record (Principles #4, #5 & #8). A separate visual critic—who never sees the builder's reasoning—drives your app across locales (e.g.
enandarRTL), verifies zero layout clipping and fast paint/mount timings, and attaches durable proof on the Coordinator dashboard.
Install both skills in Claude Code
/plugin marketplace add mindmakr/coordinator
/plugin install coordinator@coordinator
/plugin marketplace add mindmakr/reins
/plugin install reins@reins
Then in any web or Expo project
# 1. Wire reins into the app automatically
/reins:setup
# 2. Initialize Coordinator on the repo
/coordinator:init
# 3. Build & verify with builders + blind critics
/coordinator:run Add Arabic RTL checkout flow