InSitue Dev · open-source, MIT, free

Local dev tool —
point + Claude edits.

Pick an element in your running app, describe what should change, hit Send. Claude in your terminal reads the file at the exact line and proposes the edit. You approve, claude writes, HMR picks it up. No copy-pasting file paths.

Last updated 2026-06-02 · @insitue/sdk@0.9.6 · @insitue/claude-plugin@0.7.7 · Code + Desktop

cheat sheet · the whole loopbash
# In your app (any React framework with a dev tree)
pnpm add -D @insitue/sdk
# Then mount <InSitueCapture /> dev-only in your root layout
# (see setup step 2 below for the exact snippet).

# In claude — Claude CODE
/plugin marketplace add InSitue/insitue
/plugin install insitue@insitue-plugins
/insitue:connect       # pick loop — responsive, never blocks typing
/insitue:watch         # hands-free — acts on every pick instantly (Esc to chat)
/insitue:disconnect    # exit cleanly, launcher mutes
/insitue:login         # browser sign-in (unlocks the cloud issues inbox)
/insitue:logout        # revoke + clear credentials

# In claude — Claude DESKTOP (one-time)
npx -y @insitue/claude-plugin setup --desktop
# Restart Desktop, then in a new chat:
#   "Use the InSitue MCP — call start_session."

# Optional companion CLI (sign in, work cloud issues)
npx @insitue/companion@latest login    # browser PKCE; auto-links this repo
npx @insitue/companion@latest issues    # list your cloud issues

# Troubleshoot anything
npx -y @insitue/claude-plugin diagnose
The loop

Four steps.
Repeat for everything you want to change.

Step 1

Pick

Click the InSitue dot in the bottom-right of your app. Purple when claude is attached, muted grey otherwise. Hover any element to highlight it; click to capture. The widget resolves the JSX file:line via React fiber _debugSource data (dev) or the build-stamped data-insitue-source attribute (prod, via the SWC plugin) — exact when either is available; falls back to owner-fiber with an approximate-confidence warning otherwise. Your first pick in a session triggers a browser tab-share permission prompt — grant it for pixel-perfect screenshots, or turn off “Always pixel-perfect” in the widget’s gear to fall back to the html-to-image rasterise path (no prompt, occasional z-order trade-offs on motion components and text-over-image).

Step 2

Describe

A textarea auto-focuses with the picked target and a screenshot. Type your instruction in plain English — it's what you'd say to the engineer next to you. No slash commands, no special syntax.

Step 3

Send

Press Enter (or click Send). The widget submits the bundle over a loopback WebSocket to the companion, which broadcasts it to your terminal claude.

Step 4

Act in terminal

Claude reads the file at exactly the right line, proposes a diff, waits for your approval. On approval: claude writes (built-in Edit on Code, apply_edit MCP tool on Desktop), your dev server's HMR picks it up, you see the change in the browser. Pick the next thing.

Stack support
Any React app with a dev server
Primary dogfood: Next.js (App + Pages Router). Should work on any React app with a dev server — Vite, Remix, Astro+React, Redwood — because the picker resolves source from React fiber _debugSource data, which React 18+ provides in dev for any tree. No SWC/babel plugin needed for the dev loop (we ship one for production source attribution; see /docs). If your framework breaks, file an issue with the picked element + your framework version.
Setup · 3 steps

From zero to first edit in 60 seconds.

The Claude plugin is once-per-machine. The SDK is once-per-app. After that you only ever run two terminals.

1

Install the Claude plugin

Works on both Claude Code (the CLI) and Claude Desktop (the macOS / Windows app). Same MCP server, same widget, same picks — only the install step differs.
Claude Code · Claude Desktopsh
# Claude Code — in any claude session
/plugin marketplace add InSitue/insitue
/plugin install insitue@insitue-plugins

# Claude Desktop — in your project directory
npx -y @insitue/claude-plugin setup --desktop
# (idempotent; backs up your existing claude_desktop_config.json)
The plugin ships a stdio MCP server. On boot it auto-spawns the local companion (@insitue/companion) via npx — no separate terminal to babysit. Desktop users restart the app after setup and start a new chat with “Use the InSitue MCP — call start_session.”
2

Mount the capture widget in your app

