@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.
- package/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/adapters/cloudflare-pages.d.ts +42 -0
- package/dist/adapters/cloudflare-pages.d.ts.map +1 -0
- package/dist/adapters/cloudflare-pages.js +46 -0
- package/dist/adapters/cloudflare-pages.js.map +1 -0
- package/dist/adapters/cloudflare-workers.d.ts +41 -0
- package/dist/adapters/cloudflare-workers.d.ts.map +1 -0
- package/dist/adapters/cloudflare-workers.js +49 -0
- package/dist/adapters/cloudflare-workers.js.map +1 -0
- package/dist/adapters/express.d.ts +49 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +91 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +48 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +64 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/next.d.ts +41 -0
- package/dist/adapters/next.d.ts.map +1 -0
- package/dist/adapters/next.js +70 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/crawlers.d.ts +68 -0
- package/dist/crawlers.d.ts.map +1 -0
- package/dist/crawlers.js +248 -0
- package/dist/crawlers.js.map +1 -0
- package/dist/filter.d.ts +33 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +169 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/ip.d.ts +35 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +109 -0
- package/dist/ip.js.map +1 -0
- package/dist/matcher.d.ts +44 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +111 -0
- package/dist/matcher.js.map +1 -0
- package/dist/report.d.ts +43 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +116 -0
- package/dist/report.js.map +1 -0
- package/dist/track.d.ts +30 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +96 -0
- package/dist/track.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +87 -0
- package/src/adapters/cloudflare-pages.ts +64 -0
- package/src/adapters/cloudflare-workers.ts +70 -0
- package/src/adapters/express.ts +113 -0
- package/src/adapters/hono.ts +89 -0
- package/src/adapters/next.ts +87 -0
- package/src/config.ts +127 -0
- package/src/crawlers.ts +269 -0
- package/src/filter.ts +178 -0
- package/src/index.ts +46 -0
- package/src/ip.ts +112 -0
- package/src/matcher.ts +119 -0
- package/src/report.ts +149 -0
- package/src/track.ts +117 -0
- package/src/types.ts +190 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono adapter + generic `Request`/`Response` helper.
|
|
3
|
+
*
|
|
4
|
+
* Hono (structural types — no dependency on hono):
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { aiCrawlerTracking } from "@traceten/ai-crawl/hono";
|
|
7
|
+
* app.use("*", aiCrawlerTracking({ siteId: "…", authToken: "tt_bot_…" }));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Any framework that exposes fetch-API `Request`/`Response`:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { defineAiCrawlConfig } from "@traceten/ai-crawl";
|
|
13
|
+
* import { trackAICrawlerFetch } from "@traceten/ai-crawl/hono";
|
|
14
|
+
* const config = defineAiCrawlConfig({ … });
|
|
15
|
+
* // after producing the response:
|
|
16
|
+
* trackAICrawlerFetch(request, response, config, ctx?.waitUntil?.bind(ctx));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { type FetchLikeRequest } from "../track.js";
|
|
20
|
+
import type { AiCrawlConfig, ResolvedAiCrawlConfig } from "../types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Generic helper for any `Request`/`Response` runtime. Never throws; pass a
|
|
23
|
+
* `waitUntil` when the runtime has one, otherwise delivery is
|
|
24
|
+
* fire-and-forget.
|
|
25
|
+
*/
|
|
26
|
+
export declare function trackAICrawlerFetch(request: FetchLikeRequest, response: {
|
|
27
|
+
status: number;
|
|
28
|
+
} | undefined, config: ResolvedAiCrawlConfig, waitUntil?: ((promise: Promise<unknown>) => void) | undefined): void;
|
|
29
|
+
/** Structural subset of Hono's `Context`. */
|
|
30
|
+
export interface HonoLikeContext {
|
|
31
|
+
req: {
|
|
32
|
+
raw: FetchLikeRequest;
|
|
33
|
+
};
|
|
34
|
+
res: {
|
|
35
|
+
status: number;
|
|
36
|
+
};
|
|
37
|
+
/** Throws on runtimes without an execution context — accessed inside try. */
|
|
38
|
+
executionCtx?: {
|
|
39
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Hono middleware factory. THROWS at construction when the config is
|
|
44
|
+
* invalid; the middleware itself never throws its own errors (route errors
|
|
45
|
+
* propagate untouched).
|
|
46
|
+
*/
|
|
47
|
+
export declare function aiCrawlerTracking(config: AiCrawlConfig | ResolvedAiCrawlConfig): (c: HonoLikeContext, next: () => Promise<void>) => Promise<void>;
|
|
48
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACxC,MAAM,EAAE,qBAAqB,EAC7B,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,GAC5D,IAAI,CAYN;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QAAE,GAAG,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAC/B,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxB,6EAA6E;IAC7E,YAAY,CAAC,EAAE;QAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CAC/D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,GAAG,qBAAqB,GAC5C,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CA0BlE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono adapter + generic `Request`/`Response` helper.
|
|
3
|
+
*
|
|
4
|
+
* Hono (structural types — no dependency on hono):
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { aiCrawlerTracking } from "@traceten/ai-crawl/hono";
|
|
7
|
+
* app.use("*", aiCrawlerTracking({ siteId: "…", authToken: "tt_bot_…" }));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Any framework that exposes fetch-API `Request`/`Response`:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { defineAiCrawlConfig } from "@traceten/ai-crawl";
|
|
13
|
+
* import { trackAICrawlerFetch } from "@traceten/ai-crawl/hono";
|
|
14
|
+
* const config = defineAiCrawlConfig({ … });
|
|
15
|
+
* // after producing the response:
|
|
16
|
+
* trackAICrawlerFetch(request, response, config, ctx?.waitUntil?.bind(ctx));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { defineAiCrawlConfig, isResolvedConfig } from "../config.js";
|
|
20
|
+
import { factsFromFetchRequest, trackFacts } from "../track.js";
|
|
21
|
+
/**
|
|
22
|
+
* Generic helper for any `Request`/`Response` runtime. Never throws; pass a
|
|
23
|
+
* `waitUntil` when the runtime has one, otherwise delivery is
|
|
24
|
+
* fire-and-forget.
|
|
25
|
+
*/
|
|
26
|
+
export function trackAICrawlerFetch(request, response, config, waitUntil) {
|
|
27
|
+
try {
|
|
28
|
+
if (!isResolvedConfig(config))
|
|
29
|
+
return; // raw config: refuse silently, never throw per-request
|
|
30
|
+
trackFacts(config, factsFromFetchRequest(request), response !== undefined && typeof response.status === "number" ? response.status : undefined, waitUntil);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* silent by contract */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hono middleware factory. THROWS at construction when the config is
|
|
38
|
+
* invalid; the middleware itself never throws its own errors (route errors
|
|
39
|
+
* propagate untouched).
|
|
40
|
+
*/
|
|
41
|
+
export function aiCrawlerTracking(config) {
|
|
42
|
+
const cfg = isResolvedConfig(config) ? config : defineAiCrawlConfig(config);
|
|
43
|
+
return async (c, next) => {
|
|
44
|
+
// Route errors propagate untouched — tracking must not change behaviour.
|
|
45
|
+
await next();
|
|
46
|
+
try {
|
|
47
|
+
let waitUntil;
|
|
48
|
+
try {
|
|
49
|
+
const ctx = c.executionCtx; // getter throws on Node's hono server
|
|
50
|
+
if (ctx !== undefined && typeof ctx.waitUntil === "function") {
|
|
51
|
+
waitUntil = (p) => ctx.waitUntil(p);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
waitUntil = undefined;
|
|
56
|
+
}
|
|
57
|
+
trackFacts(cfg, factsFromFetchRequest(c.req.raw), typeof c.res?.status === "number" ? c.res.status : undefined, waitUntil);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* silent by contract */
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AAGvF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAyB,EACzB,QAAwC,EACxC,MAA6B,EAC7B,SAA6D;IAE7D,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,CAAC,uDAAuD;QAC9F,UAAU,CACR,MAAM,EACN,qBAAqB,CAAC,OAAO,CAAC,EAC9B,QAAQ,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC3F,SAAS,CACV,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;AACH,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA6C;IAE7C,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5E,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,yEAAyE;QACzE,MAAM,IAAI,EAAE,CAAC;QACb,IAAI,CAAC;YACH,IAAI,SAAsD,CAAC;YAC3D,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,sCAAsC;gBAClE,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;oBAC7D,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,GAAG,SAAS,CAAC;YACxB,CAAC;YACD,UAAU,CACR,GAAG,EACH,qBAAqB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAChC,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC5D,SAAS,CACV,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js adapter — call from `proxy.ts` (Next ≥15.5; formerly
|
|
3
|
+
* `middleware.ts`).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // proxy.ts
|
|
7
|
+
* import { defineAiCrawlConfig } from "@traceten/ai-crawl";
|
|
8
|
+
* import { trackAICrawlerRequest } from "@traceten/ai-crawl/next";
|
|
9
|
+
*
|
|
10
|
+
* const config = defineAiCrawlConfig({
|
|
11
|
+
* siteId: process.env.TRACETEN_SITE_ID!,
|
|
12
|
+
* authToken: process.env.TRACETEN_CRAWL_TOKEN!,
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* export function proxy(request: NextRequest, event: NextFetchEvent) {
|
|
16
|
+
* trackAICrawlerRequest(request, event, config);
|
|
17
|
+
* return NextResponse.next();
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Validate the config at module scope with `defineAiCrawlConfig` so a
|
|
22
|
+
* missing token fails the deploy, not your visitors' requests. If a raw
|
|
23
|
+
* (unvalidated) config reaches this function it is validated lazily; an
|
|
24
|
+
* invalid one logs ONE loud error and no-ops — per-request code paths never
|
|
25
|
+
* throw.
|
|
26
|
+
*
|
|
27
|
+
* The middleware runs before the response exists, so no `status` is sent —
|
|
28
|
+
* the edge treats a crawl report without a status as "served".
|
|
29
|
+
*/
|
|
30
|
+
import { type FetchLikeRequest } from "../track.js";
|
|
31
|
+
import type { AiCrawlConfig, ResolvedAiCrawlConfig } from "../types.js";
|
|
32
|
+
/** Structural subset of Next's `NextFetchEvent`. */
|
|
33
|
+
export interface WaitUntilEvent {
|
|
34
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Report the request if it looks like an AI crawler. Never throws, never
|
|
38
|
+
* blocks — delivery is scheduled via `event.waitUntil` when available.
|
|
39
|
+
*/
|
|
40
|
+
export declare function trackAICrawlerRequest(request: FetchLikeRequest, event: WaitUntilEvent | undefined, config: AiCrawlConfig | ResolvedAiCrawlConfig): void;
|
|
41
|
+
//# sourceMappingURL=next.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAExE,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AA4BD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,MAAM,EAAE,aAAa,GAAG,qBAAqB,GAC5C,IAAI,CAaN"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js adapter — call from `proxy.ts` (Next ≥15.5; formerly
|
|
3
|
+
* `middleware.ts`).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // proxy.ts
|
|
7
|
+
* import { defineAiCrawlConfig } from "@traceten/ai-crawl";
|
|
8
|
+
* import { trackAICrawlerRequest } from "@traceten/ai-crawl/next";
|
|
9
|
+
*
|
|
10
|
+
* const config = defineAiCrawlConfig({
|
|
11
|
+
* siteId: process.env.TRACETEN_SITE_ID!,
|
|
12
|
+
* authToken: process.env.TRACETEN_CRAWL_TOKEN!,
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* export function proxy(request: NextRequest, event: NextFetchEvent) {
|
|
16
|
+
* trackAICrawlerRequest(request, event, config);
|
|
17
|
+
* return NextResponse.next();
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Validate the config at module scope with `defineAiCrawlConfig` so a
|
|
22
|
+
* missing token fails the deploy, not your visitors' requests. If a raw
|
|
23
|
+
* (unvalidated) config reaches this function it is validated lazily; an
|
|
24
|
+
* invalid one logs ONE loud error and no-ops — per-request code paths never
|
|
25
|
+
* throw.
|
|
26
|
+
*
|
|
27
|
+
* The middleware runs before the response exists, so no `status` is sent —
|
|
28
|
+
* the edge treats a crawl report without a status as "served".
|
|
29
|
+
*/
|
|
30
|
+
import { defineAiCrawlConfig, isResolvedConfig } from "../config.js";
|
|
31
|
+
import { factsFromFetchRequest, trackFacts } from "../track.js";
|
|
32
|
+
const resolvedCache = new WeakMap();
|
|
33
|
+
/** Resolve lazily, warning loudly ONCE per config object on invalid input. */
|
|
34
|
+
function resolveLazily(config) {
|
|
35
|
+
if (isResolvedConfig(config))
|
|
36
|
+
return config;
|
|
37
|
+
if (resolvedCache.has(config))
|
|
38
|
+
return resolvedCache.get(config) ?? null;
|
|
39
|
+
let resolved = null;
|
|
40
|
+
try {
|
|
41
|
+
resolved = defineAiCrawlConfig(config);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
try {
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error("[@traceten/ai-crawl] invalid config — crawler tracking is DISABLED:", err instanceof Error ? err.message : err);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* ignore */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
resolvedCache.set(config, resolved);
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Report the request if it looks like an AI crawler. Never throws, never
|
|
57
|
+
* blocks — delivery is scheduled via `event.waitUntil` when available.
|
|
58
|
+
*/
|
|
59
|
+
export function trackAICrawlerRequest(request, event, config) {
|
|
60
|
+
try {
|
|
61
|
+
const cfg = resolveLazily(config);
|
|
62
|
+
if (cfg === null)
|
|
63
|
+
return;
|
|
64
|
+
trackFacts(cfg, factsFromFetchRequest(request), undefined, event !== undefined ? (p) => event.waitUntil(p) : undefined);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* silent by contract */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AAQvF,MAAM,aAAa,GAAG,IAAI,OAAO,EAA+C,CAAC;AAEjF,8EAA8E;AAC9E,SAAS,aAAa,CACpB,MAA6C;IAE7C,IAAI,gBAAgB,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5C,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IACxE,IAAI,QAAQ,GAAiC,IAAI,CAAC;IAClD,IAAI,CAAC;QACH,QAAQ,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,sCAAsC;YACtC,OAAO,CAAC,KAAK,CACX,qEAAqE,EACrE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACzC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IACD,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAyB,EACzB,KAAiC,EACjC,MAA6C;IAE7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO;QACzB,UAAU,CACR,GAAG,EACH,qBAAqB,CAAC,OAAO,CAAC,EAC9B,SAAS,EACT,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAC5D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config validation + defaulting.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE place the package is allowed to throw: at construction,
|
|
5
|
+
* in the customer's module scope / server boot — never per-request. A
|
|
6
|
+
* missing `authToken` must fail loudly here rather than silently sending
|
|
7
|
+
* requests that the endpoint will 401.
|
|
8
|
+
*/
|
|
9
|
+
import type { AiCrawlConfig, ResolvedAiCrawlConfig } from "./types.js";
|
|
10
|
+
export declare const DEFAULT_ENDPOINT = "https://ingest.traceten.com/v1/ai-crawls";
|
|
11
|
+
export declare const DEFAULT_ALLOWED_METHODS: readonly string[];
|
|
12
|
+
export declare const AUTH_TOKEN_PREFIX = "tt_bot_";
|
|
13
|
+
/**
|
|
14
|
+
* Validate a config and apply defaults. THROWS on invalid input — call it at
|
|
15
|
+
* module scope (or in your middleware factory), so a misconfiguration fails
|
|
16
|
+
* your deploy, not your visitors' requests.
|
|
17
|
+
*/
|
|
18
|
+
export declare function defineAiCrawlConfig(config: AiCrawlConfig): ResolvedAiCrawlConfig;
|
|
19
|
+
/** True when the object already passed through {@link defineAiCrawlConfig}. */
|
|
20
|
+
export declare function isResolvedConfig(config: AiCrawlConfig | ResolvedAiCrawlConfig): config is ResolvedAiCrawlConfig;
|
|
21
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEvE,eAAO,MAAM,gBAAgB,6CAA6C,CAAC;AAC3E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAoB,CAAC;AAC1E,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAM3C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,qBAAqB,CA8FhF;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,aAAa,GAAG,qBAAqB,GAC5C,MAAM,IAAI,qBAAqB,CAEjC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config validation + defaulting.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE place the package is allowed to throw: at construction,
|
|
5
|
+
* in the customer's module scope / server boot — never per-request. A
|
|
6
|
+
* missing `authToken` must fail loudly here rather than silently sending
|
|
7
|
+
* requests that the endpoint will 401.
|
|
8
|
+
*/
|
|
9
|
+
import { DEFAULT_DENY_EXTENSIONS, DEFAULT_DENY_PATH_PREFIXES } from "./filter.js";
|
|
10
|
+
export const DEFAULT_ENDPOINT = "https://ingest.traceten.com/v1/ai-crawls";
|
|
11
|
+
export const DEFAULT_ALLOWED_METHODS = ["GET", "HEAD"];
|
|
12
|
+
export const AUTH_TOKEN_PREFIX = "tt_bot_";
|
|
13
|
+
function fail(message) {
|
|
14
|
+
throw new Error(`[@traceten/ai-crawl] ${message}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validate a config and apply defaults. THROWS on invalid input — call it at
|
|
18
|
+
* module scope (or in your middleware factory), so a misconfiguration fails
|
|
19
|
+
* your deploy, not your visitors' requests.
|
|
20
|
+
*/
|
|
21
|
+
export function defineAiCrawlConfig(config) {
|
|
22
|
+
if (typeof config !== "object" || config === null) {
|
|
23
|
+
fail("config must be an object");
|
|
24
|
+
}
|
|
25
|
+
if (typeof config.siteId !== "string" || config.siteId.trim() === "") {
|
|
26
|
+
fail("siteId is required — use your site key (the ttid_… data-site value on the install page)");
|
|
27
|
+
}
|
|
28
|
+
if (typeof config.authToken !== "string" || config.authToken.trim() === "") {
|
|
29
|
+
fail("authToken is required. POST /v1/ai-crawls rejects unauthenticated reports, " +
|
|
30
|
+
"so without it every report would silently 401. Create a crawl token " +
|
|
31
|
+
"(tt_bot_...) in the Traceten dashboard and keep it server-side.");
|
|
32
|
+
}
|
|
33
|
+
if (!config.authToken.startsWith(AUTH_TOKEN_PREFIX)) {
|
|
34
|
+
fail(`authToken must start with "${AUTH_TOKEN_PREFIX}". It looks like you passed ` +
|
|
35
|
+
"something else (perhaps the public snippet key?) — the crawl token is a " +
|
|
36
|
+
"server-side secret created in the dashboard.");
|
|
37
|
+
}
|
|
38
|
+
const proxyDepth = config.proxyDepth ?? 1;
|
|
39
|
+
if (!Number.isInteger(proxyDepth) || proxyDepth < 1) {
|
|
40
|
+
fail("proxyDepth must be a positive integer (number of trusted proxies appending to x-forwarded-for)");
|
|
41
|
+
}
|
|
42
|
+
if (config.endpoint !== undefined) {
|
|
43
|
+
// https only: the request carries the tt_bot_ Bearer token and crawler
|
|
44
|
+
// IPs. A typo'd http:// endpoint would leak both in cleartext, silently.
|
|
45
|
+
// Plain http is allowed solely for local development loopback.
|
|
46
|
+
const isHttps = /^https:\/\//.test(config.endpoint);
|
|
47
|
+
const isLocalHttp = /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/.test(config.endpoint);
|
|
48
|
+
if (!isHttps && !isLocalHttp) {
|
|
49
|
+
fail("endpoint must be an https:// URL (http:// is allowed only for localhost). " +
|
|
50
|
+
"Reports carry your tt_bot_ token and crawler IPs — never send them in cleartext.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (config.publicOrigin !== undefined && !/^https?:\/\/[^/]+$/.test(config.publicOrigin)) {
|
|
54
|
+
fail('publicOrigin must be an origin with no path, e.g. "https://example.com"');
|
|
55
|
+
}
|
|
56
|
+
const allowedMethods = (config.allowedMethods ?? DEFAULT_ALLOWED_METHODS).map((m) => String(m).toUpperCase());
|
|
57
|
+
if (allowedMethods.length === 0) {
|
|
58
|
+
fail("allowedMethods must not be empty");
|
|
59
|
+
}
|
|
60
|
+
// Extras EXTEND the built-in lists; they never replace them.
|
|
61
|
+
const denyPathPrefixes = [
|
|
62
|
+
...DEFAULT_DENY_PATH_PREFIXES,
|
|
63
|
+
// Trailing slashes are stripped so the segment-boundary match in
|
|
64
|
+
// filter.ts (`=== prefix || startsWith(prefix + "/")`) works either way.
|
|
65
|
+
...(config.extraDenyPathPrefixes ?? []).map((p) => String(p).toLowerCase().replace(/\/+$/, "")),
|
|
66
|
+
];
|
|
67
|
+
const denyExtensions = [
|
|
68
|
+
...DEFAULT_DENY_EXTENSIONS,
|
|
69
|
+
...(config.extraDenyExtensions ?? []).map((e) => {
|
|
70
|
+
const ext = String(e).toLowerCase();
|
|
71
|
+
return ext.startsWith(".") ? ext : `.${ext}`;
|
|
72
|
+
}),
|
|
73
|
+
];
|
|
74
|
+
const resolved = {
|
|
75
|
+
siteId: config.siteId.trim(),
|
|
76
|
+
authToken: config.authToken.trim(),
|
|
77
|
+
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
|
|
78
|
+
allowedMethods,
|
|
79
|
+
denyPathPrefixes,
|
|
80
|
+
denyExtensions,
|
|
81
|
+
disableAnswerFetch: config.disableAnswerFetch === true,
|
|
82
|
+
disableSearchCrawlers: config.disableSearchCrawlers === true,
|
|
83
|
+
disableTrainingCrawlers: config.disableTrainingCrawlers === true,
|
|
84
|
+
disableOtherCrawlers: config.disableOtherCrawlers === true,
|
|
85
|
+
trustProxy: config.trustProxy === true,
|
|
86
|
+
trustCfConnectingIp: config.trustCfConnectingIp === true,
|
|
87
|
+
proxyDepth,
|
|
88
|
+
publicOrigin: config.publicOrigin,
|
|
89
|
+
onError: typeof config.onError === "function" ? config.onError : undefined,
|
|
90
|
+
fetch: config.fetch,
|
|
91
|
+
__resolved: true,
|
|
92
|
+
};
|
|
93
|
+
return resolved;
|
|
94
|
+
}
|
|
95
|
+
/** True when the object already passed through {@link defineAiCrawlConfig}. */
|
|
96
|
+
export function isResolvedConfig(config) {
|
|
97
|
+
return config.__resolved === true;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
import type { CrawlerCategory } from "./types.js";
|
|
41
|
+
export interface AgentToken {
|
|
42
|
+
/** Lowercased agent token, matched with token-boundary anchoring. */
|
|
43
|
+
readonly token: string;
|
|
44
|
+
/** Canonical display name of the agent (original casing). */
|
|
45
|
+
readonly agent: string;
|
|
46
|
+
readonly provider: string;
|
|
47
|
+
readonly category: CrawlerCategory;
|
|
48
|
+
}
|
|
49
|
+
/** Tier 1 — exact agent tokens. First match wins. */
|
|
50
|
+
export declare const AGENT_TOKENS: readonly AgentToken[];
|
|
51
|
+
export interface ProviderAlias {
|
|
52
|
+
/** Lowercased alias, matched as a LEFT-anchored prefix at a token boundary. */
|
|
53
|
+
readonly alias: string;
|
|
54
|
+
readonly provider: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Tier 2 — coarse provider aliases, so a NEW agent from a known vendor
|
|
58
|
+
* (e.g. a hypothetical `ChatGPT-Reader/1.0`) is still captured without a
|
|
59
|
+
* package release. Left-boundary-anchored prefix match: the alias must start
|
|
60
|
+
* at the beginning of a token (start of string or after a non-token
|
|
61
|
+
* character), which is what defeats `not-really-GPTBot`-style spoofs while
|
|
62
|
+
* still catching `Claude-NewAgent/1.0`.
|
|
63
|
+
*
|
|
64
|
+
* Tier-2 matches are reported with category `ai_crawler` locally (the server
|
|
65
|
+
* assigns the real category) and are gated by `disableOtherCrawlers`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const PROVIDER_ALIASES: readonly ProviderAlias[];
|
|
68
|
+
//# sourceMappingURL=crawlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawlers.d.ts","sourceRoot":"","sources":["../src/crawlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED,qDAAqD;AACrD,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAwK7C,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EA6BpD,CAAC"}
|