@traceten/ai-crawl 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.
Files changed (74) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +260 -0
  4. package/dist/adapters/cloudflare-pages.d.ts +42 -0
  5. package/dist/adapters/cloudflare-pages.d.ts.map +1 -0
  6. package/dist/adapters/cloudflare-pages.js +46 -0
  7. package/dist/adapters/cloudflare-pages.js.map +1 -0
  8. package/dist/adapters/cloudflare-workers.d.ts +41 -0
  9. package/dist/adapters/cloudflare-workers.d.ts.map +1 -0
  10. package/dist/adapters/cloudflare-workers.js +49 -0
  11. package/dist/adapters/cloudflare-workers.js.map +1 -0
  12. package/dist/adapters/express.d.ts +49 -0
  13. package/dist/adapters/express.d.ts.map +1 -0
  14. package/dist/adapters/express.js +91 -0
  15. package/dist/adapters/express.js.map +1 -0
  16. package/dist/adapters/hono.d.ts +48 -0
  17. package/dist/adapters/hono.d.ts.map +1 -0
  18. package/dist/adapters/hono.js +64 -0
  19. package/dist/adapters/hono.js.map +1 -0
  20. package/dist/adapters/next.d.ts +41 -0
  21. package/dist/adapters/next.d.ts.map +1 -0
  22. package/dist/adapters/next.js +70 -0
  23. package/dist/adapters/next.js.map +1 -0
  24. package/dist/config.d.ts +21 -0
  25. package/dist/config.d.ts.map +1 -0
  26. package/dist/config.js +99 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/crawlers.d.ts +68 -0
  29. package/dist/crawlers.d.ts.map +1 -0
  30. package/dist/crawlers.js +248 -0
  31. package/dist/crawlers.js.map +1 -0
  32. package/dist/filter.d.ts +33 -0
  33. package/dist/filter.d.ts.map +1 -0
  34. package/dist/filter.js +169 -0
  35. package/dist/filter.js.map +1 -0
  36. package/dist/index.d.ts +21 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +20 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/ip.d.ts +35 -0
  41. package/dist/ip.d.ts.map +1 -0
  42. package/dist/ip.js +109 -0
  43. package/dist/ip.js.map +1 -0
  44. package/dist/matcher.d.ts +44 -0
  45. package/dist/matcher.d.ts.map +1 -0
  46. package/dist/matcher.js +111 -0
  47. package/dist/matcher.js.map +1 -0
  48. package/dist/report.d.ts +43 -0
  49. package/dist/report.d.ts.map +1 -0
  50. package/dist/report.js +116 -0
  51. package/dist/report.js.map +1 -0
  52. package/dist/track.d.ts +30 -0
  53. package/dist/track.d.ts.map +1 -0
  54. package/dist/track.js +96 -0
  55. package/dist/track.js.map +1 -0
  56. package/dist/types.d.ts +184 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +11 -0
  59. package/dist/types.js.map +1 -0
  60. package/package.json +87 -0
  61. package/src/adapters/cloudflare-pages.ts +64 -0
  62. package/src/adapters/cloudflare-workers.ts +70 -0
  63. package/src/adapters/express.ts +113 -0
  64. package/src/adapters/hono.ts +89 -0
  65. package/src/adapters/next.ts +87 -0
  66. package/src/config.ts +127 -0
  67. package/src/crawlers.ts +269 -0
  68. package/src/filter.ts +178 -0
  69. package/src/index.ts +46 -0
  70. package/src/ip.ts +112 -0
  71. package/src/matcher.ts +119 -0
  72. package/src/report.ts +149 -0
  73. package/src/track.ts +117 -0
  74. package/src/types.ts +190 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Local crawler token data — a deliberately-loose COST FILTER, not the source
