@caprail-dev/analytics
v0.10.1
Published
Edge collector for Caprail — see which AI agents access your site.
Readme
@caprail-dev/analytics
Edge collector for Caprail — see which AI agents (Claude Code, Codex, ChatGPT, Gemini, Perplexity, …) access your site, in real time.
One install, framework adapters as subpath exports. The collector only classifies
nothing locally and beacons each request to your Caprail ingest endpoint; the
server re-classifies the User-Agent authoritatively.
Install
npm i @caprail-dev/analyticsSet CAPRAIL_INGEST_URL and CAPRAIL_INGEST_KEY (your site's cap_live_… key,
from /dashboard/sites) in your environment, or pass them explicitly.
Next.js
// middleware.ts
import { createCaprailMiddleware } from "@caprail-dev/analytics/next";
export const middleware = createCaprailMiddleware(); // reads env, or pass { url, key }
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};Middleware runs before the response, so it cannot see the final status /
Content-Type — status shows as unknown and the markdown/html grade falls back
to path inference. Add the OTel adapter below to recover real status + latency.
Accurate status & latency (recommended)
Next.js emits an OpenTelemetry root span per request carrying the authoritative
http.status_code after the response completes. Register the Caprail processor
(@vercel/otel + @opentelemetry/api ship with this package):
// instrumentation.ts
import { registerCaprail } from "@caprail-dev/analytics/next-otel";
export function register() {
registerCaprail(); // reads env, or pass { url, key }
}Run both: the middleware sees every request (including CDN cache hits that
never reach the server) but no status; the span processor sees the real status
but only for requests that run code. The middleware forwards an
x-caprail-request-id header that the span picks up, and ingest merges the two
beacons into one row.
| Setup | Coverage | Status / latency |
| ------------------------------ | ----------------------------- | --------------------------------------------- |
| middleware alone | everything (incl. cache hits) | unknown |
| next-otel alone | dynamic requests only | ✅ real |
| hybrid (both) | everything | real where a span exists; unknown for pure cache hits |
Already calling registerOTel yourself? Compose instead of replacing:
import { registerOTel } from "@vercel/otel";
import {
CaprailSpanProcessor,
caprailAttributesFromHeaders,
} from "@caprail-dev/analytics/next-otel";
registerOTel({
// ...your config
attributesFromHeaders: { ...caprailAttributesFromHeaders },
spanProcessors: ["auto", new CaprailSpanProcessor()],
});Cloudflare Worker
// worker.ts
import { withCaprail } from "@caprail-dev/analytics/cloudflare";
export default withCaprail({
async fetch(request) {
return fetch(request); // your origin
},
});The Worker wraps the real Response, so it reports the authoritative status,
content-type, and latency.
Exports
| Subpath | Export |
| -------------- | ------------------------- |
| . | classify, collect, resolveConfig, types |
| ./next | createCaprailMiddleware, resolveRequestId, REQUEST_ID_HEADER |
| ./next-otel | registerCaprail, CaprailSpanProcessor, caprailAttributesFromHeaders, eventFromSpan |
| ./cloudflare | withCaprail |
Adapters for TanStack Start and Node (Express/Hono) are planned.
./next-otelpulls in@vercel/otel+@opentelemetry/api(shipped as dependencies); they are only loaded when you import that subpath, so the/nextand/cloudflareadapters stay free of them in your bundle.
