@apideck/agent-analytics 0.11.0 → 0.13.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 +56 -0
- package/dist/adapters/posthog.cjs +2 -30
- package/dist/adapters/posthog.cjs.map +1 -1
- package/dist/adapters/posthog.d.cts +7 -1
- package/dist/adapters/posthog.d.ts +7 -1
- package/dist/adapters/posthog.js +2 -28
- package/dist/adapters/posthog.js.map +1 -1
- package/dist/adapters/webhook.cjs +1 -23
- package/dist/adapters/webhook.cjs.map +1 -1
- package/dist/adapters/webhook.d.cts +3 -1
- package/dist/adapters/webhook.d.ts +3 -1
- package/dist/adapters/webhook.js +1 -21
- package/dist/adapters/webhook.js.map +1 -1
- package/dist/index.cjs +2 -900
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -92
- package/dist/index.d.ts +102 -92
- package/dist/index.js +2 -875
- package/dist/index.js.map +1 -1
- package/dist/markdown.cjs +2 -67
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.js +2 -63
- package/dist/markdown.js.map +1 -1
- package/dist/{types-DKqlfVz6.d.cts → types-sQoQK-ox.d.cts} +31 -2
- package/dist/{types-DKqlfVz6.d.ts → types-sQoQK-ox.d.ts} +31 -2
- package/dist/verify.cjs +3 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +50 -0
- package/dist/verify.d.ts +50 -0
- package/dist/verify.js +3 -0
- package/dist/verify.js.map +1 -0
- package/package.json +7 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bots.ts","../src/bot-ranges.ts","../src/cidr.ts","../src/verify.ts","../src/hash.ts","../src/track.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["value"],"mappings":";AAgBO,IAAM,cAAA,GACX;AAmBK,IAAM,mBAAA,GACX;AAEK,SAAS,QAAQ,SAAA,EAA+C;AACrE,EAAA,IAAI,CAAC,WAAW,OAAO,KAAA;AACvB,EAAA,OAAO,cAAA,CAAe,KAAK,SAAS,CAAA;AACtC;AAEO,SAAS,aAAa,SAAA,EAA+C;AAC1E,EAAA,IAAI,CAAC,WAAW,OAAO,KAAA;AACvB,EAAA,OAAO,mBAAA,CAAoB,KAAK,SAAS,CAAA;AAC3C;AAeO,SAAS,aAAa,SAAA,EAA8C;AACzE,EAAA,IAAI,CAAC,SAAA,IAAa,OAAO,SAAA,KAAc,UAAU,OAAO,OAAA;AACxD,EAAA,MAAM,CAAA,GAAI,UAAU,WAAA,EAAY;AAGhC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,cAAc,CAAA,IAAK,EAAE,QAAA,CAAS,QAAQ,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,eAAe,CAAA,IAAK,CAAA,CAAE,SAAS,QAAQ,CAAA;AAC1G,IAAA,OAAO,SAAA;AACT,EAAA,IACE,EAAE,QAAA,CAAS,WAAW,KACtB,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,IACxB,CAAA,CAAE,QAAA,CAAS,kBAAkB,KAC7B,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,IACvB,CAAA,CAAE,SAAS,WAAW,CAAA;AAEtB,IAAA,OAAO,QAAA;AACT,EAAA,IAAI,CAAA,CAAE,SAAS,eAAe,CAAA,IAAK,EAAE,QAAA,CAAS,iBAAiB,GAAG,OAAO,YAAA;AACzE,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,OAAO,CAAA,EAAG,OAAO,cAAA;AAChC,EAAA,IACE,CAAA,CAAE,SAAS,iBAAiB,CAAA,IAC5B,EAAE,QAAA,CAAS,WAAW,CAAA,IACtB,CAAA,CAAE,QAAA,CAAS,uBAAuB,KAClC,CAAA,CAAE,QAAA,CAAS,cAAc,CAAA,IACzB,CAAA,CAAE,SAAS,qBAAqB,CAAA,IAChC,CAAA,CAAE,QAAA,CAAS,sBAAsB,CAAA;AAEjC,IAAA,OAAO,QAAA;AACT,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,OAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,MAAA;AAClC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,YAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,QAAA;AAC7F,EAAA,IACE,CAAA,CAAE,QAAA,CAAS,oBAAoB,CAAA,IAC/B,EAAE,QAAA,CAAS,sBAAsB,CAAA,IACjC,CAAA,CAAE,QAAA,CAAS,iBAAiB,CAAA,IAC5B,CAAA,CAAE,SAAS,aAAa,CAAA;AAExB,IAAA,OAAO,MAAA;AACT,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,SAAA;AACzC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,eAAe,CAAA,EAAG,OAAO,YAAA;AACxC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,SAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,SAAA;AAClC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,QAAA;AACnC,EAAA,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,IAAK,EAAE,QAAA,CAAS,QAAQ,GAAG,OAAO,SAAA;AACzD,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,OAAA;AACnC,EAAA,IAAI,CAAA,CAAE,SAAS,MAAM,CAAA,IAAK,EAAE,QAAA,CAAS,MAAM,GAAG,OAAO,KAAA;AACrD,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,OAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,mBAAmB,CAAA,EAAG,OAAO,WAAA;AAC5C,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,uBAAuB,CAAA,EAAG,OAAO,aAAA;AAChD,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AAGnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,QAAA;AACpC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,SAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,UAAA;AAClC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,KAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,gBAAA;AACzC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,aAAA;AACtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,YAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,UAAA;AAGtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,aAAA;AACtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,SAAA;AAClC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,YAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,WAAA;AACzC,EAAA,IAAI,CAAA,CAAE,SAAS,cAAc,CAAA,IAAK,EAAE,QAAA,CAAS,SAAS,GAAG,OAAO,SAAA;AAChE,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,OAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,SAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,UAAA;AACtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,SAAA;AACrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,UAAA;AACtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EAAG,OAAO,UAAA;AAGnC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,QAAA;AACpC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,OAAO,CAAA,EAAG,OAAO,OAAA;AAChC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,QAAA;AACpC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,OAAA;AAGtC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,qBAAqB,CAAA,EAAG,OAAO,UAAA;AAC9C,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,kBAAkB,CAAA,EAAG,OAAO,mBAAA;AAG3C,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,SAAA;AAGjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,iBAAA;AACzC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,WAAA;AACpC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAC/B,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AACjC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EAAG,OAAO,QAAA;AAIrC,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,UAAA;AACpC,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,MAAA;AAC7B,EAAA,IAAI,SAAA,CAAU,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,OAAA;AAC9B,EAAA,IAAI,4BAAA,CAA6B,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,KAAA;AACjD,EAAA,IAAI,WAAA,CAAY,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,OAAA;AAChC,EAAA,IAAI,cAAA,CAAe,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,YAAA;AACnC,EAAA,IAAI,mBAAA,CAAoB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,iBAAA;AACxC,EAAA,IAAI,kBAAA,CAAmB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,gBAAA;AACvC,EAAA,IAAI,UAAA,CAAW,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,QAAA;AAC/B,EAAA,IAAI,WAAA,CAAY,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,SAAA;AAChC,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,MAAA;AAG7B,EAAA,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,CAAA,IAAK,EAAE,QAAA,CAAS,QAAQ,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,IAAK,CAAA,CAAE,SAAS,SAAS,CAAA;AAC/F,IAAA,OAAO,SAAA;AAET,EAAA,OAAO,OAAA;AACT;AAOO,SAAS,sBAAsB,SAAA,EAA8C;AAClF,EAAA,IAAI,CAAC,SAAA,IAAa,OAAO,SAAA,KAAc,UAAU,OAAO,OAAA;AACxD,EAAA,MAAM,eAAA,GAAkB,SAAA,CAAU,KAAA,CAAM,yCAAyC,CAAA;AACjF,EAAA,IAAI,eAAA,IAAmB,gBAAgB,CAAC,CAAA,SAAU,eAAA,CAAgB,CAAC,EAAE,IAAA,EAAK;AAC1E,EAAA,MAAM,QAAQ,SAAA,CAAU,IAAA,EAAK,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,EAAG,IAAA,GAAO,KAAA,CAAM,KAAK,CAAA,CAAE,CAAC,GAAG,IAAA,EAAK;AAC3E,EAAA,OAAO,KAAA,IAAS,OAAA;AAClB;AAkBO,SAAS,eAAe,GAAA,EAAiC;AAC9D,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,MAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,YAAY,CAAA,IAAK,IAAI,WAAA,EAAY;AAC7D,EAAA,MAAM,WAAA,GACJ,EAAA,CAAG,QAAA,CAAS,SAAS,KAAK,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,IAAK,GAAG,QAAA,CAAS,QAAQ,CAAA,IAAK,EAAA,CAAG,SAAS,SAAS,CAAA;AAEnG,EAAA,IAAI,CAAC,WAAA,EAAa,OAAO,EAAE,KAAA,EAAO,GAAG,OAAA,EAAS,EAAC,EAAG,MAAA,EAAQ,KAAA,EAAM;AAEhE,EAAA,IAAI,CAAC,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,EAAG;AACvC,IAAA,OAAA,CAAQ,KAAK,yBAAyB,CAAA;AAAA,EACxC;AACA,EAAA,IAAI,CAAC,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,EAAG;AACtC,IAAA,OAAA,CAAQ,KAAK,wBAAwB,CAAA;AAAA,EACvC;AACA,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA;AAC3C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAA,CAAQ,KAAK,mBAAmB,CAAA;AAAA,EAClC,WAAW,OAAA,CAAQ,WAAA,EAAY,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAG;AAC3D,IAAA,OAAA,CAAQ,KAAK,sBAAsB,CAAA;AAAA,EACrC;AACA,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,IAAK,EAAA;AAC5C,EAAA,IAAI,CAAC,MAAA,IAAU,MAAA,KAAW,KAAA,EAAO;AAC/B,IAAA,OAAA,CAAQ,KAAK,wBAAwB,CAAA;AAAA,EACvC;AACA,EAAA,IAAA,CAAK,GAAA,CAAI,QAAQ,GAAA,CAAI,YAAY,KAAK,EAAA,EAAI,WAAA,OAAkB,OAAA,EAAS;AACnE,IAAA,OAAA,CAAQ,KAAK,kBAAkB,CAAA;AAAA,EACjC;AAEA,EAAA,MAAM,QAAQ,OAAA,CAAQ,MAAA;AACtB,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,MAAA,EAAQ,SAAS,CAAA,EAAE;AAC9C;AA6CO,SAAS,cAAc,SAAA,EAA2D;AACvF,EAAA,MAAM,KAAA,GAAQ,aAAa,SAAS,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,QAAQ,SAAS,CAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,aAAa,SAAS,CAAA;AAEzC,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,OAAO,IAAA,GAAO,kBAAA;AAAA,OAAA,IACT,YAAY,IAAA,GAAO,mBAAA;AAAA,OAAA,IACnB,KAAA,KAAU,WAAW,IAAA,GAAO,SAAA;AAAA,OAChC,IAAA,GAAO,OAAA;AAEZ,EAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,iBAAiB,UAAA,EAAW;AACpE;AAOO,SAAS,gBAAgB,GAAA,EAAmC;AACjE,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,IAAK,EAAA;AACnD,EAAA,MAAM,IAAA,GAAO,cAAc,SAAS,CAAA;AACpC,EAAA,MAAM,QAAA,GAAW,eAAe,GAAG,CAAA;AAEnC,EAAA,IAAI,OAAO,IAAA,CAAK,IAAA;AAChB,EAAA,IAAI,IAAA,KAAS,SAAA,IAAa,QAAA,CAAS,MAAA,EAAQ;AACzC,IAAA,IAAA,GAAO,iBAAA;AAAA,EACT;AAEA,EAAA,OAAO,EAAE,GAAG,IAAA,EAAM,IAAA,EAAM,QAAA,EAAS;AACnC;;;ACzSO,IAAM,sBAAA,GAAyB;AAE/B,IAAM,aAAA,GAA6D;AAAA,EACxE,OAAA,EAAS;AAAA,IACP,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,oBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,eAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,eAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,eAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,iBAAA;AAAA,IACA,mBAAA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","file":"index.js","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n }\n\n return { ...base, kind, headless }\n}\n","/**\n * Published crawler IP ranges, vendor by vendor.\n *\n * GENERATED FILE — do not edit by hand. Regenerate with:\n *\n * node scripts/refresh-bot-ranges.mjs\n *\n * Keys match the labels {@link parseBotName} returns, so a claimed identity\n * maps to its range list without a translation table.\n *\n * Only vendors that publish a machine-readable feed appear here. A vendor's\n * absence means \"cannot be verified\", never \"not a real bot\" — see\n * {@link verifyBotIdentity} for how that distinction is surfaced.\n *\n * Freshness is the whole ballgame: these lists rotate. Almost every OpenAI\n * prefix is an Azure block and every Anthropic prefix is GCP or similar, so\n * \"came from a datacenter\" proves nothing on its own — only membership in the\n * current published list does. A stale snapshot produces false 'spoofed'\n * verdicts on legitimate crawlers, which is the failure mode to fear.\n */\n\n/** When these ranges were captured from the vendor feeds (UTC). */\nexport const BOT_RANGES_CAPTURED_AT = '2026-08-02T00:00:00Z'\n\nexport const BOT_IP_RANGES: Readonly<Record<string, readonly string[]>> = {\n ChatGPT: [\n '104.208.184.192/28',\n '104.208.184.208/28',\n '104.210.139.192/28',\n '104.210.139.224/28',\n '104.210.140.128/28',\n '128.85.198.32/28',\n '13.65.138.112/28',\n '13.65.138.96/28',\n '13.67.72.16/28',\n '13.70.107.160/28',\n '13.71.2.208/28',\n '13.76.115.224/28',\n '13.76.115.240/28',\n '13.76.116.80/28',\n '13.76.32.208/28',\n '13.83.167.128/28',\n '13.83.237.176/28',\n '132.196.82.48/28',\n '132.196.86.0/24',\n '134.149.233.80/28',\n '135.116.136.160/28',\n '135.13.64.240/28',\n '135.220.73.208/28',\n '135.220.73.240/28',\n '135.234.64.0/24',\n '135.237.131.208/28',\n '135.237.133.48/28',\n '137.135.191.176/28',\n '138.91.30.48/28',\n '138.91.46.96/28',\n '145.132.1.32/28',\n '145.132.136.96/28',\n '145.133.0.176/28',\n '158.158.5.32/28',\n '168.63.252.240/28',\n '172.162.248.64/28',\n '172.170.1.80/28',\n '172.170.225.0/28',\n '172.170.241.80/28',\n '172.170.8.208/28',\n '172.171.4.176/28',\n '172.175.152.224/28',\n '172.178.140.144/28',\n '172.178.141.112/28',\n '172.178.141.128/28',\n '172.182.193.224/28',\n '172.182.193.80/28',\n '172.182.194.144/28',\n '172.182.194.32/28',\n '172.182.195.48/28',\n '172.182.202.0/25',\n '172.182.204.0/24',\n '172.182.207.0/25',\n '172.182.209.208/28',\n '172.182.211.192/28',\n '172.182.213.192/28',\n '172.182.214.0/24',\n '172.182.215.0/24',\n '172.182.224.0/28',\n '172.183.143.224/28',\n '172.183.222.128/28',\n '172.192.112.208/28',\n '172.192.88.192/28',\n '172.192.97.32/28',\n '172.197.160.192/28',\n '172.197.161.208/28',\n '172.197.170.80/28',\n '172.197.203.16/28',\n '172.199.137.80/28',\n '172.202.102.112/28',\n '172.203.190.128/28',\n '172.204.27.16/28',\n '172.204.28.224/28',\n '172.204.96.32/28',\n '172.204.96.48/28',\n '172.204.96.80/28',\n '172.205.189.192/28',\n '172.207.1.32/28',\n '172.208.128.32/28',\n '172.208.128.48/28',\n '172.212.159.64/28',\n '172.212.172.160/28',\n '172.213.21.16/28',\n '172.215.215.32/28',\n '172.215.218.96/28',\n '191.233.1.112/28',\n '191.233.1.128/28',\n '191.233.194.32/28',\n '191.233.196.112/28',\n '191.233.199.160/28',\n '191.233.2.0/28',\n '191.235.66.16/28',\n '191.235.99.80/28',\n '191.237.249.64/28',\n '191.239.245.16/28',\n '20.102.212.144/28',\n '20.113.211.112/28',\n '20.113.225.112/28',\n '20.125.112.224/28',\n '20.125.144.144/28',\n '20.125.66.80/28',\n '20.14.99.96/28',\n '20.161.75.208/28',\n '20.168.18.32/28',\n '20.168.7.192/28',\n '20.168.7.240/28',\n '20.169.6.224/28',\n '20.169.7.48/28',\n '20.169.72.112/28',\n '20.169.73.176/28',\n '20.169.73.32/28',\n '20.169.73.64/28',\n '20.169.77.0/25',\n '20.169.78.112/28',\n '20.169.78.128/28',\n '20.169.78.144/28',\n '20.169.78.160/28',\n '20.169.78.176/28',\n '20.169.78.192/28',\n '20.169.78.208/28',\n '20.169.78.48/28',\n '20.169.78.64/28',\n '20.169.78.80/28',\n '20.169.78.96/28',\n '20.169.86.224/28',\n '20.169.86.240/28',\n '20.169.87.112/28',\n '20.17.108.96/28',\n '20.170.184.16/28',\n '20.170.184.32/28',\n '20.170.184.48/28',\n '20.170.184.64/28',\n '20.170.184.80/28',\n '20.171.123.64/28',\n '20.171.206.0/24',\n '20.171.207.0/24',\n '20.171.53.224/28',\n '20.172.29.32/28',\n '20.193.233.240/28',\n '20.193.50.32/28',\n '20.194.0.208/28',\n '20.194.1.0/28',\n '20.198.67.96/28',\n '20.199.211.160/28',\n '20.199.242.0/28',\n '20.200.212.240/28',\n '20.204.24.240/28',\n '20.210.154.128/28',\n '20.210.174.208/28',\n '20.210.211.192/28',\n '20.215.187.208/28',\n '20.215.188.192/28',\n '20.215.214.16/28',\n '20.215.219.128/28',\n '20.215.219.160/28',\n '20.215.219.208/28',\n '20.215.220.112/28',\n '20.215.220.128/28',\n '20.215.220.144/28',\n '20.215.220.160/28',\n '20.215.220.176/28',\n '20.215.220.192/28',\n '20.215.220.208/28',\n '20.215.220.64/28',\n '20.215.220.80/28',\n '20.215.220.96/28',\n '20.218.30.240/28',\n '20.219.71.192/28',\n '20.222.36.192/28',\n '20.226.32.80/28',\n '20.227.140.32/28',\n '20.228.106.176/28',\n '20.235.75.208/28',\n '20.235.87.224/28',\n '20.249.63.208/28',\n '20.25.151.224/28',\n '20.250.136.64/28',\n '20.250.136.80/28',\n '20.250.6.128/28',\n '20.27.94.128/28',\n '20.42.10.176/28',\n '20.45.178.144/28',\n '20.48.120.208/28',\n '20.52.125.160/28',\n '20.55.129.0/28',\n '20.55.229.144/28',\n '20.57.199.192/28',\n '20.63.180.96/28',\n '20.63.221.64/28',\n '20.79.59.112/28',\n '20.81.183.64/28',\n '20.83.243.176/28',\n '20.97.189.96/28',\n '23.102.140.144/28',\n '23.102.141.32/28',\n '23.98.142.176/28',\n '23.98.179.16/28',\n '23.98.186.176/28',\n '23.98.186.192/28',\n '23.98.186.64/28',\n '23.98.186.96/28',\n '4.151.119.48/28',\n '4.151.241.240/28',\n '4.151.71.176/28',\n '4.189.118.208/28',\n '4.189.119.48/28',\n '4.196.118.112/28',\n '4.197.115.112/28',\n '4.197.19.176/28',\n '4.197.22.112/28',\n '4.197.64.0/28',\n '4.197.64.16/28',\n '4.197.64.48/28',\n '4.197.64.64/28',\n '4.198.72.16/28',\n '4.198.96.112/28',\n '4.201.232.64/28',\n '4.201.232.80/28',\n '4.203.96.80/28',\n '4.205.128.176/28',\n '4.218.24.64/28',\n '4.226.200.16/28',\n '4.226.226.32/28',\n '4.227.36.0/25',\n '40.116.73.208/28',\n '40.122.235.112/28',\n '40.67.175.0/25',\n '40.67.183.160/28',\n '40.67.183.176/28',\n '40.78.161.48/28',\n '40.81.134.128/28',\n '40.81.134.144/28',\n '40.81.234.144/28',\n '40.81.67.96/28',\n '40.84.181.32/28',\n '40.84.221.208/28',\n '40.84.221.224/28',\n '40.90.214.16/28',\n '48.193.44.32/28',\n '48.221.184.112/28',\n '48.221.184.80/28',\n '48.221.184.96/28',\n '48.221.40.176/28',\n '51.107.70.192/28',\n '51.116.2.80/28',\n '51.116.221.96/28',\n '51.56.40.80/28',\n '51.57.0.96/28',\n '51.59.24.64/28',\n '51.59.24.80/28',\n '51.59.40.80/28',\n '51.59.40.96/28',\n '51.59.48.80/28',\n '51.59.48.96/28',\n '51.8.102.0/24',\n '51.8.155.112/28',\n '51.8.155.48/28',\n '51.8.155.64/28',\n '51.8.187.224/28',\n '52.148.129.32/28',\n '52.153.130.64/28',\n '52.154.22.48/28',\n '52.156.77.144/28',\n '52.159.227.32/28',\n '52.159.249.96/28',\n '52.161.49.224/28',\n '52.161.49.96/28',\n '52.165.212.16/28',\n '52.165.212.32/28',\n '52.165.212.48/28',\n '52.172.129.160/28',\n '52.172.251.112/28',\n '52.173.219.112/28',\n '52.173.219.96/28',\n '52.173.221.16/28',\n '52.173.221.176/28',\n '52.173.221.208/28',\n '52.173.234.16/28',\n '52.173.234.80/28',\n '52.173.235.80/28',\n '52.183.217.240/28',\n '52.187.246.128/28',\n '52.190.137.144/28',\n '52.190.137.16/28',\n '52.190.139.48/28',\n '52.190.142.64/28',\n '52.190.190.16/28',\n '52.225.75.208/28',\n '52.230.152.0/24',\n '52.230.163.32/28',\n '52.230.164.176/28',\n '52.231.30.48/28',\n '52.231.34.176/28',\n '52.231.39.144/28',\n '52.231.39.192/28',\n '52.231.49.48/28',\n '52.231.50.64/28',\n '52.236.94.144/28',\n '52.241.146.208/28',\n '52.242.132.224/28',\n '52.242.132.240/28',\n '52.242.245.208/28',\n '52.252.113.240/28',\n '52.255.109.112/28',\n '52.255.109.128/28',\n '52.255.109.144/28',\n '52.255.109.80/28',\n '52.255.109.96/28',\n '52.255.111.0/28',\n '52.255.111.112/28',\n '52.255.111.32/28',\n '52.255.111.48/28',\n '52.255.111.80/28',\n '57.154.174.112/28',\n '57.154.175.0/28',\n '57.154.187.32/28',\n '68.154.28.96/28',\n '68.218.30.112/28',\n '68.220.57.64/28',\n '68.221.67.192/28',\n '68.221.67.224/28',\n '68.221.67.240/28',\n '70.153.139.208/28',\n '70.153.189.192/28',\n '70.153.190.16/28',\n '70.153.76.16/28',\n '70.153.87.224/28',\n '70.156.144.64/28',\n '70.156.152.80/28',\n '70.156.152.96/28',\n '74.161.200.96/28',\n '74.224.217.64/28',\n '74.226.253.160/28',\n '74.249.86.176/28',\n '74.7.175.128/25',\n '74.7.227.0/25',\n '74.7.227.128/25',\n '74.7.228.0/25',\n '74.7.228.128/25',\n '74.7.229.0/25',\n '74.7.229.128/25',\n '74.7.230.0/25',\n '74.7.241.0/25',\n '74.7.241.128/25',\n '74.7.242.0/25',\n '74.7.242.128/25',\n '74.7.243.0/25',\n '74.7.243.128/25',\n '74.7.244.0/25',\n '74.7.35.112/28',\n '74.7.35.48/28',\n '74.7.36.64/28',\n '74.7.36.80/28',\n '74.7.36.96/28',\n '85.211.241.128/28',\n '9.129.0.0/17',\n '9.160.128.16/28',\n '9.160.128.32/28',\n '9.160.128.64/28',\n '9.160.163.128/28',\n '9.160.164.128/28',\n '9.160.34.144/28',\n '9.160.36.16/28',\n '9.160.96.16/28',\n '9.163.101.48/28',\n '9.205.25.128/28',\n '9.205.8.48/28',\n '9.223.181.208/28',\n '9.234.96.192/28',\n '9.234.97.128/28',\n '9.234.97.96/28',\n '9.235.40.32/28',\n ],\n Claude: [\n '136.107.176.208/32',\n '216.73.216.0/22',\n '34.11.34.31/32',\n '34.150.241.79/32',\n '34.162.191.81/32',\n '34.162.230.222/32',\n '34.162.244.71/32',\n '34.182.140.95/32',\n '34.182.161.143/32',\n '34.182.218.27/32',\n '34.182.220.85/32',\n '34.182.222.37/32',\n '34.182.225.167/32',\n '34.182.226.151/32',\n '34.182.226.221/32',\n '34.186.108.163/32',\n '34.85.172.162/32',\n '35.221.29.174/32',\n '35.245.175.129/32',\n '35.245.89.239/32',\n ],\n Perplexity: [\n '107.20.236.150/32',\n '18.210.92.235/32',\n '18.97.1.228/30',\n '18.97.9.96/29',\n '3.211.124.183/32',\n '3.222.232.239/32',\n '3.224.62.45/32',\n '3.231.139.107/32',\n ],\n Apple: [\n '17.22.237.0/24',\n '17.22.245.0/24',\n '17.22.253.0/24',\n '17.241.193.160/27',\n '17.241.200.160/27',\n '17.241.208.160/27',\n '17.241.219.0/24',\n '17.241.227.0/24',\n '17.241.75.0/24',\n '17.246.15.0/24',\n '17.246.19.0/24',\n '17.246.23.0/24',\n ],\n}\n\n/** Vendor labels this build can verify. Anything else yields a null verdict. */\nexport const VERIFIABLE_VENDORS: readonly string[] = Object.keys(BOT_IP_RANGES)\n","/**\n * Dependency-free CIDR matching for IPv4 and IPv6.\n *\n * Kept separate from {@link verifyBotIdentity} so the matching logic can be\n * tested in isolation — a wrong answer here silently turns a real crawler into\n * a \"spoofed\" one, which is worse than not verifying at all.\n *\n * Everything is parsed once into numeric form (`number` for v4, `bigint` for\n * v6) and compared with masks. No allocation per request beyond the parse of\n * the incoming IP.\n */\n\n/** Parse dotted-quad IPv4 into a 32-bit unsigned integer. */\nexport function ipv4ToInt(ip: string): number | null {\n const parts = ip.split('.')\n if (parts.length !== 4) return null\n let out = 0\n for (const part of parts) {\n // Reject empty, non-numeric, out-of-range, and leading-zero forms\n // (`01.2.3.4` is ambiguous — some parsers read it as octal).\n if (!/^\\d{1,3}$/.test(part)) return null\n if (part.length > 1 && part[0] === '0') return null\n const n = Number(part)\n if (n > 255) return null\n out = (out << 8) | n\n }\n // `>>> 0` converts the signed 32-bit result back to unsigned.\n return out >>> 0\n}\n\n/**\n * Parse IPv6 (including `::` compression and IPv4-mapped tails like\n * `::ffff:1.2.3.4`) into a 128-bit BigInt.\n */\nexport function ipv6ToBigInt(ip: string): bigint | null {\n let text = ip\n // Strip a zone index (`fe80::1%eth0`) — irrelevant for range membership.\n const zone = text.indexOf('%')\n if (zone !== -1) text = text.slice(0, zone)\n if (!text || text.indexOf(':') === -1) return null\n\n // An embedded IPv4 tail contributes the low 32 bits.\n let tail: number | null = null\n const lastColon = text.lastIndexOf(':')\n const maybeV4 = text.slice(lastColon + 1)\n if (maybeV4.indexOf('.') !== -1) {\n tail = ipv4ToInt(maybeV4)\n if (tail === null) return null\n text = text.slice(0, lastColon + 1) + '0:0'\n }\n\n const halves = text.split('::')\n if (halves.length > 2) return null\n const head = halves[0] ? halves[0].split(':') : []\n const rest = halves.length === 2 ? (halves[1] ? halves[1].split(':') : []) : null\n\n let groups: string[]\n if (rest === null) {\n groups = head\n if (groups.length !== 8) return null\n } else {\n const fill = 8 - head.length - rest.length\n if (fill < 0) return null\n groups = [...head, ...Array(fill).fill('0'), ...rest]\n }\n\n let out = 0n\n for (const g of groups) {\n if (!/^[0-9a-fA-F]{1,4}$/.test(g)) return null\n out = (out << 16n) | BigInt(parseInt(g, 16))\n }\n // Overwrite the low 32 bits with the embedded IPv4 value when present.\n if (tail !== null) out = ((out >> 32n) << 32n) | BigInt(tail >>> 0)\n return out\n}\n\ninterface V4Range {\n net: number\n mask: number\n}\ninterface V6Range {\n net: bigint\n bits: number\n}\n\nexport interface CompiledRanges {\n v4: V4Range[]\n v6: V6Range[]\n}\n\n/**\n * Pre-compile a list of CIDR strings into numeric form. Invalid entries are\n * dropped rather than thrown — a malformed line in a vendor's published feed\n * shouldn't take down the whole check.\n */\nexport function compileRanges(cidrs: readonly string[]): CompiledRanges {\n const v4: V4Range[] = []\n const v6: V6Range[] = []\n for (const cidr of cidrs) {\n const slash = cidr.lastIndexOf('/')\n if (slash === -1) continue\n const addr = cidr.slice(0, slash)\n const bits = Number(cidr.slice(slash + 1))\n if (!Number.isInteger(bits) || bits < 0) continue\n\n if (addr.indexOf(':') !== -1) {\n if (bits > 128) continue\n const net = ipv6ToBigInt(addr)\n if (net === null) continue\n v6.push({ net: bits === 0 ? 0n : (net >> BigInt(128 - bits)) << BigInt(128 - bits), bits })\n } else {\n if (bits > 32) continue\n const net = ipv4ToInt(addr)\n if (net === null) continue\n // `bits === 0` needs special handling: `<<32` is a no-op in JS, not zero.\n const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0\n v4.push({ net: (net & mask) >>> 0, mask })\n }\n }\n return { v4, v6 }\n}\n\n/** True when `ip` falls inside any range in the pre-compiled set. */\nexport function ipInRanges(ip: string, ranges: CompiledRanges): boolean {\n if (!ip) return false\n const trimmed = ip.trim()\n if (!trimmed) return false\n\n if (trimmed.indexOf(':') !== -1) {\n const value = ipv6ToBigInt(trimmed)\n if (value === null) return false\n // An IPv4-mapped address (::ffff:a.b.c.d) must also be checked against the\n // v4 list — Vercel and Cloudflare both emit this form on dual-stack edges.\n const V4_MAPPED_PREFIX = 0xffffn << 32n\n if ((value >> 32n) === V4_MAPPED_PREFIX >> 32n) {\n const low = Number(value & 0xffffffffn) >>> 0\n if (matchV4(low, ranges.v4)) return true\n }\n for (const r of ranges.v6) {\n if (r.bits === 0) return true\n if ((value >> BigInt(128 - r.bits)) << BigInt(128 - r.bits) === r.net) return true\n }\n return false\n }\n\n const value = ipv4ToInt(trimmed)\n if (value === null) return false\n return matchV4(value, ranges.v4)\n}\n\nfunction matchV4(value: number, list: readonly V4Range[]): boolean {\n for (const r of list) {\n if (((value & r.mask) >>> 0) === r.net) return true\n }\n return false\n}\n\n/** Convenience wrapper — compiles on every call, so prefer {@link ipInRanges}. */\nexport function ipInCidr(ip: string, cidr: string): boolean {\n return ipInRanges(ip, compileRanges([cidr]))\n}\n","import { parseBotName } from './bots.js'\nimport { BOT_IP_RANGES, VERIFIABLE_VENDORS } from './bot-ranges.js'\nimport { compileRanges, ipInRanges, type CompiledRanges } from './cidr.js'\n\n/**\n * Verdict on whether a request's claimed crawler identity holds up against the\n * vendor's published IP ranges.\n *\n * - `'verified'` — the UA claims a vendor we can check, and the client IP is\n * inside that vendor's published ranges. High confidence.\n * - `'spoofed'` — the UA claims a vendor we can check, and the IP is **not**\n * in its ranges. Someone is impersonating the crawler.\n * - `'unverifiable'` — the UA claims a vendor that publishes no feed we bundle\n * (Bytespider, Amazon, Meta, …), or no usable client IP was available.\n * - `'not-claimed'` — the UA doesn't claim a verifiable crawler at all. This is\n * the normal verdict for browsers and HTTP clients; it is *not* a negative\n * finding.\n */\nexport type VerificationVerdict = 'verified' | 'spoofed' | 'unverifiable' | 'not-claimed'\n\n/** Why a request could not be judged. Only set when verdict is 'unverifiable'. */\nexport type UnverifiableReason = 'no-published-ranges' | 'client-side-agent' | 'no-client-ip'\n\nexport interface BotVerification {\n verdict: VerificationVerdict\n reason?: UnverifiableReason\n /** Vendor label the UA claims, when it claims one. */\n claimed: string | null\n /**\n * Convenience boolean for filtering: `true` only for `'verified'`, `false`\n * only for `'spoofed'`, `null` when no judgement was possible. Deliberately\n * tri-state — collapsing \"unverifiable\" into `false` would brand every\n * Bytespider and Amazonbot hit an impostor.\n */\n verified: boolean | null\n}\n\n/**\n * Products that fetch from the **end user's own device**, not from the\n * vendor's infrastructure.\n *\n * A published range list only covers a vendor's server-side crawler fleet.\n * When the fetch originates on a developer's laptop, the client IP is theirs\n * and will never appear in the vendor's ranges — so a range check produces\n * 'spoofed' for entirely legitimate traffic.\n *\n * This is not cosmetic. Measured against 30 days of production data:\n *\n * Claude-User (claude-code CLI) 6,492 events 4,486 IPs 0% in range\n * Perplexity-User 493 events 148 IPs 0% in range\n * ClaudeBot 13,671 events 236 IPs 96% in range\n * PerplexityBot 6,897 events 158 IPs 91% in range\n *\n * Treating the first two as impostors would have falsely accused ~7k real\n * fetches a month. The distinction is per *product*, not per vendor, and not\n * inferable from a `-User` suffix — OpenAI's ChatGPT-User fetches server-side\n * from Azure and verifies at ~99%.\n */\nconst CLIENT_SIDE_AGENT_PATTERN = /claude-code|perplexity-user|cursor|windsurf|cline|aider/i\n\n/**\n * Products known to fetch server-side from ranges the vendor publishes. Only\n * these can earn a 'verified' or 'spoofed' verdict; anything else is reported\n * 'unverifiable' so the data never overstates what was actually checked.\n */\nconst SERVER_SIDE_CRAWLER_PATTERN =\n /ClaudeBot|Claude-SearchBot|GPTBot|OAI-SearchBot|ChatGPT-User|PerplexityBot|Applebot/i\n\n// Compile each vendor's ranges once at module load rather than per request.\nconst COMPILED: Record<string, CompiledRanges> = {}\nfor (const vendor of VERIFIABLE_VENDORS) {\n COMPILED[vendor] = compileRanges(BOT_IP_RANGES[vendor] ?? [])\n}\n\n/** Vendor labels this build can produce a verified/spoofed verdict for. */\nexport function verifiableVendors(): readonly string[] {\n return VERIFIABLE_VENDORS\n}\n\n/**\n * Check a claimed crawler identity against the vendor's published IP ranges.\n *\n * Pass the client IP you already trust — on Vercel and Cloudflare that is the\n * first hop of `x-forwarded-for`. If your edge doesn't strip client-supplied\n * `X-Forwarded-For`, an attacker controls this value and a `'verified'` verdict\n * means nothing; verify your proxy's behaviour before relying on it.\n */\nexport function verifyBotIdentity(\n userAgent: string | null | undefined,\n ip: string | null | undefined\n): BotVerification {\n const ua = userAgent ?? ''\n const claimed = parseBotName(userAgent)\n const ranges = COMPILED[claimed]\n\n if (!ranges) {\n // Either not a crawler at all, or a crawler with no published feed. Both\n // are \"no judgement\", but the caller may want to tell them apart.\n const isKnownCrawler = claimed !== 'Other' && claimed !== 'Browser'\n return {\n verdict: isKnownCrawler ? 'unverifiable' : 'not-claimed',\n ...(isKnownCrawler ? { reason: 'no-published-ranges' as const } : {}),\n claimed: isKnownCrawler ? claimed : null,\n verified: null\n }\n }\n\n // Order matters: a client-side agent must be excluded before the range check,\n // and the server-side allowlist gates everything else, so an unrecognised\n // product from a covered vendor is never accused on a partial range list.\n if (CLIENT_SIDE_AGENT_PATTERN.test(ua) || !SERVER_SIDE_CRAWLER_PATTERN.test(ua)) {\n return {\n verdict: 'unverifiable',\n reason: 'client-side-agent',\n claimed,\n verified: null\n }\n }\n\n const trimmed = (ip ?? '').trim()\n if (!trimmed) {\n return { verdict: 'unverifiable', reason: 'no-client-ip', claimed, verified: null }\n }\n\n const inRange = ipInRanges(trimmed, ranges)\n return {\n verdict: inRange ? 'verified' : 'spoofed',\n claimed,\n verified: inRange\n }\n}\n\n/**\n * Extract the client IP the way {@link trackVisit} does — first hop of\n * `x-forwarded-for`, falling back to the platform-specific headers.\n */\nexport function clientIpFromRequest(req: Request): string {\n const forwarded = req.headers.get('x-forwarded-for') || ''\n const first = forwarded.split(',')[0]?.trim()\n if (first) return first\n return (req.headers.get('cf-connecting-ip') || req.headers.get('x-real-ip') || '').trim()\n}\n\n/** Verify straight from a request object. */\nexport function verifyRequest(req: Request): BotVerification {\n return verifyBotIdentity(req.headers.get('user-agent'), clientIpFromRequest(req))\n}\n","/**\n * djb2 hash returning an 8-char hex string prefixed with `anon_`. Used to\n * build stable anonymous distinct-ids from `ip:ua:...` tuples without\n * collecting identifying data. Not cryptographic — collisions are fine for\n * analytics segmentation.\n */\nexport function hashId(input: string): string {\n let h = 5381\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff\n }\n return 'anon_' + (h >>> 0).toString(16)\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { verifyBotIdentity } from './verify.js'\nimport { hashId } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits\n * the adapter but swallows errors so a downed analytics backend never breaks\n * the response path. Callers typically don't await the returned promise.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(\n req: Request,\n opts: TrackVisitOptions\n): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n // Only run the range check when asked — it is pure CPU over pre-compiled\n // masks, but the verdict is misleading unless the caller trusts `ip`.\n const verification = opts.verifyIdentity ? verifyBotIdentity(userAgent, ip) : null\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId: hashId(`${ip}:${userAgent}`),\n timestamp: new Date().toISOString(),\n properties: {\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false,\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null,\n ...opts.properties\n }\n }\n\n try {\n await opts.analytics.capture(event)\n } catch {\n // Intentional swallow — analytics failures must not affect the response.\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true\n })\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true\n })\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/bots.ts","../src/hash.ts","../src/track.ts","../src/errors.ts","../src/policy.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","firstUserAgentProduct","compatibleMatch","detectHeadless","req","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","HashSecretError","message","subtle","c","KEYS","keyFor","secret","k","hashId","input","sig","bytes","out","b","randomSecret","x","fallbackSecret","warnedNoSecret","resolveSecret","explicit","fromEnv","trackVisit","opts","onlyBots","skipBrowsers","pathname","originFromUrl","url","origin","ip","referer","country","geo","extractGeo","classification","verification","headlessMeaningful","distinctId","event","err","decode","v","fields","CaptureTransportError","status","body","RETRIEVAL","TRAINING","SEARCH","agentIntent","agentPolicy","intent","entry","posthogAnalytics","config","hostRaw","path","endpoint","fetchImpl","payload","res","webhookAnalytics","transform","e","customAnalytics","capture"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","/**\n * Keyed, non-reversible anonymous identifiers.\n *\n * The previous implementation was an unsalted 32-bit djb2 over `ip:userAgent`.\n * Because the user agent is emitted in plaintext on the same event, an attacker\n * held half the preimage and only had to search the IPv4 space — recovering a\n * residential IP took 75 seconds single-threaded. That is pseudonymisation, not\n * anonymisation, and it does not survive GDPR Recital 26.\n *\n * This uses HMAC-SHA-256 with a caller-supplied secret, truncated to 64 bits.\n * Web Crypto is available on Vercel Edge, Cloudflare Workers, Deno and Node 18+.\n */\n\n/** Thrown when a secret is missing or unusable. */\nexport class HashSecretError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'HashSecretError'\n }\n}\n\n/**\n * Web Crypto, or a clear error explaining why it is missing.\n *\n * `globalThis.crypto` is present by default from Node 19; on Node 18 it sat\n * behind `--experimental-global-webcrypto`. We require Node >= 20 rather than\n * shipping a `node:crypto` fallback, because a static import of a Node builtin\n * breaks bundling for the edge runtimes this library primarily targets — and\n * Node 18 reached end of life in April 2025.\n */\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto\n if (!c?.subtle) {\n throw new HashSecretError(\n 'Web Crypto is unavailable. agent-analytics requires Node >= 20, or any ' +\n 'runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare ' +\n 'Workers, Deno, browsers).'\n )\n }\n return c.subtle\n}\n\n// Importing a CryptoKey costs more than the signature itself, so keep one per\n// secret. Bounded by however many secrets a process configures — realistically\n// one.\nconst KEYS = new Map<string, Promise<CryptoKey>>()\n\nfunction keyFor(secret: string): Promise<CryptoKey> {\n let k = KEYS.get(secret)\n if (!k) {\n k = subtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n KEYS.set(secret, k)\n }\n return k\n}\n\n/**\n * Hash `input` under `secret`, returning `anon_` followed by 16 hex characters\n * (64 bits — collision-free well past any realistic distinct-visitor count).\n *\n * The secret must be stable across instances for identifiers to be comparable\n * over time, and secret from anyone who can read your events: publishing it\n * makes the identifier exactly as reversible as the old implementation was.\n * Rotating it deliberately breaks continuity, which is correct behaviour for a\n * privacy-preserving id.\n */\nexport async function hashId(input: string, secret: string): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new HashSecretError('hashId requires a non-empty secret')\n }\n const sig = await subtle().sign('HMAC', await keyFor(secret), new TextEncoder().encode(input))\n const bytes = new Uint8Array(sig, 0, 8)\n let out = ''\n for (const b of bytes) out += b.toString(16).padStart(2, '0')\n return 'anon_' + out\n}\n\n/**\n * Generate a random secret. Used as the default when none is configured, so the\n * privacy-preserving path is the one you get by doing nothing. Identifiers are\n * then only stable within a single instance's lifetime — set a real secret when\n * you need them comparable across instances and over time.\n */\nexport function randomSecret(): string {\n const b = new Uint8Array(32)\n subtle() // surface the same clear error if Web Crypto is missing\n globalThis.crypto.getRandomValues(b)\n let out = ''\n for (const x of b) out += x.toString(16).padStart(2, '0')\n return out\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { hashId, randomSecret } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Fallback secret, generated once per instance. Keeps the default path\n * privacy-preserving rather than making callers opt in to safety, at the cost\n * of identifiers that only correlate within one instance's lifetime.\n */\nlet fallbackSecret: string | undefined\nlet warnedNoSecret = false\n\nfunction resolveSecret(explicit: string | undefined): string {\n if (explicit) return explicit\n const fromEnv =\n typeof process !== 'undefined' ? process.env?.AGENT_ANALYTICS_ID_SECRET : undefined\n if (fromEnv) return fromEnv\n if (!fallbackSecret) {\n fallbackSecret = randomSecret()\n if (!warnedNoSecret) {\n warnedNoSecret = true\n // Once per instance, not once per request.\n console.warn(\n '[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. ' +\n 'Using a per-instance random secret: distinctIds will not correlate ' +\n 'across instances or deploys.'\n )\n }\n }\n return fallbackSecret\n}\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits the\n * adapter but routes errors to {@link TrackVisitOptions.onError} rather than\n * letting them reach the response path. Callers typically don't await it.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(req: Request, opts: TrackVisitOptions): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n try {\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n\n // Verification is injected rather than imported, so the published IP range\n // tables only reach bundles that actually use them. Import `verifyRequest`\n // from `@apideck/agent-analytics/verify` and pass it as `verify`.\n const verification = opts.verify ? opts.verify(req) : null\n\n // Headless scoring only discriminates for browser-shaped UAs. On a declared\n // crawler or an HTTP client it fires on nearly everything — measured true on\n // 99% of captured events — so it reads as signal when it is noise. Omitted\n // rather than emitted as a near-constant.\n const headlessMeaningful =\n classification.kind === 'headless-likely' || classification.kind === 'browser'\n\n const distinctId = await hashId(`${ip}:${userAgent}`, resolveSecret(opts.idSecret))\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n // Caller properties are spread FIRST so library-computed fields always\n // win. Spreading them last let a colliding key silently overwrite\n // bot_name or is_ai_bot — corrupting the very classification they were\n // meant to annotate.\n ...opts.properties,\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n ...(headlessMeaningful\n ? {\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false\n }\n : {}),\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null\n }\n }\n\n await opts.analytics.capture(event)\n } catch (err) {\n // Analytics must never affect the response — but silence is how a wrong API\n // key goes unnoticed for a week, so surface it when the caller asks.\n opts.onError?.(err instanceof Error ? err : new Error(String(err)))\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","/** Thrown when the analytics backend rejects, errors, or times out a capture. */\nexport class CaptureTransportError extends Error {\n readonly status: number | undefined\n readonly body: string | undefined\n constructor(message: string, status?: number, body?: string) {\n super(message)\n this.name = 'CaptureTransportError'\n this.status = status\n this.body = body\n }\n}\n","import { classifyRequest } from './bots.js'\nimport type { BotVerificationLike } from './types.js'\n\n/**\n * What to do with an agent request.\n *\n * - `'allow'` — serve it, free. Humans, search crawlers, and the retrieval\n * agents you *want* reading your site.\n * - `'meter'` — serve it, but count it as billable. Bulk corpus collection.\n * - `'charge'` — don't serve it until it pays (HTTP 402).\n * - `'block'` — refuse. Failed identity verification, mostly.\n */\nexport type AgentAction = 'allow' | 'meter' | 'charge' | 'block'\n\n/**\n * Why an agent fetched the page. This is the distinction the whole module\n * exists for, and no other signal on the request carries it.\n *\n * - `'retrieval'` — a person asked a question and the assistant went to read\n * the page for them. This is *demand*: the agent is a distribution channel,\n * and charging for it is charging for your own marketing.\n * - `'training'` — bulk corpus collection for model training. You get nothing\n * back per fetch, which is where a price makes sense.\n * - `'search'` — classic index crawlers. Blocking these costs you SEO.\n * - `'tooling'` — coding agents and HTTP clients. Usually developers using\n * your docs; treat like retrieval unless you see abuse.\n * - `'unknown'` — everything else, including real browsers.\n */\nexport type AgentIntent = 'retrieval' | 'training' | 'search' | 'tooling' | 'unknown'\n\n/**\n * User agents where a human is waiting on the answer. Deliberately explicit\n * rather than pattern-guessed: `-User` is not a reliable marker (OpenAI's\n * ChatGPT-User fetches server-side; Claude Code's Claude-User runs on a\n * laptop), and getting this wrong means charging your own demand channel.\n */\nconst RETRIEVAL = /ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i\n\n/** Bulk crawlers that collect corpora. No human is waiting on these. */\nconst TRAINING = /GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i\n\n/** Classic search indexers — blocking these costs you organic traffic. */\nconst SEARCH = /bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|Applebot(?!-Extended)/i\n\nexport interface AgentDecision {\n action: AgentAction\n intent: AgentIntent\n /** Vendor label, same string `parseBotName` returns. */\n label: string\n /** Identity verdict, when a verifier was supplied. */\n verification?: string\n /** Short human-readable justification — log it, don't parse it. */\n reason: string\n}\n\nexport interface AgentPolicyOptions {\n /**\n * Identity verifier. Import `verifyRequest` from\n * `@apideck/agent-analytics/verify` and pass it here to have a `spoofed`\n * verdict produce `'block'`.\n *\n * Injected rather than imported so the published IP range tables only reach\n * bundles that use them. Only meaningful when your edge controls\n * `x-forwarded-for`: behind a proxy that forwards a client-supplied header,\n * an attacker picks their own verdict.\n */\n verify?: (req: Request) => BotVerificationLike\n /** What to do with bulk training crawlers. Defaults to `'meter'`. */\n onTraining?: AgentAction\n /** What to do with retrieval agents. Defaults to `'allow'` — see AgentIntent. */\n onRetrieval?: AgentAction\n /** What to do with search indexers. Defaults to `'allow'`. */\n onSearch?: AgentAction\n /** What to do with coding agents and HTTP clients. Defaults to `'allow'`. */\n onTooling?: AgentAction\n /** Vendor labels or UA substrings always allowed, whatever the intent. */\n allowList?: readonly string[]\n}\n\n/** Classify why an agent is here, from its user agent alone. */\nexport function agentIntent(userAgent: string | null | undefined): AgentIntent {\n const ua = userAgent ?? ''\n if (!ua) return 'unknown'\n // Retrieval is checked first: several vendors ship both a bulk crawler and a\n // user-facing fetcher whose tokens overlap (ClaudeBot vs Claude-User).\n if (RETRIEVAL.test(ua)) return 'retrieval'\n if (TRAINING.test(ua)) return 'training'\n if (SEARCH.test(ua)) return 'search'\n return 'unknown'\n}\n\n/**\n * Decide what to do with a request. Pure classification plus policy — no\n * payment rails, no network calls, nothing to configure beyond the four\n * intent knobs.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { verify: true, onTraining: 'charge' })\n * if (decision.action === 'block') return new Response(null, { status: 403 })\n * if (decision.action === 'charge') return paymentRequired(decision)\n * ```\n */\nexport function agentPolicy(req: Request, opts: AgentPolicyOptions = {}): AgentDecision {\n const ua = req.headers.get('user-agent') || ''\n const classification = classifyRequest(req)\n const label = classification.label\n\n let intent = agentIntent(ua)\n // An HTTP-library UA that matched no vendor is a coding agent or a script.\n if (intent === 'unknown' && classification.codingAgentHint) intent = 'tooling'\n\n const allowed = opts.allowList?.some(\n (entry) => entry === label || ua.toLowerCase().includes(entry.toLowerCase())\n )\n if (allowed) {\n return { action: 'allow', intent, label, reason: 'on allowList' }\n }\n\n let verification: string | undefined\n if (opts.verify) {\n verification = opts.verify(req).verdict\n // Only 'spoofed' is actionable. 'unverifiable' means we couldn't check —\n // blocking on it would refuse every vendor without a published feed and\n // every coding agent running on someone's own machine.\n if (verification === 'spoofed') {\n return {\n action: 'block',\n intent,\n label,\n verification,\n reason: `${label} claimed but client IP is outside its published ranges`\n }\n }\n }\n\n const action: AgentAction =\n intent === 'training'\n ? (opts.onTraining ?? 'meter')\n : intent === 'retrieval'\n ? (opts.onRetrieval ?? 'allow')\n : intent === 'search'\n ? (opts.onSearch ?? 'allow')\n : intent === 'tooling'\n ? (opts.onTooling ?? 'allow')\n : 'allow'\n\n const REASONS: Record<AgentIntent, string> = {\n retrieval: 'a person is waiting on this answer',\n training: 'bulk corpus collection',\n search: 'search index crawler',\n tooling: 'coding agent or HTTP client',\n unknown: 'not a recognised agent'\n }\n\n return {\n action,\n intent,\n label,\n // Spread rather than assign: `exactOptionalPropertyTypes` distinguishes an\n // absent key from one explicitly set to undefined.\n ...(verification ? { verification } : {}),\n reason: REASONS[intent]\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n /**\n * Abort the capture after this many milliseconds. Defaults to 3000. Without\n * a bound, a hung backend leaves a pending promise for the lifetime of an\n * edge invocation.\n */\n timeoutMs?: number\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n // A 401 from a mistyped key used to look identical to success. Surface\n // it: `trackVisit` routes it to `onError` and still never throws into\n // the response path.\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `PostHog capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n /** Abort the capture after this many milliseconds. Defaults to 3000. */\n timeoutMs?: number\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const res = await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `Webhook capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
|
package/dist/markdown.cjs
CHANGED
|
@@ -1,68 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/bots.ts
|
|
4
|
-
var AI_BOT_PATTERN = /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i;
|
|
5
|
-
function isAiBot(userAgent) {
|
|
6
|
-
if (!userAgent) return false;
|
|
7
|
-
return AI_BOT_PATTERN.test(userAgent);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// src/markdown.ts
|
|
11
|
-
function markdownServeDecision(req) {
|
|
12
|
-
let pathname = "/";
|
|
13
|
-
try {
|
|
14
|
-
pathname = new URL(req.url).pathname;
|
|
15
|
-
} catch {
|
|
16
|
-
pathname = req.url || "/";
|
|
17
|
-
}
|
|
18
|
-
const ua = req.headers.get("user-agent") || "";
|
|
19
|
-
if (isAiBot(ua)) {
|
|
20
|
-
return { reason: "ua-rewrite", strippedPath: pathname };
|
|
21
|
-
}
|
|
22
|
-
if (pathname.endsWith(".md")) {
|
|
23
|
-
return { reason: "md-suffix", strippedPath: pathname.replace(/\.md$/, "") };
|
|
24
|
-
}
|
|
25
|
-
const accept = req.headers.get("accept") || "";
|
|
26
|
-
if (accept.includes("text/markdown")) {
|
|
27
|
-
return { reason: "accept-header", strippedPath: pathname };
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
function markdownHeaders(input = {}) {
|
|
32
|
-
const headers = {
|
|
33
|
-
"Content-Type": "text/markdown; charset=utf-8",
|
|
34
|
-
"Content-Signal": input.contentSignal ?? "search=yes, ai-input=yes, ai-train=no",
|
|
35
|
-
Vary: "accept"
|
|
36
|
-
};
|
|
37
|
-
if (typeof input.tokens === "number" && input.tokens > 0) {
|
|
38
|
-
headers["x-markdown-tokens"] = Math.max(1, Math.ceil(input.tokens)).toString();
|
|
39
|
-
}
|
|
40
|
-
return headers;
|
|
41
|
-
}
|
|
42
|
-
function synthesizeMarkdownPointer(input) {
|
|
43
|
-
const site = input.siteName ?? (() => {
|
|
44
|
-
try {
|
|
45
|
-
return new URL(input.origin).hostname;
|
|
46
|
-
} catch {
|
|
47
|
-
return input.origin;
|
|
48
|
-
}
|
|
49
|
-
})();
|
|
50
|
-
const url = `${input.origin}${input.pathname}`;
|
|
51
|
-
const lines = [`# ${site}`, "", `This page (${url}) does not have a dedicated Markdown mirror yet.`, ""];
|
|
52
|
-
const links = [];
|
|
53
|
-
if (input.llmsTxtUrl) links.push(`- [${input.llmsTxtUrl}](${input.llmsTxtUrl}) \u2014 curated index of docs`);
|
|
54
|
-
if (input.llmsFullTxtUrl)
|
|
55
|
-
links.push(`- [${input.llmsFullTxtUrl}](${input.llmsFullTxtUrl}) \u2014 full enumerated index`);
|
|
56
|
-
if (input.markdownIndexUrl)
|
|
57
|
-
links.push(`- [${input.markdownIndexUrl}](${input.markdownIndexUrl}) \u2014 JSON index of all Markdown paths`);
|
|
58
|
-
if (links.length) {
|
|
59
|
-
lines.push("For machine-readable documentation, see:", "", ...links, "");
|
|
60
|
-
}
|
|
61
|
-
return lines.join("\n");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
exports.markdownHeaders = markdownHeaders;
|
|
65
|
-
exports.markdownServeDecision = markdownServeDecision;
|
|
66
|
-
exports.synthesizeMarkdownPointer = synthesizeMarkdownPointer;
|
|
67
|
-
//# sourceMappingURL=markdown.cjs.map
|
|
1
|
+
'use strict';var o=/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i;function s(e){return e?o.test(e):false}function c(e){let t="/";try{t=new URL(e.url).pathname;}catch{t=e.url||"/";}let i=t.endsWith(".md"),r=i?t.slice(0,-3):t,n=e.headers.get("user-agent")||"";return s(n)?{reason:i?"md-suffix":"ua-rewrite",strippedPath:r}:i?{reason:"md-suffix",strippedPath:r}:(e.headers.get("accept")||"").includes("text/markdown")?{reason:"accept-header",strippedPath:r}:null}function d(e={}){let t={"Content-Type":"text/markdown; charset=utf-8","Content-Signal":e.contentSignal??"search=yes, ai-input=yes, ai-train=no",Vary:"accept"};return typeof e.tokens=="number"&&e.tokens>0&&(t["x-markdown-tokens"]=Math.max(1,Math.ceil(e.tokens)).toString()),t}function f(e){let t=e.siteName??(()=>{try{return new URL(e.origin).hostname}catch{return e.origin}})(),i=`${e.origin}${e.pathname}`,r=[`# ${t}`,"",`This page (${i}) does not have a dedicated Markdown mirror yet.`,""],n=[];return e.llmsTxtUrl&&n.push(`- [${e.llmsTxtUrl}](${e.llmsTxtUrl}) \u2014 curated index of docs`),e.llmsFullTxtUrl&&n.push(`- [${e.llmsFullTxtUrl}](${e.llmsFullTxtUrl}) \u2014 full enumerated index`),e.markdownIndexUrl&&n.push(`- [${e.markdownIndexUrl}](${e.markdownIndexUrl}) \u2014 JSON index of all Markdown paths`),n.length&&r.push("For machine-readable documentation, see:","",...n,""),r.join(`
|
|
2
|
+
`)}exports.markdownHeaders=d;exports.markdownServeDecision=c;exports.synthesizeMarkdownPointer=f;//# sourceMappingURL=markdown.cjs.map
|
|
68
3
|
//# sourceMappingURL=markdown.cjs.map
|
package/dist/markdown.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bots.ts","../src/markdown.ts"],"names":[],"mappings":"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","file":"markdown.cjs","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n }\n\n return { ...base, kind, headless }\n}\n","import { isAiBot } from './bots.js'\n\nexport type MarkdownServeReason =\n | 'ua-rewrite'\n | 'md-suffix'\n | 'accept-header'\n\nexport interface MarkdownDecision {\n /** Why this request should be served Markdown. */\n reason: MarkdownServeReason\n /**\n * The request's original logical path, with any trailing `.md` stripped.\n * Use this when mapping to a mirror file.\n */\n strippedPath: string\n}\n\n/**\n * Decide whether the request should be served Markdown instead of HTML.\n * Returns `null` when the request should go through your normal handler.\n *\n * Covers three triggers:\n * - Known AI-bot UA on any URL (`ua-rewrite`)\n * - Explicit `.md` suffix on the URL (`md-suffix`)\n * - `Accept: text/markdown` header (`accept-header`)\n *\n * This helper intentionally does not perform the rewrite itself — routing is\n * framework-specific (NextResponse.rewrite for Next.js, ctx.rewrite for\n * Hono, etc.). Use the returned decision to build the appropriate response.\n */\nexport function markdownServeDecision(req: Request): MarkdownDecision | null {\n let pathname = '/'\n try {\n pathname = new URL(req.url).pathname\n } catch {\n pathname = req.url || '/'\n }\n\n const ua = req.headers.get('user-agent') || ''\n if (isAiBot(ua)) {\n return { reason: 'ua-rewrite', strippedPath: pathname }\n }\n\n if (pathname.endsWith('.md')) {\n return { reason: 'md-suffix', strippedPath: pathname.replace(/\\.md$/, '') }\n }\n\n const accept = req.headers.get('accept') || ''\n if (accept.includes('text/markdown')) {\n return { reason: 'accept-header', strippedPath: pathname }\n }\n\n return null\n}\n\nexport interface MarkdownHeadersInput {\n /**\n * If provided, rendered as `x-markdown-tokens` so agents can budget context\n * before parsing the body. Typically `Math.ceil(body.length / 4)`.\n */\n tokens?: number\n /**\n * Content-Signal directive (see contentsignals.org). Defaults to\n * `'search=yes, ai-input=yes, ai-train=no'` — change if you want to permit\n * training or restrict indexing.\n */\n contentSignal?: string\n}\n\n/**\n * Build the set of response headers to attach to a Markdown response. Safe\n * defaults: UTF-8 text/markdown, Vary: accept, and a Content-Signal directive\n * that permits search + agent input but denies training.\n */\nexport function markdownHeaders(input: MarkdownHeadersInput = {}): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Signal': input.contentSignal ?? 'search=yes, ai-input=yes, ai-train=no',\n Vary: 'accept'\n }\n if (typeof input.tokens === 'number' && input.tokens > 0) {\n headers['x-markdown-tokens'] = Math.max(1, Math.ceil(input.tokens)).toString()\n }\n return headers\n}\n\nexport interface SynthesizePointerInput {\n origin: string\n pathname: string\n /** URL of the site's curated index, usually `/llms.txt`. */\n llmsTxtUrl?: string\n /** URL of the full enumerated index, usually `/llms-full.txt`. */\n llmsFullTxtUrl?: string\n /** URL of the machine-readable path manifest, usually `/md/index.json`. */\n markdownIndexUrl?: string\n /** Site name to title the pointer document. Defaults to the origin hostname. */\n siteName?: string\n}\n\n/**\n * Generate a minimal pointer Markdown document for URLs that don't have a\n * pre-built mirror. Keeps the `Accept: text/markdown` contract intact\n * site-wide — agents always get *something* parseable, not a 404.\n */\nexport function synthesizeMarkdownPointer(input: SynthesizePointerInput): string {\n const site =\n input.siteName ??\n (() => {\n try {\n return new URL(input.origin).hostname\n } catch {\n return input.origin\n }\n })()\n const url = `${input.origin}${input.pathname}`\n const lines: string[] = [`# ${site}`, '', `This page (${url}) does not have a dedicated Markdown mirror yet.`, '']\n const links: string[] = []\n if (input.llmsTxtUrl) links.push(`- [${input.llmsTxtUrl}](${input.llmsTxtUrl}) — curated index of docs`)\n if (input.llmsFullTxtUrl)\n links.push(`- [${input.llmsFullTxtUrl}](${input.llmsFullTxtUrl}) — full enumerated index`)\n if (input.markdownIndexUrl)\n links.push(`- [${input.markdownIndexUrl}](${input.markdownIndexUrl}) — JSON index of all Markdown paths`)\n if (links.length) {\n lines.push('For machine-readable documentation, see:', '', ...links, '')\n }\n return lines.join('\\n')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/bots.ts","../src/markdown.ts"],"names":["AI_BOT_PATTERN","isAiBot","userAgent","markdownServeDecision","req","pathname","hasSuffix","strippedPath","ua","markdownHeaders","input","headers","synthesizeMarkdownPointer","site","url","lines","links"],"mappings":"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","file":"markdown.cjs","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","import { isAiBot } from './bots.js'\n\nexport type MarkdownServeReason =\n | 'ua-rewrite'\n | 'md-suffix'\n | 'accept-header'\n\nexport interface MarkdownDecision {\n /** Why this request should be served Markdown. */\n reason: MarkdownServeReason\n /**\n * The request's original logical path, with any trailing `.md` stripped.\n * Use this when mapping to a mirror file.\n */\n strippedPath: string\n}\n\n/**\n * Decide whether the request should be served Markdown instead of HTML.\n * Returns `null` when the request should go through your normal handler.\n *\n * Covers three triggers:\n * - Known AI-bot UA on any URL (`ua-rewrite`)\n * - Explicit `.md` suffix on the URL (`md-suffix`)\n * - `Accept: text/markdown` header (`accept-header`)\n *\n * This helper intentionally does not perform the rewrite itself — routing is\n * framework-specific (NextResponse.rewrite for Next.js, ctx.rewrite for\n * Hono, etc.). Use the returned decision to build the appropriate response.\n */\nexport function markdownServeDecision(req: Request): MarkdownDecision | null {\n let pathname = '/'\n try {\n pathname = new URL(req.url).pathname\n } catch {\n pathname = req.url || '/'\n }\n\n // Strip once, up front. Stripping only inside the md-suffix branch meant an\n // AI bot following a `.md` link — the very links this library advertises via\n // `Link: rel=\"alternate\"` and llms.txt — matched `ua-rewrite` first and kept\n // the extension, so callers building `/md${strippedPath}.md` requested\n // `/md/docs/intro.md.md` and silently fell back to a pointer document.\n const hasSuffix = pathname.endsWith('.md')\n const strippedPath = hasSuffix ? pathname.slice(0, -3) : pathname\n\n const ua = req.headers.get('user-agent') || ''\n if (isAiBot(ua)) {\n return { reason: hasSuffix ? 'md-suffix' : 'ua-rewrite', strippedPath }\n }\n\n if (hasSuffix) {\n return { reason: 'md-suffix', strippedPath }\n }\n\n const accept = req.headers.get('accept') || ''\n if (accept.includes('text/markdown')) {\n return { reason: 'accept-header', strippedPath }\n }\n\n return null\n}\n\nexport interface MarkdownHeadersInput {\n /**\n * If provided, rendered as `x-markdown-tokens` so agents can budget context\n * before parsing the body. Typically `Math.ceil(body.length / 4)`.\n */\n tokens?: number\n /**\n * Content-Signal directive (see contentsignals.org). Defaults to\n * `'search=yes, ai-input=yes, ai-train=no'` — change if you want to permit\n * training or restrict indexing.\n */\n contentSignal?: string\n}\n\n/**\n * Build the set of response headers to attach to a Markdown response. Safe\n * defaults: UTF-8 text/markdown, Vary: accept, and a Content-Signal directive\n * that permits search + agent input but denies training.\n */\nexport function markdownHeaders(input: MarkdownHeadersInput = {}): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Signal': input.contentSignal ?? 'search=yes, ai-input=yes, ai-train=no',\n Vary: 'accept'\n }\n if (typeof input.tokens === 'number' && input.tokens > 0) {\n headers['x-markdown-tokens'] = Math.max(1, Math.ceil(input.tokens)).toString()\n }\n return headers\n}\n\nexport interface SynthesizePointerInput {\n origin: string\n pathname: string\n /** URL of the site's curated index, usually `/llms.txt`. */\n llmsTxtUrl?: string\n /** URL of the full enumerated index, usually `/llms-full.txt`. */\n llmsFullTxtUrl?: string\n /** URL of the machine-readable path manifest, usually `/md/index.json`. */\n markdownIndexUrl?: string\n /** Site name to title the pointer document. Defaults to the origin hostname. */\n siteName?: string\n}\n\n/**\n * Generate a minimal pointer Markdown document for URLs that don't have a\n * pre-built mirror. Keeps the `Accept: text/markdown` contract intact\n * site-wide — agents always get *something* parseable, not a 404.\n */\nexport function synthesizeMarkdownPointer(input: SynthesizePointerInput): string {\n const site =\n input.siteName ??\n (() => {\n try {\n return new URL(input.origin).hostname\n } catch {\n return input.origin\n }\n })()\n const url = `${input.origin}${input.pathname}`\n const lines: string[] = [`# ${site}`, '', `This page (${url}) does not have a dedicated Markdown mirror yet.`, '']\n const links: string[] = []\n if (input.llmsTxtUrl) links.push(`- [${input.llmsTxtUrl}](${input.llmsTxtUrl}) — curated index of docs`)\n if (input.llmsFullTxtUrl)\n links.push(`- [${input.llmsFullTxtUrl}](${input.llmsFullTxtUrl}) — full enumerated index`)\n if (input.markdownIndexUrl)\n links.push(`- [${input.markdownIndexUrl}](${input.markdownIndexUrl}) — JSON index of all Markdown paths`)\n if (links.length) {\n lines.push('For machine-readable documentation, see:', '', ...links, '')\n }\n return lines.join('\\n')\n}\n"]}
|
package/dist/markdown.js
CHANGED
|
@@ -1,64 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function isAiBot(userAgent) {
|
|
4
|
-
if (!userAgent) return false;
|
|
5
|
-
return AI_BOT_PATTERN.test(userAgent);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// src/markdown.ts
|
|
9
|
-
function markdownServeDecision(req) {
|
|
10
|
-
let pathname = "/";
|
|
11
|
-
try {
|
|
12
|
-
pathname = new URL(req.url).pathname;
|
|
13
|
-
} catch {
|
|
14
|
-
pathname = req.url || "/";
|
|
15
|
-
}
|
|
16
|
-
const ua = req.headers.get("user-agent") || "";
|
|
17
|
-
if (isAiBot(ua)) {
|
|
18
|
-
return { reason: "ua-rewrite", strippedPath: pathname };
|
|
19
|
-
}
|
|
20
|
-
if (pathname.endsWith(".md")) {
|
|
21
|
-
return { reason: "md-suffix", strippedPath: pathname.replace(/\.md$/, "") };
|
|
22
|
-
}
|
|
23
|
-
const accept = req.headers.get("accept") || "";
|
|
24
|
-
if (accept.includes("text/markdown")) {
|
|
25
|
-
return { reason: "accept-header", strippedPath: pathname };
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
function markdownHeaders(input = {}) {
|
|
30
|
-
const headers = {
|
|
31
|
-
"Content-Type": "text/markdown; charset=utf-8",
|
|
32
|
-
"Content-Signal": input.contentSignal ?? "search=yes, ai-input=yes, ai-train=no",
|
|
33
|
-
Vary: "accept"
|
|
34
|
-
};
|
|
35
|
-
if (typeof input.tokens === "number" && input.tokens > 0) {
|
|
36
|
-
headers["x-markdown-tokens"] = Math.max(1, Math.ceil(input.tokens)).toString();
|
|
37
|
-
}
|
|
38
|
-
return headers;
|
|
39
|
-
}
|
|
40
|
-
function synthesizeMarkdownPointer(input) {
|
|
41
|
-
const site = input.siteName ?? (() => {
|
|
42
|
-
try {
|
|
43
|
-
return new URL(input.origin).hostname;
|
|
44
|
-
} catch {
|
|
45
|
-
return input.origin;
|
|
46
|
-
}
|
|
47
|
-
})();
|
|
48
|
-
const url = `${input.origin}${input.pathname}`;
|
|
49
|
-
const lines = [`# ${site}`, "", `This page (${url}) does not have a dedicated Markdown mirror yet.`, ""];
|
|
50
|
-
const links = [];
|
|
51
|
-
if (input.llmsTxtUrl) links.push(`- [${input.llmsTxtUrl}](${input.llmsTxtUrl}) \u2014 curated index of docs`);
|
|
52
|
-
if (input.llmsFullTxtUrl)
|
|
53
|
-
links.push(`- [${input.llmsFullTxtUrl}](${input.llmsFullTxtUrl}) \u2014 full enumerated index`);
|
|
54
|
-
if (input.markdownIndexUrl)
|
|
55
|
-
links.push(`- [${input.markdownIndexUrl}](${input.markdownIndexUrl}) \u2014 JSON index of all Markdown paths`);
|
|
56
|
-
if (links.length) {
|
|
57
|
-
lines.push("For machine-readable documentation, see:", "", ...links, "");
|
|
58
|
-
}
|
|
59
|
-
return lines.join("\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export { markdownHeaders, markdownServeDecision, synthesizeMarkdownPointer };
|
|
63
|
-
//# sourceMappingURL=markdown.js.map
|
|
1
|
+
var o=/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i;function s(e){return e?o.test(e):false}function c(e){let t="/";try{t=new URL(e.url).pathname;}catch{t=e.url||"/";}let i=t.endsWith(".md"),r=i?t.slice(0,-3):t,n=e.headers.get("user-agent")||"";return s(n)?{reason:i?"md-suffix":"ua-rewrite",strippedPath:r}:i?{reason:"md-suffix",strippedPath:r}:(e.headers.get("accept")||"").includes("text/markdown")?{reason:"accept-header",strippedPath:r}:null}function d(e={}){let t={"Content-Type":"text/markdown; charset=utf-8","Content-Signal":e.contentSignal??"search=yes, ai-input=yes, ai-train=no",Vary:"accept"};return typeof e.tokens=="number"&&e.tokens>0&&(t["x-markdown-tokens"]=Math.max(1,Math.ceil(e.tokens)).toString()),t}function f(e){let t=e.siteName??(()=>{try{return new URL(e.origin).hostname}catch{return e.origin}})(),i=`${e.origin}${e.pathname}`,r=[`# ${t}`,"",`This page (${i}) does not have a dedicated Markdown mirror yet.`,""],n=[];return e.llmsTxtUrl&&n.push(`- [${e.llmsTxtUrl}](${e.llmsTxtUrl}) \u2014 curated index of docs`),e.llmsFullTxtUrl&&n.push(`- [${e.llmsFullTxtUrl}](${e.llmsFullTxtUrl}) \u2014 full enumerated index`),e.markdownIndexUrl&&n.push(`- [${e.markdownIndexUrl}](${e.markdownIndexUrl}) \u2014 JSON index of all Markdown paths`),n.length&&r.push("For machine-readable documentation, see:","",...n,""),r.join(`
|
|
2
|
+
`)}export{d as markdownHeaders,c as markdownServeDecision,f as synthesizeMarkdownPointer};//# sourceMappingURL=markdown.js.map
|
|
64
3
|
//# sourceMappingURL=markdown.js.map
|