npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usesapient/agent-tracker

v0.1.10

Published

Track AI coding agents visiting your site. Works with Next.js, Vercel, Cloudflare, Express, and Netlify DNS proxy setups.

Readme

@usesapient/agent-tracker

Track AI coding agents visiting your documentation. Works with Next.js, Vercel, Cloudflare, Express, and Netlify DNS proxy setups.

Installation

npm install @usesapient/agent-tracker

Quick Start

Browser Visitor Identity

Add the browser helper once in your client app to identify repeat human visitors with Sapient's first-party browser fingerprint. It writes visitor/session and first-touch attribution cookies that the server middleware below automatically attaches to tracking events.

import {
  identifyUser,
  initVisitorIdentity,
  trackConversion,
} from "@usesapient/agent-tracker/browser";

void initVisitorIdentity();

// After login/signup, attach your own application user ID.
void identifyUser("user_123");

// Track explicit outcomes when they happen.
void trackConversion("signup.completed", { plan: "pro" }, {
  apiKey: "sap_...",
});

The helper has no third-party dependency. It falls back to storage/random IDs when browser fingerprinting is unavailable. The userId value is your customer application's user ID, not a Sapient account ID.

Conversion Tracking

Use conversions for outcomes you care about: sign-up intent, sign-up complete, demo requested, checkout complete, API key created. Sapient does not need every click on the site to close the attribution loop.

Programmatic conversion:

import { trackConversion } from "@usesapient/agent-tracker/browser";

await trackConversion(
  "signup.completed",
  { plan: "pro" },
  {
    apiKey: "sap_...",
    eventStage: "completed",
  },
);

Declarative pixel for CTA clicks:

<script
  type="module"
  src="https://cdn.example.com/sapient-browser.mjs"
  data-sapient-key="sap_..."
></script>

<button
  data-sapient-conversion="signup.click"
  data-sapient-conversion-stage="intent"
  data-sapient-label="Sign in"
>
  Sign in
</button>

For first-party deployments, point conversionEndpoint or data-sapient-endpoint at your own proxy and add the Sapient API key on the server instead of exposing it in the browser.

Next.js

// middleware.ts
import { withAgentTracking } from "@usesapient/agent-tracker/next";

export default withAgentTracking({ apiKey: "sap_..." });

export const config = {
  matcher: ["/((?!api|_next|admin).*)"],
};

Vercel (Remix, SvelteKit, Astro, Nuxt)

// middleware.ts
import { withAgentTracking } from "@usesapient/agent-tracker/vercel";

export default withAgentTracking({ apiKey: "sap_..." });

export const config = {
  matcher: "/((?!_next|api|favicon.ico|assets|.*\\..*).*)",
};

Cloudflare Pages

// functions/_middleware.ts
import { onRequest as withTracking } from "@usesapient/agent-tracker/cloudflare";

export const onRequest = withTracking({ apiKey: "sap_..." });

Cloudflare Workers

import { withAgentTracking } from "@usesapient/agent-tracker/cloudflare";

const handler = {
  async fetch(request, env, ctx) {
    return new Response("Hello!");
  },
};

export default {
  fetch: withAgentTracking({ apiKey: "sap_..." }, handler.fetch),
};

Netlify DNS + Framer/Webflow Proxy

// For Framer/Webflow sites whose custom domain is managed in Netlify DNS:
// netlify/edge-functions/sapient-proxy.ts
import { proxyWithAgentTracking } from "@usesapient/agent-tracker/netlify";

export default proxyWithAgentTracking();

export const config = {
  path: "/*",
};

Set these environment variables on the Netlify proxy site:

SAPIENT_API_KEY=sap_...
ORIGIN_URL=https://your-site.framer.website
SITE_PROVIDER=framer
SAPIENT_TRACKING_SOURCE=netlify_edge_proxy

Netlify DNS-only does not run tracking code. This setup works by pointing the custom domain at a small Netlify proxy site, then forwarding requests to the Framer/Webflow origin.