Add @insitue/sdk to your devDependencies and mount <InSitueCapture /> at your app root. With no projectKey prop, the widget connects to a local companion (your terminal claude). With one, the same component ships to InSitue Cloud — same widget, two sinks.
app/layout.tsxtsx
pnpm add -D @insitue/sdk

import { InSitueCapture } from "@insitue/sdk";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        {process.env.NODE_ENV !== "production" && <InSitueCapture />}
      </body>
    </html>
  );
}
Vite users substitute import.meta.env.DEV for the Node-env check. Every framework with a React tree works — Next.js, Vite, Remix, RedwoodJS, Astro+React islands.

On pnpm 11.x a freshly published SDK version is held back for ~24 hours by the minimumReleaseAge policy. If pnpm add prints (X.Y.Z is available) next to an older version, the easiest path is to wait — once the window passes, the plain install resolves to latest as expected. Otherwise, set minimumReleaseAge: 0 at the top of your pnpm-workspace.yaml and re-install: the per-package minimumReleaseAgeExclude list isn't enough, because pnpm also re-checks the cutoff against the lockfile on every pnpm dev / pnpm install.
3

Run it

Two terminals. Your normal dev server plus a claude session in the project root. Run /insitue:connect — within ~3 seconds the companion is up and the bridge is listening for picks.
terminalsh
# Terminal A — your dev server, any port
pnpm dev

# Terminal B — claude, in the project root
claude
> /insitue:connect
The widget

<InSitueCapture />

One component, two sinks. Auto-detected: with projectKey it ships to InSitue Cloud; without, it ships to the local companion. Same picker, same screenshot pipeline, same approval-gating in the sink.

Dev mode

No props. Connects to the local companion at ws://127.0.0.1:5747. The launcher is a small circular dot in the corner — purple when a claude session is attached via /insitue:connect, muted grey otherwise (click it then to see setup instructions). Works fully standalone — no account needed. Sign in and it also unlocks the cloud issues inbox.

devtsx
<InSitueCapture />

Production mode

Pass your publishable projectKey. Same widget with a warm SaaS skin — end users see "Report a problem," tickets land in your InSitue inbox, the autopilot opens draft PRs.

prodtsx
<InSitueCapture projectKey="pk_live_..." />
proptypedescription
projectKeystring?Publishable key. When set, captures POST to InSitue Cloud. Origin-pinned + quota'd; safe to ship in your prod bundle.
endpointstring?Override the cloud ingest endpoint. Almost no one needs this.
sinkCaptureSink?Force a specific sink (`{ kind: 'cloud' | 'companion' }`). Auto-detection from `projectKey` covers the common cases.
onCapturefunction?Take over delivery yourself. Wins over `projectKey` and `sink`. Useful for routing to a custom backend.
The slash commands

/insitue:connect

Five slash commands ship with the plugin. Two drive picks: /insitue:connect (responsive — never blocks your typing; a pick is grabbed on your next message) and /insitue:watch (hands-free — acts on every pick the instant you make it; press Esc to chat). /insitue:disconnect ends the session; /insitue:login and /insitue:logout handle cloud sign-in (see Sign in). Run /insitue:connect once per claude session: the MCP server starts up, finds or spawns the companion, and you're in the loop — receive a pick → read the file → propose an edit → wait for your approval → write.

What claude does

  • Calls list_recent_picks once for any backlog.
  • Polls next_pick (~8s) for a pick, then hands control back — it never parks in a loop, so your typing is always answered instantly.
  • On each pick, echoes your typed prompt + source line back into chat, reads the file at pick.source.file, proposes a diff, waits for approval.
  • Writes only after explicit "yes" / "approve" — never auto-applies.
  • Grabs any pick you made while idle the moment you send your next message. Want it to act the instant you pick instead? Run /insitue:watch (Esc to chat).
  • On "stop" / "done" / "disconnect": auto-calls end_session — companion stops, browser launcher mutes.

Guardrails

  • Every write gated by explicit terminal approval. No auto-apply.
  • On Claude Code: writes go through claude's built-in Edit tool.
  • On Claude Desktop: writes go through the plugin's apply_edit / write_file MCP tools — same human-in-the-loop gate; project-dir scoped (refuses paths outside your project root).
  • Selector-only picks (no source resolution) are refused at the widget — claude never gets useless context.
  • Approximate-confidence picks (owner-fiber fallback, not the exact JSX site) surface a warning before edit.
  • Cross-file changes require an explicit user ask.