3
+ * of truth. Traceten's server-side crawler registry decides provider,
4
+ * category, verification and confidence; this list only decides "worth
5
+ * reporting". Keep it small and keep every token anchored against current
6
+ * vendor documentation.
7
+ *
8
+ * Recent reconciliation notes, kept because they explain non-obvious entries:
9
+ * - Deleted `GoogleAgent` (a hyphenation typo of the real `Google-Agent`,
10
+ * which stays), `FacebookBot`, Microsoft `Copilot` and `AliyunBot` — no
11
+ * vendor documents any of them.
12
+ * - Added `Google-GeminiNotebook`, Google's 2026-07-16 rename of
13
+ * `Google-NotebookLM`; the old token is honoured only until August 2026.
14
+ * BOTH are listed on purpose — stale traffic still carries the old one.
15
+ * - Added `MistralAI-Training`, documented on Mistral's crawler page.
16
+ * - Recategorised `GoogleOther` (training → ai_crawler; Google states it
17
+ * "doesn't affect any specific product") and `Bytespider` (training →
18
+ * search_index; ByteDance documents only a Toutiao Search index crawler).
19
+ *
20
+ * DELIBERATE EXCLUSIONS: `Google-Extended` and `Applebot-Extended` are
21
+ * robots.txt control tokens, not crawlers — they never appear as a live
22
+ * user agent, so listing them here would be dead code that can never fire.
23
+ *
24
+ * ⚠️ THE EXCLUSION ONLY ACTUALLY HOLDS FOR `Google-Extended`. Nothing matches
25
+ * it, so it is never reported. `Applebot-Extended` IS reported, by two
26
+ * independent paths:
27
+ * 1. Apple's UA strings carry `+http://www.apple.com/go/applebot`, whose
28
+ * `applebot` substring is a TIER-1 exact token match (anchored between
29
+ * `/` and `)`), so the report lands as agent `Applebot`, training.
30
+ * 2. The bare string `Applebot-Extended` hits the tier-2 alias
31
+ * `{ alias: "applebot" }` below, because prefix anchoring accepts a
32
+ * trailing `-`. That yields provider `apple`, agent null, `ai_crawler`.
33
+ * Neither is a bug — attributing an Apple fetch to Apple is right, and this
34
+ * file is a cost filter, not the classifier. Do NOT write, in code comments
35
+ * or in customer copy, that these tokens are "never reported" — only
36
+ * `Google-Extended` qualifies.
37
+ *
38
+ * All tokens are stored lowercase; matching lowercases the UA once.
39
+ */
40
+ /** Tier 1 — exact agent tokens. First match wins. */
41
+ export const AGENT_TOKENS = [
42
+ // ── answer_fetch · user-triggered live fetches ─────────────────────────
43
+ { token: "chatgpt-user", agent: "ChatGPT-User", provider: "openai", category: "answer_fetch" },
44
+ { token: "claude-user", agent: "Claude-User", provider: "anthropic", category: "answer_fetch" },
45
+ {
46
+ token: "perplexity-user",
47
+ agent: "Perplexity-User",
48
+ provider: "perplexity",
49
+ category: "answer_fetch",
50
+ },
51
+ { token: "google-agent", agent: "Google-Agent", provider: "google", category: "answer_fetch" },
52
+ {
53
+ token: "google-gemininotebook",
54
+ agent: "Google-GeminiNotebook",
55
+ provider: "google",
56
+ category: "answer_fetch",
57
+ },
58
+ // Superseded by Google-GeminiNotebook on 2026-07-16, honoured until August
59
+ // 2026. Kept: traffic already in flight still carries it.
60
+ {
61
+ token: "google-notebooklm",
62
+ agent: "Google-NotebookLM",
63
+ provider: "google",
64
+ category: "answer_fetch",
65
+ },
66
+ {
67
+ token: "google-read-aloud",
68
+ agent: "Google-Read-Aloud",
69
+ provider: "google",
70
+ category: "answer_fetch",
71
+ },
72
+ {
73
+ token: "mistralai-user",
74
+ agent: "MistralAI-User",
75
+ provider: "mistral",
76
+ category: "answer_fetch",
77
+ },
78
+ { token: "amzn-user", agent: "Amzn-User", provider: "amazon", category: "answer_fetch" },
79
+ {
80
+ token: "duckassistbot",
81
+ agent: "DuckAssistBot",
82
+ provider: "duckduckgo",
83
+ category: "answer_fetch",
84
+ },
85
+ {
86
+ token: "meta-externalfetcher",
87
+ agent: "meta-externalfetcher",
88
+ provider: "meta",
89
+ category: "answer_fetch",
90
+ },
91
+ { token: "kimi-user", agent: "Kimi-User", provider: "moonshot", category: "answer_fetch" },
92
+ { token: "xai-searchbot", agent: "xAI-SearchBot", provider: "xai", category: "answer_fetch" },
93
+ { token: "grok-deepsearch", agent: "Grok-DeepSearch", provider: "xai", category: "answer_fetch" },
94
+ { token: "qwen-user", agent: "Qwen-User", provider: "alibaba", category: "answer_fetch" },
95
+ // ── search_index · AI search index crawlers ────────────────────────────
96
+ { token: "oai-searchbot", agent: "OAI-SearchBot", provider: "openai", category: "search_index" },
97
+ {
98
+ token: "claude-searchbot",
99
+ agent: "Claude-SearchBot",
100
+ provider: "anthropic",
101
+ category: "search_index",
102
+ },
103
+ {
104
+ token: "perplexitybot",
105
+ agent: "PerplexityBot",
106
+ provider: "perplexity",
107
+ category: "search_index",
108
+ },
109
+ { token: "googlebot", agent: "Googlebot", provider: "google", category: "search_index" },
110
+ {
111
+ token: "google-inspectiontool",
112
+ agent: "Google-InspectionTool",
113
+ provider: "google",
114
+ category: "search_index",
115
+ },
116
+ {
117
+ token: "mistralai-index",
118
+ agent: "MistralAI-Index",
119
+ provider: "mistral",
120
+ category: "search_index",
121
+ },
122
+ { token: "bingbot", agent: "Bingbot", provider: "microsoft", category: "search_index" },
123
+ { token: "msnbot", agent: "msnbot", provider: "microsoft", category: "search_index" },
124
+ {
125
+ token: "amzn-searchbot",
126
+ agent: "Amzn-SearchBot",
127
+ provider: "amazon",
128
+ category: "search_index",
129
+ },
130
+ {
131
+ token: "meta-webindexer",
132
+ agent: "meta-webindexer",
133
+ provider: "meta",
134
+ category: "search_index",
135
+ },
136
+ {
137
+ token: "kimi-searchbot",
138
+ agent: "Kimi-SearchBot",
139
+ provider: "moonshot",
140
+ category: "search_index",
141
+ },
142
+ { token: "tiktokspider", agent: "TikTokSpider", provider: "bytedance", category: "search_index" },
143
+ { token: "bytespider", agent: "Bytespider", provider: "bytedance", category: "search_index" },
144
+ { token: "baiduspider", agent: "Baiduspider", provider: "baidu", category: "search_index" },
145
+ { token: "youbot", agent: "YouBot", provider: "youcom", category: "search_index" },
146
+ // ── training · corpus crawlers ─────────────────────────────────────────
147
+ { token: "gptbot", agent: "GPTBot", provider: "openai", category: "training" },
148
+ { token: "claudebot", agent: "ClaudeBot", provider: "anthropic", category: "training" },
149
+ {
150
+ token: "mistralai-training",
151
+ agent: "MistralAI-Training",
152
+ provider: "mistral",
153
+ category: "training",
154
+ },
155
+ {
156
+ token: "google-cloudvertexbot",
157
+ agent: "Google-CloudVertexBot",
158
+ provider: "google",
159
+ category: "training",
160
+ },
161
+ { token: "applebot", agent: "Applebot", provider: "apple", category: "training" },
162
+ { token: "amazonbot", agent: "Amazonbot", provider: "amazon", category: "training" },
163
+ {
164
+ token: "meta-externalagent",
165
+ agent: "meta-externalagent",
166
+ provider: "meta",
167
+ category: "training",
168
+ },
169
+ { token: "kimibot", agent: "KimiBot", provider: "moonshot", category: "training" },
170
+ { token: "ccbot", agent: "CCBot", provider: "commoncrawl", category: "training" },
171
+ { token: "erniebot", agent: "ERNIEBot", provider: "baidu", category: "training" },
172
+ { token: "qwenbot", agent: "QwenBot", provider: "alibaba", category: "training" },
173
+ { token: "chatglm-spider", agent: "ChatGLM-Spider", provider: "zhipu", category: "training" },
174
+ { token: "deepseekbot", agent: "DeepSeekBot", provider: "deepseek", category: "training" },
175
+ { token: "cohere-ai", agent: "cohere-ai", provider: "cohere", category: "training" },
176
+ {
177
+ token: "cohere-training-data-crawler",
178
+ agent: "cohere-training-data-crawler",
179
+ provider: "cohere",
180
+ category: "training",
181
+ },
182
+ { token: "ai2bot", agent: "AI2Bot", provider: "allenai", category: "training" },
183
+ // ── ai_crawler · AI-adjacent, uncategorised ────────────────────────────
184
+ { token: "googleother", agent: "GoogleOther", provider: "google", category: "ai_crawler" },
185
+ {
186
+ token: "meta-externalads",
187
+ agent: "meta-externalads",
188
+ provider: "meta",
189
+ category: "ai_crawler",
190
+ },
191
+ {
192
+ token: "facebookexternalhit",
193
+ agent: "facebookexternalhit",
194
+ provider: "meta",
195
+ category: "ai_crawler",
196
+ },
197
+ { token: "oai-adsbot", agent: "OAI-AdsBot", provider: "openai", category: "ai_crawler" },
198
+ { token: "grokbot", agent: "GrokBot", provider: "xai", category: "ai_crawler" },
199
+ { token: "xai-bot", agent: "xAI-Bot", provider: "xai", category: "ai_crawler" },
200
+ { token: "xai-grok", agent: "xAI-Grok", provider: "xai", category: "ai_crawler" },
201
+ { token: "xai-web-crawler", agent: "xAI-Web-Crawler", provider: "xai", category: "ai_crawler" },
202
+ { token: "grok", agent: "Grok", provider: "xai", category: "ai_crawler" },
203
+ { token: "doubaobot", agent: "Doubaobot", provider: "bytedance", category: "ai_crawler" },
204
+ { token: "yiyanbot", agent: "YiyanBot", provider: "baidu", category: "ai_crawler" },
205
+ { token: "tongyibot", agent: "TongyiBot", provider: "alibaba", category: "ai_crawler" },
206
+ ];
207
+ /**
208
+ * Tier 2 — coarse provider aliases, so a NEW agent from a known vendor
209
+ * (e.g. a hypothetical `ChatGPT-Reader/1.0`) is still captured without a
210
+ * package release. Left-boundary-anchored prefix match: the alias must start
211
+ * at the beginning of a token (start of string or after a non-token
212
+ * character), which is what defeats `not-really-GPTBot`-style spoofs while
213
+ * still catching `Claude-NewAgent/1.0`.
214
+ *
215
+ * Tier-2 matches are reported with category `ai_crawler` locally (the server
216
+ * assigns the real category) and are gated by `disableOtherCrawlers`.
217
+ */
218
+ export const PROVIDER_ALIASES = [
219
+ { alias: "gptbot", provider: "openai" },
220
+ { alias: "chatgpt", provider: "openai" },
221
+ { alias: "oai-", provider: "openai" },
222
+ { alias: "claudebot", provider: "anthropic" },
223
+ { alias: "claude-", provider: "anthropic" },
224
+ { alias: "anthropic", provider: "anthropic" },
225
+ { alias: "perplexity", provider: "perplexity" },
226
+ { alias: "mistralai", provider: "mistral" },
227
+ { alias: "duckassist", provider: "duckduckgo" },
228
+ { alias: "kimi", provider: "moonshot" },
229
+ { alias: "grok", provider: "xai" },
230
+ { alias: "xai-", provider: "xai" },
231
+ { alias: "qwen", provider: "alibaba" },
232
+ { alias: "tongyi", provider: "alibaba" },
233
+ { alias: "bytespider", provider: "bytedance" },
234
+ { alias: "doubao", provider: "bytedance" },
235
+ { alias: "baiduspider", provider: "baidu" },
236
+ { alias: "erniebot", provider: "baidu" },
237
+ { alias: "chatglm", provider: "zhipu" },
238
+ { alias: "deepseek", provider: "deepseek" },
239
+ { alias: "cohere", provider: "cohere" },
240
+ { alias: "ai2bot", provider: "allenai" },
241
+ { alias: "ccbot", provider: "commoncrawl" },
242
+ { alias: "youbot", provider: "youcom" },
243
+ { alias: "meta-", provider: "meta" },
244
+ { alias: "applebot", provider: "apple" },
245
+ { alias: "amazonbot", provider: "amazon" },
246
+ { alias: "amzn-", provider: "amazon" },
247
+ ];
248
+ //# sourceMappingURL=crawlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawlers.js","sourceRoot":"","sources":["../src/crawlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Local pre-filter — everything here runs BEFORE any network call, so the
3
+ * overwhelming majority of requests (assets, API calls, browser subresource
4
+ * fetches) cost nothing.
5
+ *
6
+ * Order of checks:
7
+ * 1. Method gate (GET/HEAD by default)
8
+ * 2. sec-fetch-dest deny-list (browser subresource fetches; crawlers
9
+ * don't send the header, so absence passes)
10
+ * 3. Crawler-facing path re-allow — /robots.txt, /llms.txt,
11
+ * /llms-full.txt, *sitemap*.xml skip BOTH deny-lists below. A GPTBot
12
+ * hit on /llms.txt is one of the highest-signal events available and a
13
+ * naive `.txt` rule would eat it.
14
+ * 4. Path-prefix deny-list (extendable, never replaceable)
15
+ * 5. Static-extension deny-list (extendable, never replaceable)
16
+ */
17
+ import type { ResolvedAiCrawlConfig } from "./types.js";
18
+ /** Built-in path-prefix deny-list (plain prefix match on the pathname). */
19
+ export declare const DEFAULT_DENY_PATH_PREFIXES: readonly string[];
20
+ /** Built-in static-extension deny-list (leading dot, lowercase). */
21
+ export declare const DEFAULT_DENY_EXTENSIONS: readonly string[];
22
+ /** `sec-fetch-dest` values that identify browser subresource fetches. */
23
+ export declare const DENY_SEC_FETCH_DESTS: readonly string[];
24
+ /** True for /robots.txt, /llms.txt, /llms-full.txt and *sitemap*.xml paths. */
25
+ export declare function isCrawlerFacingPath(pathname: string): boolean;
26
+ /** Extract a lowercase pathname from an absolute URL or a bare path. Never throws. */
27
+ export declare function extractPathname(url: string): string;
28
+ /**
29
+ * The full local pre-filter. Returns `true` when the request should proceed
30
+ * to UA matching. Never throws.
31
+ */
32
+ export declare function passesPreFilter(cfg: ResolvedAiCrawlConfig, method: string | null | undefined, url: string | null | undefined, secFetchDest: string | null | undefined): boolean;
33
+ //# sourceMappingURL=filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAmBvD,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAsCpD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAYjD,CAAC;AAQF,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBnD;AAYD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,qBAAqB,EAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CA4BT"}
package/dist/filter.js ADDED
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Local pre-filter — everything here runs BEFORE any network call, so the
3
+ * overwhelming majority of requests (assets, API calls, browser subresource
4
+ * fetches) cost nothing.
5
+ *
6
+ * Order of checks:
7
+ * 1. Method gate (GET/HEAD by default)
8
+ * 2. sec-fetch-dest deny-list (browser subresource fetches; crawlers
9
+ * don't send the header, so absence passes)
10
+ * 3. Crawler-facing path re-allow — /robots.txt, /llms.txt,
11
+ * /llms-full.txt, *sitemap*.xml skip BOTH deny-lists below. A GPTBot
12
+ * hit on /llms.txt is one of the highest-signal events available and a
13
+ * naive `.txt` rule would eat it.
14
+ * 4. Path-prefix deny-list (extendable, never replaceable)
15
+ * 5. Static-extension deny-list (extendable, never replaceable)
16
+ */
17
+ /** Built-in path-prefix deny-list (plain prefix match on the pathname). */
18
+ export const DEFAULT_DENY_PATH_PREFIXES = [
19
+ "/api",
20
+ "/_next",
21
+ "/_nuxt",
22
+ "/_astro",
23
+ "/static",
24
+ "/assets",
25
+ "/public",
26
+ "/images",
27
+ "/img",
28
+ "/fonts",
29
+ "/favicon",
30
+ "/build",
31
+ "/dist",
32
+ "/admin",
33
+ "/webhook",
34
+ "/webhooks",
35
+ "/cdn-cgi",
36
+ "/.well-known",
37
+ ];
38
+ /** Built-in static-extension deny-list (leading dot, lowercase). */
39
+ export const DEFAULT_DENY_EXTENSIONS = [
40
+ ".js",
41
+ ".mjs",
42
+ ".cjs",
43
+ ".css",
44
+ ".map",
45
+ ".json",
46
+ ".xml",
47
+ ".txt",
48
+ ".ico",
49
+ ".png",
50
+ ".jpg",
51
+ ".jpeg",
52
+ ".gif",
53
+ ".webp",
54
+ ".avif",
55
+ ".svg",
56
+ ".bmp",
57
+ ".tiff",
58
+ ".woff",
59
+ ".woff2",
60
+ ".ttf",
61
+ ".otf",
62
+ ".eot",
63
+ ".mp4",
64
+ ".webm",
65
+ ".avi",
66
+ ".mov",
67
+ ".mp3",
68
+ ".wav",
69
+ ".ogg",
70
+ ".flac",
71
+ ".zip",
72
+ ".gz",
73
+ ".tar",
74
+ ".rar",
75
+ ".7z",
76
+ ".wasm",
77
+ ];
78
+ /** `sec-fetch-dest` values that identify browser subresource fetches. */
79
+ export const DENY_SEC_FETCH_DESTS = [
80
+ "audio",
81
+ "embed",
82
+ "font",
83
+ "image",
84
+ "manifest",
85
+ "object",
86
+ "script",
87
+ "style",
88
+ "track",
89
+ "video",
90
+ "worker",
91
+ ];
92
+ /**
93
+ * Crawler-facing paths re-allowed PAST both deny-lists. Exact matches plus
94
+ * the `*sitemap*.xml` pattern.
95
+ */
96
+ const CRAWLER_PATH_EXACT = ["/robots.txt", "/llms.txt", "/llms-full.txt"];
97
+ /** True for /robots.txt, /llms.txt, /llms-full.txt and *sitemap*.xml paths. */
98
+ export function isCrawlerFacingPath(pathname) {
99
+ if (CRAWLER_PATH_EXACT.includes(pathname))
100
+ return true;
101
+ return pathname.endsWith(".xml") && pathname.includes("sitemap");
102
+ }
103
+ /** Extract a lowercase pathname from an absolute URL or a bare path. Never throws. */
104
+ export function extractPathname(url) {
105
+ try {
106
+ let path = url;
107
+ // Strip scheme://host for absolute URLs without allocating a URL object.
108
+ const schemeIdx = path.indexOf("://");
109
+ if (schemeIdx !== -1) {
110
+ const pathStart = path.indexOf("/", schemeIdx + 3);
111
+ path = pathStart === -1 ? "/" : path.slice(pathStart);
112
+ }
113
+ const q = path.indexOf("?");
114
+ if (q !== -1)
115
+ path = path.slice(0, q);
116
+ const h = path.indexOf("#");
117
+ if (h !== -1)
118
+ path = path.slice(0, h);
119
+ if (path === "")
120
+ path = "/";
121
+ return path.toLowerCase();
122
+ }
123
+ catch {
124
+ return "/";
125
+ }
126
+ }
127
+ /** Last-segment extension (with dot, lowercase), or `null`. */
128
+ function pathExtension(pathname) {
129
+ const lastSlash = pathname.lastIndexOf("/");
130
+ const lastDot = pathname.lastIndexOf(".");
131
+ if (lastDot === -1 || lastDot < lastSlash || lastDot === pathname.length - 1) {
132
+ return null;
133
+ }
134
+ return pathname.slice(lastDot);
135
+ }
136
+ /**
137
+ * The full local pre-filter. Returns `true` when the request should proceed
138
+ * to UA matching. Never throws.
139
+ */
140
+ export function passesPreFilter(cfg, method, url, secFetchDest) {
141
+ try {
142
+ if (!method || !url)
143
+ return false;
144
+ if (!cfg.allowedMethods.includes(method.toUpperCase()))
145
+ return false;
146
+ if (secFetchDest && DENY_SEC_FETCH_DESTS.includes(secFetchDest.toLowerCase())) {
147
+ return false;
148
+ }
149
+ const pathname = extractPathname(url);
150
+ // Crawler-facing paths skip both deny-lists.
151
+ if (isCrawlerFacingPath(pathname))
152
+ return true;
153
+ // Segment-boundary prefix match: `/api` denies `/api` and `/api/users`
154
+ // but NOT `/apidocs-overview`; `/public` does not eat `/publications`.
155
+ // A plain startsWith here silently unreports real crawls on real paths.
156
+ for (const prefix of cfg.denyPathPrefixes) {
157
+ if (pathname === prefix || pathname.startsWith(prefix + "/"))
158
+ return false;
159
+ }
160
+ const ext = pathExtension(pathname);
161
+ if (ext !== null && cfg.denyExtensions.includes(ext))
162
+ return false;
163
+ return true;
164
+ }
165
+ catch {
166
+ return false;
167
+ }
168
+ }
169
+ //# sourceMappingURL=filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.js","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @traceten/ai-crawl — server-side AI crawler tracking.
3
+ *
4
+ * AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) never execute
5
+ * JavaScript, so the browser snippet cannot see them. The observation point
6
+ * has to be the customer's server — this package is that observation point.
7
+ * It pre-filters locally, matches user agents with anchored tokens, and
8
+ * reports plausible AI crawls to `POST /v1/ai-crawls`. Classification,
9
+ * verification and confidence are decided server-side.
10
+ *
11
+ * Adapters: `@traceten/ai-crawl/next`, `/cloudflare-pages`,
12
+ * `/cloudflare-workers`, `/express`, `/hono`.
13
+ */
14
+ export { AUTH_TOKEN_PREFIX, DEFAULT_ALLOWED_METHODS, DEFAULT_ENDPOINT, defineAiCrawlConfig, isResolvedConfig, } from "./config.js";
15
+ export { DEFAULT_DENY_EXTENSIONS, DEFAULT_DENY_PATH_PREFIXES, DENY_SEC_FETCH_DESTS, isCrawlerFacingPath, passesPreFilter, } from "./filter.js";
16
+ export { matchCrawler } from "./matcher.js";
17
+ export { resolveCrawlerIp } from "./ip.js";
18
+ export { REPORT_TIMEOUT_MS, buildPayload, sendReport, type SendOptions } from "./report.js";
19
+ export { evaluateRequest, factsFromFetchRequest, trackFacts, type FetchLikeRequest, } from "./track.js";
20
+ export type { AiCrawlConfig, AiCrawlDeliveryError, AiCrawlWirePayload, CrawlerCategory, CrawlerMatch, RequestFacts, ResolvedAiCrawlConfig, } from "./types.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @traceten/ai-crawl — server-side AI crawler tracking.
3
+ *
4
+ * AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) never execute
5
+ * JavaScript, so the browser snippet cannot see them. The observation point
6
+ * has to be the customer's server — this package is that observation point.
7
+ * It pre-filters locally, matches user agents with anchored tokens, and
8
+ * reports plausible AI crawls to `POST /v1/ai-crawls`. Classification,
9
+ * verification and confidence are decided server-side.
10
+ *
11
+ * Adapters: `@traceten/ai-crawl/next`, `/cloudflare-pages`,
12
+ * `/cloudflare-workers`, `/express`, `/hono`.
13
+ */
14
+ export { AUTH_TOKEN_PREFIX, DEFAULT_ALLOWED_METHODS, DEFAULT_ENDPOINT, defineAiCrawlConfig, isResolvedConfig, } from "./config.js";
15
+ export { DEFAULT_DENY_EXTENSIONS, DEFAULT_DENY_PATH_PREFIXES, DENY_SEC_FETCH_DESTS, isCrawlerFacingPath, passesPreFilter, } from "./filter.js";
16
+ export { matchCrawler } from "./matcher.js";
17
+ export { resolveCrawlerIp } from "./ip.js";
18
+ export { REPORT_TIMEOUT_MS, buildPayload, sendReport } from "./report.js";
19
+ export { evaluateRequest, factsFromFetchRequest, trackFacts, } from "./track.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,UAAU,GAEX,MAAM,YAAY,CAAC"}
package/dist/ip.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Crawler IP resolution.
3
+ *
4
+ * The crawler's TCP connection terminates at the CUSTOMER's server, so the
5
+ * crawler IP is only observable here — Traceten's edge sees the customer's
6
+ * origin IP, not the crawler's. This module
7
+ * derives the best-available value; the payload OMITS the field rather than
8
+ * send a wrong one.
9
+ *
10
+ * Resolution order:
11
+ * 1. `cf-connecting-ip` — ONLY when `trustCfConnectingIp` is set. The
12
+ * Cloudflare adapters set it automatically (the platform strips and
13
+ * rewrites the header there); anywhere else it is client-forgeable,
14
+ * and a forged vendor-range IP paired with a vendor UA is exactly the
15
+ * spoof the server-side verification exists to catch.
16
+ * 2. `x-forwarded-for` — ONLY when `trustProxy` is true, selecting the
17
+ * entry `proxyDepth` hops from the right (each trusted proxy appends
18
+ * one entry; anything further left is attacker-controllable)
19
+ * 3. Socket remote address, where the runtime exposes one
20
+ */
21
+ import type { RequestFacts, ResolvedAiCrawlConfig } from "./types.js";
22
+ /** Normalise a candidate; returns `undefined` when it is not a plausible IP. */
23
+ export declare function normalizeIp(raw: string | null | undefined): string | undefined;
24
+ /**
25
+ * Select the client entry from an `x-forwarded-for` value given the number
26
+ * of trusted proxies that append to it. Returns `undefined` when the chain
27
+ * is shorter than `proxyDepth` (a wrong value is worse than none).
28
+ */
29
+ export declare function ipFromForwardedFor(headerValue: string, proxyDepth: number): string | undefined;
30
+ /**
31
+ * Resolve the crawler IP from a request. Returns `undefined` when no
32
+ * trustworthy value is derivable. Never throws.
33
+ */
34
+ export declare function resolveCrawlerIp(cfg: ResolvedAiCrawlConfig, facts: RequestFacts): string | undefined;
35
+ //# sourceMappingURL=ip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip.d.ts","sourceRoot":"","sources":["../src/ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAQtE,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAqC9E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS9F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,qBAAqB,EAC1B,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,SAAS,CAmBpB"}