@tactical-app/agent-analytics
v0.1.0
Published
First-party AI crawler & shopping-agent detection middleware for e-commerce. Detects AI traffic (ChatGPT, Perplexity, Claude, Google AI, and more) that JavaScript analytics can't see — Express and Next.js adapters.
Maintainers
Readme
@tactical-app/agent-analytics
First-party detection of AI crawlers and shopping agents on your e-commerce store — the traffic JavaScript analytics can't see.
Most AI retrieval crawlers (the bots feeding ChatGPT, Perplexity, Claude, and Google's AI answers) don't run JavaScript, so a client-side snippet never sees them. This middleware detects them server-side from the request User-Agent and reports matched hits to Tactical — where they're turned into the AEO measurement funnel (crawler fetches → agent sessions → AI-referred visits → revenue).
- Zero impact on humans. Only a matched AI User-Agent triggers anything; everything else is a single in-memory lookup.
- Fire-and-forget. The report is a non-blocking
fetchwith a hard 500 ms abort that never throws into your request path. - No PII. Only the User-Agent, request URL, and IP of the matched crawler are sent.
Install
npm install @tactical-app/agent-analyticsYou need a Tactical account and API key — free at tactical-app.work.
Express
import express from "express";
import { tacticalExpress } from "@tactical-app/agent-analytics/express";
const app = express();
app.use(tacticalExpress({ shop: "mystore.com", key: process.env.TACTICAL_KEY }));Next.js (middleware)
// middleware.ts
import { NextResponse } from "next/server";
import { reportAgentTraffic } from "@tactical-app/agent-analytics/next";
export function middleware(request) {
reportAgentTraffic(request, { shop: "mystore.com", key: process.env.TACTICAL_KEY });
return NextResponse.next();
}Config
| Option | Default | Notes |
|---|---|---|
| shop | — (required) | Your store's canonical domain, e.g. "mystore.com". |
| key | — | Tactical API key. Server-side only — never expose it to the browser. |
| endpoint | https://tactical-app.work/api/ingest/server | Override for self-host/testing. |
| timeoutMs | 500 | Hard abort for the fire-and-forget report. |
| trustProxy | false | Read the client IP from X-Forwarded-For — only enable behind a trusted proxy/CDN (the header is spoofable otherwise). |
Core (framework-agnostic)
Roll your own integration with the two primitives:
import { detect, report } from "@tactical-app/agent-analytics";
const match = detect(userAgent); // AgentMatch | null
if (match) {
void report({ match, ua: userAgent, url, ip }, { shop: "mystore.com", key });
}detect() is pure and synchronous. report() is fire-and-forget — don't await it in a latency-sensitive path.
Cloudflare Worker (reverse-proxy recipe)
On Cloudflare in front of your origin? Detect at the edge with a Worker — no origin changes:
import { detect } from "@tactical-app/agent-analytics";
export default {
async fetch(request, env, ctx) {
const ua = request.headers.get("user-agent") || "";
const match = detect(ua);
if (match) {
// Report without blocking the response.
ctx.waitUntil(
fetch("https://tactical-app.work/api/ingest/server", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Tactical-Key": env.TACTICAL_KEY },
body: JSON.stringify({
shop: "mystore.com",
key: env.TACTICAL_KEY,
ua,
url: request.url,
ip: request.headers.get("cf-connecting-ip") || "",
ts: Date.now(),
source: "cf-worker",
}),
}).catch(() => {}),
);
}
return fetch(request); // pass through to origin
},
};Log-file ingestion (parsing server access logs offline) is intentionally not built here — it'll be added only if there's demand. Open an issue if you need it.
License
MIT
