Lightweight error capture SDK for InariWatch — zero dependencies, works everywhere Node runs.
npx @inariwatch/captureOne command. Auto-detects your framework, installs, and starts capturing errors to your terminal. No signup. No config.
When you're ready for the cloud dashboard, add one env var:
INARIWATCH_DSN=https://app.inariwatch.com/api/webhooks/capture/YOUR_IDnpx @inariwatch/capture auto-detects and configures any of these:
| Framework | Plugin | Runtime |
|---|---|---|
| Next.js | @inariwatch/capture/next |
Node + Edge |
| Vite | @inariwatch/capture/vite |
Node |
| Nuxt 3 | @inariwatch/capture/nuxt |
Node |
| Remix / SvelteKit / Astro | @inariwatch/capture/vite |
Node |
| SolidStart / Qwik | @inariwatch/capture/vite |
Node |
| webpack (CRA, Vue CLI, Angular) | @inariwatch/capture/webpack |
Node |
| Express / Fastify / Koa / Hono | @inariwatch/capture/auto |
Node |
| Bun / Deno | @inariwatch/capture/auto |
Node-compat |
| Python / Go / Rust apps with Node instrumentation | — | Parent process |
// next.config.ts
import { withInariWatch } from "@inariwatch/capture/next"
export default withInariWatch(nextConfig)// instrumentation.ts
import "@inariwatch/capture/auto"
import { captureRequestError } from "@inariwatch/capture"
export const onRequestError = captureRequestError// vite.config.ts
import { defineConfig } from "vite"
import { inariwatchVite } from "@inariwatch/capture/vite"
export default defineConfig({
plugins: [inariwatchVite()],
})// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@inariwatch/capture/nuxt"],
})// astro.config.mjs
import { defineConfig } from "astro/config"
import { inariwatchVite } from "@inariwatch/capture/vite"
export default defineConfig({
vite: { plugins: [inariwatchVite()] },
})// webpack.config.js
const { withInariWatchWebpack } = require("@inariwatch/capture/webpack")
module.exports = withInariWatchWebpack({
// your existing webpack config
})node --import @inariwatch/capture/auto app.jsOr in package.json:
{ "scripts": { "start": "node --import @inariwatch/capture/auto src/index.js" } }All framework plugins inject git context (commit, branch, message) at build time and mark capture as external on server bundles so its node: builtin imports don't leak into client or edge chunks.
Every error includes rich context automatically — no code changes needed:
| Context | How | What the AI sees |
|---|---|---|
| Git | Injected at build time by withInariWatch |
commit f5eface on main — "refactor session handling" |
| Breadcrumbs | Auto-intercepts console.log + fetch |
Last 30 actions before the crash |
| Environment | Reads process + os at crash time |
Node version, memory, CPU, uptime |
| Request | Set via middleware or setRequestContext() |
Method, URL, headers, body (redacted) |
| User | Set via setUser() |
User ID + role (email stripped) |
| Tags | Set via setTag() |
Custom key-value pairs |
Sensitive data is scrubbed automatically: Bearer tokens, JWTs, passwords, API keys, credit card numbers, connection strings, and auth headers are all redacted before leaving your app.
Initialize the SDK. Call once at app startup. All options are optional — config is read from env vars.
| Option | Type | Description |
|---|---|---|
dsn |
string |
Capture endpoint (default: INARIWATCH_DSN env var) |
environment |
string |
Environment tag (default: INARIWATCH_ENVIRONMENT or NODE_ENV) |
release |
string |
Release version — also triggers a deploy marker |
substrate |
boolean | object |
Enable I/O recording (requires @inariwatch/substrate-agent) |
debug |
boolean |
Log transport errors to console |
silent |
boolean |
Suppress all console output |
beforeSend |
(event) => event | null |
Transform or drop events before sending |
try {
await riskyOperation();
} catch (err) {
captureException(err as Error);
}captureLog("DB timeout", "error", { host: "db.example.com", latency: 5200 });captureMessage("Deploy started", "info");addBreadcrumb({ category: "auth", message: "User logged in", data: { userId: "123" } });Console and fetch breadcrumbs are captured automatically.
setUser({ id: "user_456", role: "admin" });Email is stripped by default for privacy.
setTag("feature", "checkout");setRequestContext({ method: "POST", url: "/api/users", body: req.body });Headers with tokens/keys/secrets are redacted automatically. Body fields like password, credit_card, ssn are scrubbed.
Wait for pending events before process exit.
await flush();Capture every HTTP call, DB query, and file operation alongside your errors:
npm install @inariwatch/substrate-agentINARIWATCH_SUBSTRATE=trueWhen captureException() fires, the last 60 seconds of I/O are uploaded with the error.
| Variable | Description |
|---|---|
INARIWATCH_DSN |
Capture endpoint. Omit for local mode. |
INARIWATCH_ENVIRONMENT |
Environment tag (fallback: NODE_ENV) |
INARIWATCH_RELEASE |
Release version |
INARIWATCH_SUBSTRATE |
Set to "true" to enable I/O recording |
| Import | Description |
|---|---|
@inariwatch/capture |
SDK — init, captureException, captureLog, addBreadcrumb, setUser, setTag, setRequestContext, flush |
@inariwatch/capture/auto |
Auto-init on import — config from env vars |
@inariwatch/capture/browser |
Browser entry — error + unhandled rejection listeners |
@inariwatch/capture/shield |
Runtime security — source-to-sink attack detection |
@inariwatch/capture/next |
Next.js plugin — withInariWatch() |
@inariwatch/capture/vite |
Vite plugin — inariwatchVite() (covers Nuxt/Remix/SvelteKit/Astro/SolidStart/Qwik) |
@inariwatch/capture/webpack |
webpack wrapper — withInariWatchWebpack() (covers CRA/Vue CLI/Angular) |
@inariwatch/capture/nuxt |
Nuxt 3 module — add to modules: [] |
- Zero config —
npx @inariwatch/captureand you're done - Zero dependencies — just
fetch(Node 18+) - Works with every major framework — Next, Vite, Nuxt, Remix, SvelteKit, Astro, webpack, Express, Fastify, Node
- Automatic context — git, breadcrumbs, environment, request, user, tags
- Privacy by default — secrets, PII, and auth headers scrubbed automatically
- Env var driven — no DSN in source code,
INARIWATCH_DSNfrom env - Local mode — works without signup, errors print to terminal
- Substrate — full I/O recording with one env var
- Deploy markers — setting
releasesends a deploy event - Retry buffer — failed events retry automatically
- HMAC signing — events signed for webhook verification
Session replay lives in a separate package so the core SDK stays lean (~32 KB gzipped). Install only when you need it:
npm install @inariwatch/capture-replayOr let the CLI do it interactively:
npx @inariwatch/capture init
# → "Enable session replay? [y/N]"// app/capture-init.tsx
"use client"
import { useEffect } from "react"
export function CaptureInit() {
useEffect(() => {
void (async () => {
const [{ init }, { replayIntegration }] = await Promise.all([
import("@inariwatch/capture"),
import("@inariwatch/capture-replay"),
])
init({
dsn: process.env.NEXT_PUBLIC_INARIWATCH_DSN,
projectId: process.env.NEXT_PUBLIC_INARIWATCH_PROJECT_ID,
integrations: [replayIntegration()],
})
})()
}, [])
return null
}// app/layout.tsx
import { CaptureInit } from "./capture-init"
export default function RootLayout({ children }) {
return <html><body><CaptureInit />{children}</body></html>
}See @inariwatch/capture-replay for the full options list (PII classifier, block duration, mask selectors).
The integrations: [...] array in init() is how plugin-style extensions hook into capture. Any package exporting an Integration-shaped object can be registered:
import { init } from "@inariwatch/capture"
import { replayIntegration } from "@inariwatch/capture-replay"
init({
dsn: "...",
integrations: [
replayIntegration({ piiClassifier: "ai" }),
// future: performanceIntegration(), feedbackIntegration(), ...
],
})Core capture has zero knowledge of replay or any future integration — each lives in its own package and pays zero bundle cost for users who don't opt in.
MIT