@insitue/sdk
v0.9.6
Published
InSitue capture SDK — drop one snippet into your deployed app; your users point at a bug, InSitue opens a verified pull request.
Maintainers
Readme
@insitue/sdk
The InSitue capture widget for browser apps. One component,
two sinks: ship captures to the InSitue Cloud (production
bug-reporting → autopilot → draft PR) or to a local
@insitue/companion (developer mode → claude in your terminal
acts on each pick).
import { InSitueCapture } from "@insitue/sdk";
// Dev: companion sink (no projectKey).
<InSitueCapture />
// Prod: cloud sink with your publishable project key.
<InSitueCapture projectKey="pk_..." />Same widget. Same picker. Same screenshot pipeline. Only the submit step changes.
Install
npm install -D @insitue/sdk
# or pnpm add -D @insitue/sdk
# or yarn add -D @insitue/sdkDev mode (talk to claude in your terminal)
Mount with no props. The widget connects to a local companion
over a loopback WebSocket. A claude session running
/insitue:connect
picks up each capture and edits the file you pointed at.
// app/layout.tsx (Next.js) — dev-gated so the chunk never
// ships in production.
import { InSitueCapture } from "@insitue/sdk";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
{process.env.NODE_ENV !== "production" && <InSitueCapture />}
</body>
</html>
);
}// src/main.tsx (Vite)
import { InSitueCapture } from "@insitue/sdk";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
{import.meta.env.DEV && <InSitueCapture />}
</StrictMode>,
);Cloud issues inbox (dev mode)
When the companion is running (insitue dev) and you are logged in and
linked to an InSitue Cloud project, the widget launcher gains an Issues
entry with an unread count. Opening it lists the project's InSitue Cloud
bug reports directly in your browser.
What it shows
The inbox is a navigable stacked card deck — one card per issue, with a persistent launcher you can reopen at any time (it isn't a flat list you scroll once and lose). Page through the deck to triage; each card shows:
- The reporter's screenshot
- Description and a status badge (open / in-progress / resolved)
- Source location as
file:line(e.g.src/components/Header.tsx:42) - Duplicate-count pill when multiple users reported the same element
Claim → resolve flow
Fix locally doesn't claim immediately — it opens an inline, editable
pre-filled request ("Send to Claude") so you can tweak the wording first.
When you send, the issue is claimed and the repro (screenshot, DOM context,
source location) is fed into the local agent loop and broadcast to any
insitue connect / Claude Code MCP subscriber listening in your terminal.
The agent acts on the file immediately — no cloud-agent run consumed, no
PR quota touched.
Once you have a fix, Mark resolved accepts a PR URL inline and submits the resolution back to InSitue Cloud.
Reject marks the issue won't-fix (status → rejected) with an optional reason. It consumes no agent run — consistent with the dashboard.
Token handling
The browser never holds your auth token. Issue requests go through the
companion process over the loopback WebSocket (ws://127.0.0.1:5747).
The companion — which has filesystem access to ~/.insitue/ — proxies
the cloud API call and returns only the issue data. The token and project
link never leave the companion's process.
Prerequisites
| Requirement | How to satisfy |
|---|---|
| Companion running | insitue dev in your project root |
| Authenticated | insitue login |
| Project linked | insitue link |
| Paid plan | Free plan shows a gated state |
| Companion version | ≥ 0.7.0 |
Cloud features require companion ≥ 0.7.0 (the enforced floor). Older companions show an "Update InSitue to continue" banner instead of the issues inbox.
See the @insitue/companion docs
for the full fix-cloud-issues-locally guide.
Error states
The widget surfaces a clear state for each failure instead of failing silently:
not_logged_in— no credentials. Runinsitue login(or the/insitue:loginslash command) from this repo.not_linked— signed in but the repo isn't linked to a project. Runinsitue link(noteinsitue loginalready auto-links, so this is rarely needed).scope_mismatch— your token is scoped to a different project than the one linked here. The widget tells you to sign in for this project — runinsitue loginfrom this repo to mint a project-scoped token for it.not_paid— the project is on the free plan; the inbox shows a gated state. Upgrade to unlock cloud issues.offline— the companion can't reach InSitue Cloud. Check your connection; the widget retries automatically.
Production mode (cloud sink → autopilot → draft PR)
Pass your publishable projectKey (e.g. pk_…). The key is
Origin-pinned and quota-gated server-side, so it's safe to ship
in a public bundle.
<InSitueCapture projectKey={process.env.NEXT_PUBLIC_INSITUE_KEY!} />End users see a friendly "Report a problem" pill in the corner of your app. Clicking it activates the picker; describing + sending submits to your InSitue inbox.
Props
interface InSitueCaptureProps {
/** Publishable project key. Set → cloud sink. */
projectKey?: string;
/** Override the cloud ingest endpoint. */
endpoint?: string;
/** Take over delivery yourself. Wins over both projectKey
* and sink. */
onCapture?: (draft: IssueDraft, bundle: CaptureBundle) => void;
/** Explicit sink override (rarely needed — most callers rely
* on auto-detection). */
sink?: CaptureSink;
/** Force getDisplayMedia capture from mount. Dev/dogfood only. */
defaultPixelPerfect?: boolean;
}Sink auto-detection
| projectKey | sink | Result |
|---|---|---|
| set | unset | Cloud sink (post to InSitue Cloud) |
| unset | unset | Companion sink (post to local WS, default port 5747) |
| any | set | Use sink verbatim |
onCapture wins over everything when defined.
Bulletproof selection
Every pick the widget submits has a usable source location.
The picker walks React fiber _debugSource first (exact), then
the data-insitue-source attribute injected by
@insitue/sdk/babel (exact), then the nearest
owning component's source (approximate). If even that fails,
the widget refuses to send and asks the user to pick a parent
— claude never gets a meaningless selector-only pick.
The confidence chip is shown inline so users (and you, in testing) can see exactly what's been resolved before they hit Send.
Source attribution
In production builds React strips its component-debugger metadata, so without an explicit source attribute the picker can only ship a CSS selector + DOM snapshot — and the cloud agent has to guess which file to edit. Confidence drops below the resolution threshold and the report routes to a human instead of auto-opening a draft PR.
A source-attribution plugin fixes this by stamping
data-insitue-source="file:line:col" onto every JSX element at compile
time. Two interchangeable plugins produce the identical attribute:
SWC plugin — recommended on Next.js ≥ 16.1. Fastest builds (keeps Next's
SWC compiler). See
@insitue/swc-source-attr:
pnpm add -D @insitue/swc-source-attr@next16// next.config.mjs
const nextConfig = {
experimental: {
swcPlugins: [["@insitue/swc-source-attr", { rootDir: process.cwd() }]],
},
};
export default nextConfig;Run npx @insitue/swc-source-attr doctor to confirm the right dist-tag for
your Next version.
Babel plugin (the universal fallback)
@insitue/sdk/babel works on any Next.js version and any bundler (Vite,
Webpack, older Next) and produces the identical data-insitue-source
output as the SWC plugin — capture confidence is exactly the same. The
cost: adding a Babel config opts your project out of Next's SWC compiler,
which makes builds noticeably slower. Prefer the SWC plugin on Next ≥ 16.1;
use Babel when you're on an older Next, can't upgrade, or use a non-SWC
bundler.
Next.js — add a .babelrc (this is what switches Next from SWC to Babel):
{
"presets": ["next/babel"],
"plugins": [["@insitue/sdk/babel", { "root": "." }]]
}Vite — wire it through the React plugin's Babel options:
// vite.config.ts
import insitueBabel from "@insitue/sdk/babel";
export default {
plugins: [
react({
babel: {
plugins: [[insitueBabel, { root: __dirname }]],
},
}),
],
};The root must match the project directory the companion is scoped to
(typically __dirname of the config file). The plugin is idempotent — safe
to run alongside the SWC plugin during a migration.
What's in a capture
Every send produces a CaptureBundle:
interface CaptureBundle {
id: string; // unique per capture
target: { source, confidence, selector, componentStack };
screenshot?: { dataUrl, source: "rasterise" | "display-media" };
computedStyles: Record<string, string>;
tailwindClasses: string[];
userNote?: string; // the user's description
runtime: { url, route, console, network, errors };
viewport: { w, h, dpr, breakpoint };
}Cloud sink: this goes to https://www.insitue.com/api/v1/capture
with your projectKey. Companion sink: it goes to
ws://127.0.0.1:5747 (the local companion's loopback WS) which
broadcasts to subscribed CLI/MCP listeners.
The SDK also sends a lightweight heartbeat that reports sdkVersion and
hostNextVersion to InSitue Cloud. This is advisory telemetry only — it
is never used to gate or reject captures.
Going to production
The same <InSitueCapture /> works in both modes — just pass
projectKey in production and your users get the SaaS theme
(warm, friendly) instead of the dev theme (dark, terminal-flavour).
A typical setup:
const PROJECT_KEY = process.env.NEXT_PUBLIC_INSITUE_KEY;
const IS_PROD = process.env.NODE_ENV === "production";
export default function InSitueWidget() {
if (IS_PROD && !PROJECT_KEY) return null;
return IS_PROD ? <InSitueCapture projectKey={PROJECT_KEY} /> : <InSitueCapture />;
}Then drop <InSitueWidget /> once in your app root.
Public API
The package ships three entry points:
@insitue/sdk (default)
<InSitueCapture />— the canonical widget. Auto-detects sink (cloud whenprojectKeyset, otherwise companion).<InSitue />— backward-compat dev alias for the companion sink. Equivalent to<InSitueCapture sink={{ kind: "companion", port }} />.InSitueCaptureProps,InSitueProps— prop types.SDK_VERSION: string— build-time-inlined package version. Surfaced in the widget footer so a screenshot proves the build.
@insitue/sdk/capture-only
mountCaptureOnly(opts): () => void— imperative mount for non-React apps (vanilla / Svelte / Vue). Returns a dispose function.CaptureOnlyOptions— full options shape (sink, projectKey, endpoint, onCapture, defaultPixelPerfect).CaptureSink— the sink discriminated union the SDK accepts.
@insitue/sdk/babel
- Default export — the Babel plugin that injects
data-insitue-source="file:line:col"onto intrinsic JSX elements. Optional; only needed when your bundler doesn't expose React fiber_debugSource(Vite, some Webpack setups).
The bundle ships with React fiber resolution as the primary source resolver, falling back to the build-injected attribute, falling back to the nearest owning component. The selector is always present as the last-resort locator.
Stability
The widget API (<InSitueCapture /> props, mountCaptureOnly
options) is the stable consumer surface. Internal modules
(capture.ts, picker.ts, client.ts) are not — import paths
under @insitue/sdk/* other than the three documented entry
points may change at any time.
The pre-0.3.0 chat-style overlay is removed; if you imported
<InSitue /> for that, the new behaviour is the unified capture
widget in companion-sink mode. No separate session history, no
in-overlay diff.
Versioning
- Major — backwards-incompatible widget prop changes, removed
exports, changes to the auto-detection rules (e.g. a new prop
precedence), bundle-shape changes that need a
CAPTURE_SCHEMA_VERSIONbump in@insitue/capture-core. - Minor — additive props, additive options, new exports, new
sink kinds, new optional features (e.g.
defaultPixelPerfect). - Patch — bug fixes, browser-quirk workarounds, docs, internal refactors, dep-bumps for transitive deps.
The SDK pins its CAPTURE_SCHEMA_VERSION and PROTOCOL_VERSION
against the companion. A mismatch is rejected at the WS handshake
rather than silently degraded.
Security
Report vulnerabilities privately — see SECURITY.md
in the repo root. Especially relevant for this package: anything
that lets the widget submit captures from a non-host origin,
that leaks the projectKey outside its Origin pin, or that lets
a host-page script read the Shadow DOM overlay's internal state.
The cloud sink ships projectKey (publishable, Origin-pinned,
quota-gated). The companion sink connects loopback-only and
authenticates via a per-session token written to
<project>/.insitue/session.json by the companion at bind time.
Tests
The package has a small unit suite covering bundle building, a Next/Image-specific regression, and a "don't render layer 2" DOM check.
pnpm test # one-shot
pnpm test:watch # watch modeFull integration coverage lives in @insitue/companion's
trust-boundary tests and the cloud-side autopilot suite (closed-
source). If you make a change here that touches the
SDK ↔ companion contract, run the companion's tests too.
License
MIT. See LICENSE.