See examples/netlify-dns-proxy for a minimal Netlify proxy site starter. Do not set ORIGIN_URL to the custom domain; the proxy rejects that loop.

Express

import express from "express";
import { withAgentTracking } from "@usesapient/agent-tracker/express";

const app = express();
app.use(withAgentTracking({ apiKey: "sap_..." }));

Configuration

interface SapientConfig {
  /** Your Sapient API key (starts with sap_). Required. */
  apiKey: string;

  /** Override the track endpoint (for self-hosting). */
  endpoint?: string;

  /** Where the event was captured. Defaults to sdk. */
  trackingSource?: "sdk" | "cloudflare_worker" | "netlify_edge_proxy";

  /** Site builder/provider for no-code custom-domain setups. */
  provider?: "framer" | "webflow" | "netlify" | "static" | "custom";

  /** Apex domain used to ignore self-referrals across subdomains. */
  siteDomain?: string;
}

Browser Identity Options

import { initVisitorIdentity } from "@usesapient/agent-tracker/browser";

void initVisitorIdentity({
  cookieDomain: ".example.com", // optional
  siteDomain: "example.com", // recommended when using subdomains
  userId: "user_123", // optional, can also call identifyUser later
  apiKey: "sap_...", // optional, only needed for direct browser conversions
  conversionEndpoint: "/api/sapient/conversion", // optional first-party proxy
  visitorMaxAgeDays: 365,
  sessionMaxAgeMinutes: 30,
  cookieSameSite: "Lax",
});

Environment Variables

Instead of passing apiKey directly, you can set:

SAPIENT_API_KEY=sap_...
# or
SAPIENT_TRACKING_KEY=sap_...

# Optional for Cloudflare Worker tracking
SAPIENT_TRACKING_SOURCE=cloudflare_worker

# Optional for Netlify DNS proxy tracking
SAPIENT_TRACKING_SOURCE=netlify_edge_proxy
ORIGIN_URL=https://your-site.framer.website
SITE_PROVIDER=framer

Then initialize without config:

// Next.js
import { withAgentTracking } from "@usesapient/agent-tracker/next";
export default withAgentTracking({} as any); // reads from env

Wrapping Existing Middleware

All platform wrappers accept an optional second argument to wrap existing middleware:

Next.js

import { withAgentTracking } from "@usesapient/agent-tracker/next";
import { NextResponse } from "next/server";

function myMiddleware(request) {
  // your existing logic
  return NextResponse.next();
}

export default withAgentTracking({ apiKey: "sap_..." }, myMiddleware);

Express

// Tracking middleware runs before your routes
app.use(withAgentTracking({ apiKey: "sap_..." }));
app.use(yourOtherMiddleware);

How It Works

The middleware intercepts requests and:

  1. Filters non-page requests — Only tracks requests with Accept: text/html or Accept: text/markdown
  2. Classifies visitors — Detects AI coding agents (Claude Code, Codex, OpenCode) by their unique headers
  3. Identifies browsers — Reads Sapient visitor/session cookies produced by the browser helper
  4. Preserves attribution — Captures current, session, and first-touch attribution while ignoring configured same-site referrers
  5. Sends async — Tracking is non-blocking via waitUntil() or fire-and-forget
  6. Auto-creates sites — Sites are automatically registered when first event is received
  7. Records outcomes — Browser helpers can send explicit conversion milestones tied to the same visitor/user

What Gets Tracked

| Field | Source | | ------------------------ | ------------------------------ | | host | Request hostname | | path | Request pathname | | user_agent | User-Agent header | | accept | Accept header | | country | Geo header (platform-specific) | | visitor_id | Browser identity cookie/header | | session_id | Browser identity cookie/header | | fingerprint_hash | Browser identity cookie/header | | fingerprint_source | Browser identity cookie/header | | fingerprint_confidence | Browser identity cookie/header | | external_user_id | Browser identity cookie/header | | attribution_source | First-touch cookie/header | | attribution_medium | First-touch cookie/header | | attribution_campaign | First-touch cookie/header | | attribution_referrer | First-touch cookie/header | | attribution_landing_page | First-touch cookie/header | | first_touch | First valid acquisition touch | | session_touch | Source that began the session | | current_touch | Source resolved from this request |