MCP tools the plugin exposes

proptypedescription
start_sessiontoolDesktop entry point. Returns the operating instructions + current state (project dir, companion reachability, buffered picks). Code users hit /insitue:connect instead; Desktop has no slash commands, so claude calls this once at the start of every session.
list_recent_pickstoolUp to N picks buffered since the MCP server started. Called once at session start to surface picks made before claude attached.
next_picktoolLong-polls (8s default; max 30 min) until the user clicks Send. Returns the pick complete — target, source.file:line, screenshot, userNote, cmsSource (if the picked element was CMS-rendered). After end_session, returns { status: "disconnected" } instead of attaching.
end_sessiontoolCleanly disconnect. Closes the WS subscriber (browser launcher mutes immediately), kills the companion if we spawned it, deletes the stale session file. Auto-called when the user says "stop" / "done" / "disconnect". Safe to call repeatedly. Reconnect via /insitue:connect or start_session.
diagnosetoolHealth check: project-dir source, session-file freshness, companion reachability, @insitue/sdk + swc-source-attr install + wiring, concrete recommendations. Use when picks aren't flowing.
read_filetoolProject-scoped file read. Optional startLine/endLine for partial reads. Used on Desktop (Code uses its built-in Read). Refuses paths outside the resolved project dir.
apply_edittoolProject-scoped string-replacement edit. oldString must occur exactly once unless replaceAll: true. Used on Desktop. Approval-gated.
write_filetoolProject-scoped full-file write. For new files or full rewrites. Used on Desktop. Approval-gated.
authenticatetoolStart a browser sign-in. Chooses the device flow vs loopback for you (device when remote/SSH), returns the authorize URL + a short user code to confirm. Backs /insitue:login.
complete_authenticationtoolPolls (~5 min) until you approve in the browser, saves credentials, then best-effort auto-links this repo to its InSitue project.
logouttoolRevokes the token server-side and clears ~/.insitue/auth.json. Backs /insitue:logout. If the server revoke fails, local creds are still cleared.
list_cloud_issuestoolList the cloud issues for the linked project (requires sign-in). The widget renders these as a stacked card deck with screenshots.
claim_cloud_issuetoolClaim an issue so the team knows you're on it. Takes the issue id.
resolve_cloud_issuetoolMark an issue resolved with the PR url (and optional branch). The dashboard then shows live PR status — opened / merged / closed.
release_cloud_issuetoolRelease a claimed issue back to the queue.
Sign in

insitue login

The dev loop is fully tokenless — sign-in is optional. You only authenticate to unlock the cloud issues inbox (work tickets reported by your users, right in the widget). Bare insitue login is a browser PKCE sign-in: no tokens to paste. Or run /insitue:login from inside claude.

Browser sign-in (default)

insitue login opens your browser, prints the authorize URL + a short user code to confirm, and on success prints ✓ Signed in as <login>. It then best-effort auto-links the current repo to its InSitue project. Locally it uses a loopback redirect; over SSH (or when no loopback port is free) it auto-switches to the device flow. Force either with --loopback or --device.

terminalsh
# browser PKCE — the default; or run /insitue:login in claude
npx @insitue/companion@latest login

# force the device flow (SSH / remote, can't open a browser locally)
npx @insitue/companion@latest login --device

# log out: revokes server-side, clears ~/.insitue/auth.json
npx @insitue/companion@latest logout

