@mentionable/tracker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @mentionable/tracker
2
+
3
+ Track AI bot crawls on your website. Monitor when GPTBot, ClaudeBot, PerplexityBot, Grok and others crawl your pages.
4
+
5
+ See which pages AI models are indexing, how often they come back, and correlate crawl data with your AI visibility on [Mentionable](https://mentionable.io).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @mentionable/tracker
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Next.js
16
+
17
+ ```ts
18
+ // middleware.ts
19
+ import { withMentionable } from '@mentionable/tracker/next'
20
+
21
+ export default withMentionable({
22
+ apiKey: process.env.MENTIONABLE_API_KEY!,
23
+ })
24
+
25
+ export const config = {
26
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
27
+ }
28
+ ```
29
+
30
+ That's it. Every AI bot crawl is now tracked in your [Mentionable dashboard](https://mentionable.io).
31
+
32
+ ### Cloudflare Worker
33
+
34
+ ```ts
35
+ // src/index.ts
36
+ import { withMentionable } from '@mentionable/tracker/cloudflare'
37
+
38
+ export default withMentionable({
39
+ apiKey: 'mtbl_xxxxx', // or use env: true to read from MENTIONABLE_API_KEY env var
40
+ })
41
+ ```
42
+
43
+ ```bash
44
+ npx wrangler deploy
45
+ ```
46
+
47
+ Works in front of any origin: Shopify, WordPress, custom backends. The Worker proxies the request and reports crawl data without adding any latency.
48
+
49
+ ### Existing Middleware (Next.js)
50
+
51
+ If you already have a `middleware.ts`, use the handler directly:
52
+
53
+ ```ts
54
+ // middleware.ts
55
+ import { NextResponse } from 'next/server'
56
+ import { createMentionable } from '@mentionable/tracker/next'
57
+
58
+ const mentionable = createMentionable({
59
+ apiKey: process.env.MENTIONABLE_API_KEY!,
60
+ })
61
+
62
+ export async function middleware(req) {
63
+ // your existing logic
64
+ mentionable.track(req)
65
+
66
+ return NextResponse.next()
67
+ }
68
+ ```
69
+
70
+ ### Existing Cloudflare Worker
71
+
72
+ ```ts
73
+ import { createMentionable } from '@mentionable/tracker/cloudflare'
74
+
75
+ const mentionable = createMentionable({
76
+ apiKey: 'mtbl_xxxxx',
77
+ })
78
+
79
+ export default {
80
+ async fetch(request, env, ctx) {
81
+ // your existing logic
82
+ mentionable.track(request, ctx)
83
+
84
+ return fetch(request)
85
+ },
86
+ }
87
+ ```
88
+
89
+ ## What Gets Tracked
90
+
91
+ The tracker sends minimal, non-sensitive data to the Mentionable API:
92
+
93
+ | Field | Description |
94
+ | -------- | ---------------------------- |
95
+ | `ua` | User-Agent header |
96
+ | `path` | Request path (e.g. `/pricing`) |
97
+ | `host` | Hostname |
98
+ | `method` | HTTP method |
99
+ | `ip` | Client IP (for bot identification) |
100
+ | `status` | Response status code |
101
+ | `t` | Timestamp |
102
+
103
+ No cookies, no request bodies, no personal data.
104
+
105
+ ## Configuration
106
+
107
+ ```ts
108
+ withMentionable({
109
+ // Required
110
+ apiKey: 'mtbl_xxxxx',
111
+ })
112
+ ```
113
+
114
+ ## How It Works
115
+
116
+ 1. The middleware intercepts incoming requests
117
+ 2. It forwards the User-Agent + request metadata to the Mentionable API (fire-and-forget, zero latency impact)
118
+ 3. Mentionable detects and classifies the bot server-side
119
+ 4. Data appears in your dashboard within seconds
120
+
121
+ The detection logic lives on Mentionable's servers, not in the package. When new AI bots emerge, they're detected automatically without any update on your end.
122
+
123
+ ## Performance
124
+
125
+ The tracker is designed to have zero impact on your site:
126
+
127
+ - **Non-blocking**: requests are sent with fire-and-forget (no `await`)
128
+ - **Cloudflare**: uses `ctx.waitUntil()` so the response is never delayed
129
+ - **Next.js**: runs on the Edge Runtime, adds < 1ms overhead
130
+ - **Tiny payload**: ~200 bytes per event
131
+
132
+ ## Get Your API Key
133
+
134
+ 1. Sign up at [mentionable.com](https://mentionable.io)
135
+ 2. Go to Settings > API
136
+ 3. Copy your API key (starts with `mtbl_`)
137
+
138
+ ## Links
139
+
140
+ - [Mentionable Dashboard](https://mentionable.io)
141
+
142
+ ## License
143
+
144
+ MIT
@@ -0,0 +1,115 @@
1
+ // src/core.ts
2
+ var API_URL = "https://api.mentionable.io/v1/track";
3
+ var BOTS_URL = "https://api.mentionable.io/v1/bots";
4
+ var REFRESH_INTERVAL = 24 * 60 * 60 * 1e3;
5
+ var FALLBACK_BOTS = [
6
+ // OpenAI
7
+ "GPTBot",
8
+ "ChatGPT-User",
9
+ "OAI-SearchBot",
10
+ // Anthropic
11
+ "ClaudeBot",
12
+ "anthropic-ai",
13
+ // Google
14
+ "Google-Extended",
15
+ "GoogleOther",
16
+ // Perplexity
17
+ "PerplexityBot",
18
+ // Meta
19
+ "Meta-ExternalAgent",
20
+ "FacebookBot",
21
+ // Apple
22
+ "Applebot-Extended",
23
+ // Amazon
24
+ "Amazonbot",
25
+ // ByteDance
26
+ "Bytespider",
27
+ // Common Crawl
28
+ "CCBot",
29
+ // Cohere
30
+ "cohere-ai",
31
+ // Diffbot
32
+ "Diffbot",
33
+ // You.com
34
+ "YouBot",
35
+ // AI2
36
+ "AI2Bot",
37
+ // xAI
38
+ "Grok",
39
+ // Huawei / Asiabot
40
+ "PetalBot",
41
+ // Webz.io
42
+ "Omgilibot",
43
+ // Imagesift
44
+ "ImagesiftBot",
45
+ // Timpi
46
+ "Timpibot"
47
+ ];
48
+ function createBotMatcher() {
49
+ let patterns = FALLBACK_BOTS;
50
+ let lastFetch = 0;
51
+ let pending = null;
52
+ async function fetchPatterns(apiKey) {
53
+ try {
54
+ const res = await fetch(BOTS_URL, {
55
+ headers: { Authorization: `Bearer ${apiKey}` }
56
+ });
57
+ if (res.ok) {
58
+ const data = await res.json();
59
+ if (Array.isArray(data.patterns)) {
60
+ patterns = data.patterns;
61
+ }
62
+ }
63
+ } catch {
64
+ }
65
+ lastFetch = Date.now();
66
+ }
67
+ return {
68
+ /**
69
+ * Kick off a background refresh if the list is stale (>24 h) or never fetched.
70
+ * Returns the pending promise when a fetch was started, `undefined` otherwise.
71
+ */
72
+ refresh(apiKey) {
73
+ if (lastFetch > 0 && Date.now() - lastFetch < REFRESH_INTERVAL) return;
74
+ if (pending) return pending;
75
+ pending = fetchPatterns(apiKey).finally(() => {
76
+ pending = null;
77
+ });
78
+ return pending;
79
+ },
80
+ /** Case-insensitive substring match against the current pattern list. */
81
+ isBot(ua) {
82
+ const lower = ua.toLowerCase();
83
+ return patterns.some((p) => lower.includes(p.toLowerCase()));
84
+ }
85
+ };
86
+ }
87
+ function buildPayload(options) {
88
+ return {
89
+ ua: options.ua,
90
+ path: options.path,
91
+ host: options.host,
92
+ method: options.method,
93
+ ip: options.ip,
94
+ status: options.status ?? 200,
95
+ t: Date.now()
96
+ };
97
+ }
98
+ function sendEvent(event, apiKey) {
99
+ return fetch(API_URL, {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ Authorization: `Bearer ${apiKey}`
104
+ },
105
+ body: JSON.stringify(event)
106
+ }).then(() => {
107
+ }).catch(() => {
108
+ });
109
+ }
110
+
111
+ export {
112
+ createBotMatcher,
113
+ buildPayload,
114
+ sendEvent
115
+ };
@@ -0,0 +1,47 @@
1
+ import { M as MentionableConfig } from './core-CfJZm23p.js';
2
+
3
+ interface CloudflareConfig extends MentionableConfig {
4
+ /** Set to `true` to read the API key from the `MENTIONABLE_API_KEY` env var at runtime. */
5
+ env?: boolean;
6
+ }
7
+ interface CloudflareEnv {
8
+ MENTIONABLE_API_KEY?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ interface CloudflareExecutionContext {
12
+ waitUntil(promise: Promise<unknown>): void;
13
+ passThroughOnException(): void;
14
+ }
15
+ /**
16
+ * Standalone Worker handler that proxies requests to the origin and tracks AI bot crawls.
17
+ *
18
+ * ```ts
19
+ * import { withMentionable } from '@mentionable/tracker/cloudflare'
20
+ *
21
+ * export default withMentionable({
22
+ * apiKey: 'mtbl_xxxxx',
23
+ * })
24
+ * ```
25
+ */
26
+ declare function withMentionable(config: CloudflareConfig): {
27
+ fetch(request: Request, env: CloudflareEnv, ctx: CloudflareExecutionContext): Promise<Response>;
28
+ };
29
+ /**
30
+ * Composable tracker for use inside an existing Cloudflare Worker.
31
+ *
32
+ * ```ts
33
+ * const mentionable = createMentionable({ apiKey: 'mtbl_xxxxx' })
34
+ *
35
+ * export default {
36
+ * async fetch(request, env, ctx) {
37
+ * mentionable.track(request, ctx)
38
+ * return fetch(request)
39
+ * },
40
+ * }
41
+ * ```
42
+ */
43
+ declare function createMentionable(config: MentionableConfig): {
44
+ track(request: Request, ctx: CloudflareExecutionContext): void;
45
+ };
46
+
47
+ export { type CloudflareConfig, MentionableConfig, createMentionable, withMentionable };
@@ -0,0 +1,54 @@
1
+ import {
2
+ buildPayload,
3
+ createBotMatcher,
4
+ sendEvent
5
+ } from "./chunk-JBLZVSYS.js";
6
+
7
+ // src/cloudflare.ts
8
+ function extractPayload(request, status) {
9
+ return buildPayload({
10
+ ua: request.headers.get("user-agent") || "",
11
+ path: new URL(request.url).pathname,
12
+ host: request.headers.get("host") || "",
13
+ method: request.method,
14
+ ip: request.headers.get("cf-connecting-ip") || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
15
+ status
16
+ });
17
+ }
18
+ function resolveApiKey(config, env) {
19
+ return config.env && env.MENTIONABLE_API_KEY || config.apiKey;
20
+ }
21
+ function withMentionable(config) {
22
+ const bots = createBotMatcher();
23
+ return {
24
+ async fetch(request, env, ctx) {
25
+ const apiKey = resolveApiKey(config, env);
26
+ const refreshing = bots.refresh(apiKey);
27
+ if (refreshing) ctx.waitUntil(refreshing);
28
+ const response = await fetch(request);
29
+ const ua = request.headers.get("user-agent") || "";
30
+ if (bots.isBot(ua)) {
31
+ const event = extractPayload(request, response.status);
32
+ ctx.waitUntil(sendEvent(event, apiKey));
33
+ }
34
+ return response;
35
+ }
36
+ };
37
+ }
38
+ function createMentionable(config) {
39
+ const bots = createBotMatcher();
40
+ return {
41
+ track(request, ctx) {
42
+ const refreshing = bots.refresh(config.apiKey);
43
+ if (refreshing) ctx.waitUntil(refreshing);
44
+ const ua = request.headers.get("user-agent") || "";
45
+ if (!bots.isBot(ua)) return;
46
+ const event = extractPayload(request);
47
+ ctx.waitUntil(sendEvent(event, config.apiKey));
48
+ }
49
+ };
50
+ }
51
+ export {
52
+ createMentionable,
53
+ withMentionable
54
+ };
@@ -0,0 +1,5 @@
1
+ interface MentionableConfig {
2
+ apiKey: string;
3
+ }
4
+
5
+ export type { MentionableConfig as M };
package/dist/next.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { M as MentionableConfig } from './core-CfJZm23p.js';
3
+
4
+ /**
5
+ * Standalone middleware that tracks AI bot crawls.
6
+ *
7
+ * ```ts
8
+ * // middleware.ts
9
+ * import { withMentionable } from '@mentionable/tracker/next'
10
+ *
11
+ * export default withMentionable({
12
+ * apiKey: process.env.MENTIONABLE_API_KEY!,
13
+ * })
14
+ * ```
15
+ */
16
+ declare function withMentionable(config: MentionableConfig): (req: NextRequest) => NextResponse<unknown>;
17
+ /**
18
+ * Composable tracker for use inside an existing middleware.
19
+ *
20
+ * ```ts
21
+ * const mentionable = createMentionable({ apiKey: process.env.MENTIONABLE_API_KEY! })
22
+ *
23
+ * export async function middleware(req) {
24
+ * mentionable.track(req)
25
+ * return NextResponse.next()
26
+ * }
27
+ * ```
28
+ */
29
+ declare function createMentionable(config: MentionableConfig): {
30
+ track(req: NextRequest): void;
31
+ };
32
+
33
+ export { MentionableConfig, createMentionable, withMentionable };
package/dist/next.js ADDED
@@ -0,0 +1,44 @@
1
+ import {
2
+ buildPayload,
3
+ createBotMatcher,
4
+ sendEvent
5
+ } from "./chunk-JBLZVSYS.js";
6
+
7
+ // src/next.ts
8
+ import { NextResponse } from "next/server";
9
+ function extractPayload(req) {
10
+ return buildPayload({
11
+ ua: req.headers.get("user-agent") || "",
12
+ path: new URL(req.url).pathname,
13
+ host: req.headers.get("host") || "",
14
+ method: req.method,
15
+ ip: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || ""
16
+ });
17
+ }
18
+ function withMentionable(config) {
19
+ const bots = createBotMatcher();
20
+ return function middleware(req) {
21
+ bots.refresh(config.apiKey);
22
+ const ua = req.headers.get("user-agent") || "";
23
+ if (!bots.isBot(ua)) return NextResponse.next();
24
+ const event = extractPayload(req);
25
+ sendEvent(event, config.apiKey);
26
+ return NextResponse.next();
27
+ };
28
+ }
29
+ function createMentionable(config) {
30
+ const bots = createBotMatcher();
31
+ return {
32
+ track(req) {
33
+ bots.refresh(config.apiKey);
34
+ const ua = req.headers.get("user-agent") || "";
35
+ if (!bots.isBot(ua)) return;
36
+ const event = extractPayload(req);
37
+ sendEvent(event, config.apiKey);
38
+ }
39
+ };
40
+ }
41
+ export {
42
+ createMentionable,
43
+ withMentionable
44
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@mentionable/tracker",
3
+ "version": "0.1.0",
4
+ "description": "Track AI bot crawls on your website. Monitor when GPTBot, ClaudeBot, PerplexityBot, Grok and others crawl your pages.",
5
+ "type": "module",
6
+ "exports": {
7
+ "./next": {
8
+ "types": "./dist/next.d.ts",
9
+ "default": "./dist/next.js"
10
+ },
11
+ "./cloudflare": {
12
+ "types": "./dist/cloudflare.d.ts",
13
+ "default": "./dist/cloudflare.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "devDependencies": {
25
+ "next": "^15.0.0",
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.0.0"
28
+ },
29
+ "peerDependencies": {
30
+ "next": ">=13.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "next": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "keywords": [
38
+ "ai",
39
+ "bot",
40
+ "crawler",
41
+ "tracking",
42
+ "seo",
43
+ "mentionable",
44
+ "gptbot",
45
+ "claudebot",
46
+ "perplexitybot"
47
+ ],
48
+ "license": "MIT",
49
+ "sideEffects": false
50
+ }