JavaScript / TypeScript SDK
kestrel-sdk — zero dependencies, ~1 KB gzip, passive capture only.
Install
npm install kestrel-sdk
# or: bun add kestrel-sdk / pnpm add kestrel-sdk / yarn add kestrel-sdkInitialize
Call init() once, as early as possible in your app — before any code that might throw.
import { init } from 'kestrel-sdk'
init({
endpoint: 'https://kestrel.example.com/api/v1/ingest',
token: process.env.KESTREL_TOKEN!, // never inline a token; load it from env
release: process.env.GIT_COMMIT, // optional, auto-detected when omitted
})init() registers listeners for the runtime's unhandled-error hooks and returns immediately. Once attached, the following are captured automatically:
- Browser —
window.error,window.unhandledrejection. - Node / Bun / Deno —
uncaughtException,unhandledRejection.
That's the entire integration. Done.
Configuration
init({
endpoint: string, // required — your /api/v1/ingest URL
token: string, // required — project ingest token
release?: string, // build identifier; auto-detected from common env conventions
fetch?: typeof fetch, // override fetch (SSR / tests)
recordConsole?: boolean, // see "console buffer" below; default false
})The SDK auto-detects release from any of these (in order): import.meta.env.GIT_COMMIT, import.meta.env.VITE_GIT_COMMIT, import.meta.env.PUBLIC_GIT_COMMIT, globalThis.KESTREL_RELEASE, process.env.KESTREL_RELEASE, process.env.GIT_COMMIT, process.env.npm_package_version. Whichever resolves first wins.
Manual capture
Sometimes you need to record an exception without throwing it. The second argument is a free-form context object attached to that single event:
import { capture } from 'kestrel-sdk'
try {
await thingThatMightFail()
} catch (err) {
capture(err, { userId, requestId })
// ...your fallback logic
}Manual capture exists, but it's not advertised as the primary integration. The SDK's job is to catch what your code missed; if you're calling capture() everywhere, you're paying for an SDK to do less than a console.error does.
Console buffer (opt-in)
If you want the last N console.* calls attached to each captured event:
init({ ..., recordConsole: true })This wraps console.log / info / warn / error / debug and keeps a 50-entry ring buffer. The wrapper preserves original behavior (your logs still go to stdout / the browser console).
This is the only thing the SDK monkey-patches, and it's gated behind an explicit opt-in for that reason. The default contract — passive capture only — is honored when recordConsole is omitted.
Sourcemaps
For minified browser bundles, upload the corresponding .map files at deploy time:
kestrel sourcemaps upload ./dist --release "$(git rev-parse --short HEAD)"The server resolves frames at read time; both the web UI and MCP get_error return both the original (minified) stack and the resolved stack.
Bundle size
bun run build && du -h dist/index.js should print < 5 KB. CI fails the SDK build if it doesn't.