Token scope + CI fallback

  • A token minted by `insitue login` from inside a repo is project-scoped — it can read and act on that one project's issues only; other projects return not-found.
  • Dashboard-minted PATs and `--token pat_live_…` are account-wide. Mint PATs at <host>/app/settings/developer.
  • Scope mismatch? If you're signed in but the token is scoped to a different project than the one linked here, run `insitue login` (or /insitue:login) from THIS repo.
  • CI / headless: `insitue login --token pat_live_…`. `--host <url>` overrides the API host (default https://app.insitue.com). No INSITUE_TOKEN env var exists — the only override is INSITUE_API_HOST; creds live in ~/.insitue/auth.json.
Cloud issues

Fix what your users reported without leaving dev.

Once you sign in, the widget unlocks a cloud issues inbox: the tickets your end users filed against the production reporter, ready to fix on your machine with your own claude. Cloud features require companion ≥ 0.7.0 (the enforced MIN_CLOUD_COMPANIONfloor); older companions show an “Update InSitue to continue” banner.

In the widget

  • A navigable, stacked card deck with screenshots and a persistent launcher — not a flat list.
  • Fix locally opens an editable, pre-filled request (Send to Claude) before claiming — tweak it, then hand it to your terminal claude.
  • Mark resolved, or Reject with an optional reason (consumes no agent run) — consistent with the dashboard.

From the CLI

The same actions are scriptable through the companion CLI (requires sign-in):

terminalsh
insitue issues                       # list cloud issues for this project
insitue claim <id>                   # claim before you start
insitue resolve <id> --pr <url> [--branch <name>]
insitue release <id>                 # hand it back to the queue
insitue reject <id> [--reason <text>]  # won't-fix; consumes no run
The companion

Invisible infrastructure.

The companion is a small Node process — a loopback WebSocket bridge between your browser widget and your terminal claude. The plugin auto-spawns and cleans it up. Most users never run it directly.

Loopback only

  • Binds 127.0.0.1 — refuses non-loopback at the socket layer.
  • Per-session token in .insitue/session.json (auto-gitignored).
  • Localhost-wildcard Origin allowlist for any dev port.

Auto-spawned

  • MCP server detects existing companion at boot — reuses it.
  • Otherwise spawns `npx -y @insitue/companion@latest` (dev is the default command).
  • Killed cleanly on claude session exit (only the child it owns).

Standalone, too

  • `dev` is the default command: bare `npx @insitue/companion@latest` runs it (`-p, --port <n>`, default 5747).
  • Pipe picks to ANY tool via `insitue connect | mytool` (`--json` emits NDJSON).
  • Refuses to start under NODE_ENV=production by design.
Keyboard
Esc cancels picking or closes the compose panel. Enter in the description box sends. We deliberately don't bind ⌘K globally — most apps own that combo for their command palette.
Troubleshooting

When something's off.

First — run the diagnose CLI
Before reading the rest of this list: npx -y @insitue/claude-plugin diagnose from your project root. Reports project dir + source, companion reachability, SDK install + version, SWC plugin install + config, and the exact thing to do next. Most problems show up here.
The dot stays muted (grey) — claude attached but launcher isn't going purple
The MCP server only attaches a CLI subscriber when the user actually invokes /insitue:connect (or calls start_sessionon Desktop). Just opening claude doesn't flip the launcher — the dot stays muted until someone's actually listening for picks. Click the muted dot to see setup instructions in the browser.
/insitue:connect says the plugin isn’t installed (Claude Code)
Refresh the marketplace and reinstall: /plugin marketplace update insitue-plugins then /plugin install insitue@insitue-plugins. Restart claude (/exit claude) so the new plugin manifest loads.
Desktop: claude doesn’t know about InSitue after setup
Restart Claude Desktop after running npx -y @insitue/claude-plugin setup --desktop — config changes don't hot-reload. Start a new chat and tell claude: “Use the InSitue MCP — call start_session.” If still nothing, check that the entry exists in ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows).
Widget refuses to send: “Couldn’t resolve source file”
You picked an element whose source can't be derived from React fiber data or a build-stamped attribute — often an unnamed wrapper div, a Fragment, or a component without React DevTools data. Click Re-pickand choose a parent component. The widget refuses selector-only picks by design — claude needs an exact file or it's guessing.
Picks land in chat (stderr ack) but claude doesn’t act on them
You probably ran /insitue:disconnect(or said “stop”) and forgot. After disconnect, next_pick returns { status: "disconnected" } instead of attaching. Run /insitue:connect(or ask claude to call start_session) and you're back in the loop.
I want to run the companion myself / see its logs
Run npx @insitue/companion@latest dev in any terminal. The MCP server detects the existing companion at .insitue/session.json and reuses it instead of spawning a child. Useful when debugging.
Stale companion blocking port 5747 after a previous claude exit
Find + kill: ps aux | grep @insitue/companion kill <pid>. Then remove the stale session file: rm -rf .insitue. Next /insitue:connect will spawn a fresh companion.

What's next

The dev tool is the free open-source half of InSitue. The same widget, with a projectKey, becomes the production reporter — your end users point at problems, the autopilot opens draft PRs.

Free, MIT, installable today.

Two commands in claude, one component in your app. Companion auto-spawns. Picks resolve to exact file:line.