From e69b539f887f1b6204fb77f9759e8cc340191842 Mon Sep 17 00:00:00 2001 From: GJ Date: Tue, 21 Apr 2026 01:48:41 +0200 Subject: [PATCH] feat: add skipBrowsers option to capture bots + coding agents only New option: skipBrowsers: true captures AI bots (ClaudeBot, GPTBot, etc.) and coding agents (axios, curl, node-fetch, Electron) but skips regular browsers. Use when client-side analytics already handles browser traffic. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/track.ts | 4 +++- src/types.ts | 8 +++++++- test/track.test.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/track.ts b/src/track.ts index 557924a..399b16e 100644 --- a/src/track.ts +++ b/src/track.ts @@ -1,4 +1,4 @@ -import { classifyAgent, isAiBot } from './bots.js' +import { classifyAgent, isAiBot, isHttpClient } from './bots.js' import { hashId } from './hash.js' import type { TrackVisitOptions } from './types.js' @@ -18,7 +18,9 @@ export async function trackVisit( const userAgent = req.headers.get('user-agent') || '' const onlyBots = opts.onlyBots ?? false + const skipBrowsers = opts.skipBrowsers ?? false if (onlyBots && !isAiBot(userAgent)) return + if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) return let pathname = '/' let originFromUrl = '' diff --git a/src/types.ts b/src/types.ts index cc84637..f3d3f86 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,7 +18,7 @@ export interface TrackVisitOptions { */ source?: string /** - * Event name. Defaults to `'doc_view'`. + * Event name. Defaults to `'agent_visit'`. */ eventName?: string /** @@ -27,6 +27,12 @@ export interface TrackVisitOptions { * coding-agent traffic that uses HTTP-library UAs like axios or curl). */ onlyBots?: boolean + /** + * When `true`, capture AI bots and coding agents (HTTP clients like axios, + * curl, node-fetch) but skip regular browsers. Use this when client-side + * analytics already handles browser traffic. Defaults to `false`. + */ + skipBrowsers?: boolean /** * Extra properties merged into the captured event. Useful for tagging the * site (`{ site: 'docs' }`) or any other dimension. diff --git a/test/track.test.ts b/test/track.test.ts index 305805e..d89706f 100644 --- a/test/track.test.ts +++ b/test/track.test.ts @@ -109,6 +109,37 @@ describe('trackVisit', () => { expect(event.properties.is_ai_bot).toBe(false) }) + it('skipBrowsers captures AI bots', async () => { + const spy = vi.fn() + await trackVisit( + makeRequest('https://example.com/page', { 'user-agent': 'ClaudeBot/1.0' }), + { analytics: customAnalytics(spy), skipBrowsers: true } + ) + expect(spy).toHaveBeenCalledOnce() + }) + + it('skipBrowsers captures coding agents (HTTP clients)', async () => { + const spy = vi.fn() + await trackVisit( + makeRequest('https://example.com/page', { 'user-agent': 'axios/1.6.0' }), + { analytics: customAnalytics(spy), skipBrowsers: true } + ) + expect(spy).toHaveBeenCalledOnce() + const event = spy.mock.calls[0]![0] as CaptureEvent + expect(event.properties.coding_agent_hint).toBe(true) + }) + + it('skipBrowsers skips regular browsers', async () => { + const spy = vi.fn() + await trackVisit( + makeRequest('https://example.com/page', { + 'user-agent': 'Mozilla/5.0 (Macintosh) Chrome/120' + }), + { analytics: customAnalytics(spy), skipBrowsers: true } + ) + expect(spy).not.toHaveBeenCalled() + }) + it('honours a custom event name', async () => { const spy = vi.fn() await trackVisit(