Ten npm packages ship together at the same version, all at 2.7.0, verified against the
registry. The Rust crate is versioned independently and is currently 0.1.0. That is
deliberate, not drift.
Which ones do I need?
A React app on Vite
@reticlehq/react + @reticlehq/vite-plugin. That’s it, two dev dependencies.A React app on Next.js
@reticlehq/react + @reticlehq/next. Keeps SWC; no Babel migration.Not React, or no bundler plugin
@reticlehq/browser alone, and call connect() yourself.Electron or Tauri
The web packages, plus
@reticlehq/electron or the reticle-tauri crate.@reticlehq/server. Your agent runs it with npx.
The architecture in one rule
The package split is not cosmetic. Each boundary enforces something:
Those rules are why the browser SDK cannot accidentally pull in a filesystem call, and why the server cannot accidentally depend on
window. They are enforced by the dependency graph, not by discipline.
@reticlehq/core
The wire contract. Every constant and zod schema that crosses a boundary. Browser to bridge, bridge to agent, is defined here. Why it exists: without one shared definition, the browser and the server drift. One renames a field, the other keeps reading the old one, and the failure is silent. A verdict that quietly stops carrying evidence. Core makes that a type error instead. Depends on:zod, and nothing else. That is the whole point; it sits at the bottom of the graph so everything can depend on it without cycles.
Do you install it? No. It arrives as a dependency of whatever you did install.
Before 2.0,
@reticlehq/core was an umbrella package re-exporting everything under subpaths. That
was retired: the umbrella meant every consumer pulled the whole world to get one constant.@reticlehq/browser
The SDK that lives in your page. It installs the observers (DOM, network, console, routing, animations), builds semantic snapshots, executes actions, and talks to the bridge. Why it exists: this is the part that makes Reticle different from a browser-automation tool. Because it runs inside the app rather than driving it from outside, it can see a state mutation and a fired signal, not just a rendered pixel. Depends on:@reticlehq/core, @testing-library/dom. The latter is why reticle_query speaks role, label and testid rather than CSS selectors.
Install it directly when you are not on React, or you want connect() under your own control:
@reticlehq/server
The bridge, the MCP server, and thereticle CLI. Hosts the WebSocket your app dials into and exposes the tools your agent calls.
Why it exists: agents speak MCP; browsers speak WebSocket; somebody has to be fluent in both and own the .reticle/ directory where flows, baselines and runs are stored.
Depends on: @modelcontextprotocol/sdk, @reticlehq/core, ws, zod.
Do you install it? Not into your project. Your agent launches it:
@reticlehq/react
The kit most React users install. It re-exports the browser sensor. So one install gives you bothreticle and registerCapabilities. And maps a DOM node to its React component and source file through the fiber tree.
Why it exists: this is the package that turns “the button is broken” into src/components/Login.tsx:81. Finding a bug is half the job. Reticle’s answer to the other half lives here.
Depends on: @reticlehq/browser, @reticlehq/core. Peer: react.
@reticlehq/vite-plugin
One line invite.config.ts, two jobs. It stamps source locations onto your JSX and injects connect() so you cannot forget to.
Why it exists: the two things everyone forgets are wiring the SDK and enabling source mapping. This does both, and apply: 'serve' guarantees it never reaches a production build.
Depends on: @babel/core, @reticlehq/babel-plugin, @reticlehq/core. Peer: vite.
@reticlehq/babel-plugin
Stampsdata-reticle-source="file:line:col" onto JSX host elements.
Why it exists: React 19 dropped _debugSource, which is what source mapping used to rely on. This plugin puts the information back, at build time, in dev only. Without it, reticle_inspect can still tell you the component name but not the file.
Depends on: nothing. Peer: @babel/core.
Do you install it? Only directly if you are wiring Babel yourself. The Vite and Next packages both pull it in.
@reticlehq/next
Next.js source mapping without giving up SWC. Why it exists: Next users are on SWC, and telling them to switch to Babel for a dev tool would be a rude trade. This adds a dev-only webpack pre-loader that stamps the same attribute, and leaves your build pipeline alone. Depends on:@babel/core, @reticlehq/babel-plugin. Peer: next.
This one is CommonJS on purpose.
next.config.js is loaded by Node before any ESM transform, so
an ESM-only helper would fail at the least helpful moment.@reticlehq/test
The spec runner for CI.reticleTest registers declarative specs and runs them against Reticle’s tools directly, with no MCP or stdio in the loop.
Why it exists: driving interactively is reconnaissance. At some point you want the same checks to run on every pull request without an agent. And without paying model tokens to re-derive them.
Depends on: @reticlehq/core, @reticlehq/server. Peer: vitest.
Turn a session into a suite
Writing specs that bind to signals rather than DOM structure.
@reticlehq/eslint-plugin
One rule: state changed, so a signal must fire. Why it exists: signals are the strongest evidence Reticle can use, and they rot the way all conventions rot. Someone adds a mutation, forgets the signal, and every verdict for that flow quietly drops a grade. Nothing breaks. The tests still pass. The evidence just gets weaker. A lint rule makes the signal layer self-enforcing, which is the only way a convention survives contact with a deadline. Depends on:@typescript-eslint/utils. Peer: eslint.
@reticlehq/electron
Reaches the two places the renderer cannot. Why it exists: in Electron, the interesting failures often happen across the IPC boundary. The renderer asks the main process for something and the main process quietly fails. The renderer-side SDK cannot see that. This package makes main-process IPC observable and the window screenshottable. Depends on:@reticlehq/core. Peer: electron.
reticle-tauri
The Rust crate, on crates.io, currently 0.1.0. Versioned independently of the npm packages. Why it exists: Tauri IPC observation needs nothing on the Rust side; aninvoke('load_todos') already reaches Reticle as ipc://load_todos. You only need this crate for screenshots and headless mode.
Desktop setup in full
Electron and Tauri wiring, including the Tauri CSP whose failure is silent.