Detection Logic

AI coding agents are identified by requesting text/markdown in their Accept header — browsers never do this.

| Agent | Detection | | ----------- | -------------------------------------- | | Claude Code | axios in UA + text/markdown | | Codex | chatgpt-user in UA + text/markdown | | OpenCode | Quality weights in Accept (q=0.9) |

API Reference

Core Functions

import { trackVisit, init } from "@usesapient/agent-tracker";

// Initialize globally (optional - can pass config per-call)
init({ apiKey: "sap_..." });

// Track a visit manually
const result = await trackVisit({
  host: "docs.example.com",
  path: "/getting-started",
  userAgent: "...",
  accept: "text/html,text/markdown",
  country: "US",
  visitorId: "visitor_...",
  sessionId: "session_...",
  externalUserId: "user_123",
  attributionSource: "chatgpt",
  attributionCampaign: "agent_analytics_demo",
});
// => { ok: true, category: "coding-agent", agent: "claude-code" }

Browser Functions

import {
  clearUserIdentity,
  getVisitorIdentity,
  identifyUser,
  initSapientPixel,
  initVisitorIdentity,
  trackConversion,
} from "@usesapient/agent-tracker/browser";

const identity = await getVisitorIdentity({ siteDomain: "example.com" });
// => { visitorId, sessionId, firstTouch, sessionTouch, currentTouch, ... }

void initVisitorIdentity(); // fire-and-forget cookie initializer
await identifyUser("user_123");
await trackConversion("signup.completed", { plan: "pro" });
void initSapientPixel({ apiKey: "sap_..." }); // enables data-sapient-conversion clicks
clearUserIdentity();

Platform Exports

Each platform export includes:

// Next.js
import {
  withAgentTracking,
  trackVisit,
  init,
} from "@usesapient/agent-tracker/next";

// Vercel
import {
  withAgentTracking,
  trackRequest,
  trackVisit,
  init,
} from "@usesapient/agent-tracker/vercel";

// Cloudflare
import {
  withAgentTracking,
  onRequest,
  trackRequest,
  trackVisit,
  init,
} from "@usesapient/agent-tracker/cloudflare";

// Netlify
import {
  withAgentTracking,
  proxyWithAgentTracking,
  trackRequest,
  trackProxyRequest,
  trackVisit,
  init,
} from "@usesapient/agent-tracker/netlify";

// Express
import {
  withAgentTracking,
  trackVisit,
  init,
} from "@usesapient/agent-tracker/express";

// Browser
import {
  clearUserIdentity,
  getVisitorIdentity,
  identifyUser,
  initSapientPixel,
  initVisitorIdentity,
  trackConversion,
} from "@usesapient/agent-tracker/browser";

Types

import type {
  SapientConfig,
  TrackOptions,
  TrackResult,
} from "@usesapient/agent-tracker";

interface TrackResult {
  ok: boolean;
  category?: "coding-agent" | "browsing-agent" | "bot" | "human";
  agent?: string;
  skipped?: string; // "not-page-view" if filtered
  error?: string;
}

Browser identity cookies can also be supplied as headers when you proxy requests: x-sapient-visitor-id, x-sapient-session-id, x-sapient-fingerprint-hash, x-sapient-fingerprint-source, and x-sapient-fingerprint-confidence.

Performance

  • Non-blocking — Uses waitUntil() on edge platforms, fire-and-forget on Express
  • Timeout — 2.5s max, silently fails on timeout
  • Filtered early — Asset requests (images, CSS, JS) are skipped before any network call

License

MIT