@walkeros/server-transformer-bot 4.1.0 → 4.1.1-next-1779822275564

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @walkeros/server-transformer-bot
2
+
3
+ ## 4.1.1-next-1779822275564
4
+
5
+ ### Patch Changes
6
+
7
+ - f69e5f6: New server-side bot and AI-agent detection transformer. Annotates
8
+ events with `user.botScore`, `user.agentScore`, and optionally
9
+ `user.agentProduct` (the matched user-agent, e.g. `'ChatGPT-User'`). It wraps
10
+ `isbot` and a curated AI-agent UA map. Annotate-only — events are never
11
+ dropped; destinations filter via mapping.
12
+ - Updated dependencies [b0279ee]
13
+ - Updated dependencies [b0279ee]
14
+ - Updated dependencies [0b7f494]
15
+ - @walkeros/core@4.1.1-next-1779822275564
package/README.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  Server-side bot and AI-agent detection transformer for walkerOS.
4
4
 
5
- Annotates events with `user.botScore` (0-99, higher = more bot), `user.agentScore` (0-99, higher = more AI agent), and optionally `user.agentProduct` (matched UA substring). Never drops events — downstream destination mappings decide policy.
5
+ Annotates events with `user.botScore` (0-99, higher = more bot),
6
+ `user.agentScore` (0-99, higher = more AI agent), and optionally
7
+ `user.agentProduct` (matched UA substring). Never drops events — downstream
8
+ destination mappings decide policy.
6
9
 
7
10
  ## Install
8
11
 
@@ -53,60 +56,75 @@ After the transformer runs:
53
56
 
54
57
  ## What it detects (v1)
55
58
 
56
- | Visitor | botScore | agentScore | agentProduct |
57
- |---|---|---|---|
58
- | Real browser (Chrome, Firefox, Safari, Edge) | 0 | 0 | — |
59
- | Empty / missing User-Agent | 70 | 0 | — |
60
- | curl / wget / python-requests / well-known crawlers | 80 | 0 | — |
61
- | AI training crawlers (GPTBot, ClaudeBot, CCBot, Bytespider, etc.) | 95 | 95 | e.g. "GPTBot" |
62
- | AI search-index crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) | 95 | 95 | e.g. "PerplexityBot" |
63
- | AI user-action agents (ChatGPT-User, Claude-User, Perplexity-User, etc.) | 90 | 95 | e.g. "ChatGPT-User" |
59
+ | Visitor | botScore | agentScore | agentProduct |
60
+ | ------------------------------------------------------------------------- | -------- | ---------- | -------------------- |
61
+ | Real browser (Chrome, Firefox, Safari, Edge) | 0 | 0 | — |
62
+ | Empty / missing User-Agent | 70 | 0 | — |
63
+ | curl / wget / python-requests / well-known crawlers | 80 | 0 | — |
64
+ | AI training crawlers (GPTBot, ClaudeBot, CCBot, Bytespider, etc.) | 95 | 95 | e.g. "GPTBot" |
65
+ | AI search-index crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) | 95 | 95 | e.g. "PerplexityBot" |
66
+ | AI user-action agents (ChatGPT-User, Claude-User, Perplexity-User, etc.) | 90 | 95 | e.g. "ChatGPT-User" |
64
67
 
65
68
  ## Output paths
66
69
 
67
70
  All three outputs are configurable via `settings.output`:
68
71
 
69
- | Field | Default path | Notes |
70
- |---|---|---|
71
- | `botScore` | `user.botScore` | Set to `"ingest.bot.score"` to route to pipeline scratch instead of the event. Set to `""` to skip writing. |
72
- | `agentScore` | `user.agentScore` | v1 emits 0 or 95 only. |
73
- | `agentProduct` | (off) | Set to `"user.agentProduct"` or similar to enable. |
72
+ | Field | Default path | Notes |
73
+ | -------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- |
74
+ | `botScore` | `user.botScore` | Set to `"ingest.bot.score"` to route to pipeline scratch instead of the event. Set to `""` to skip writing. |
75
+ | `agentScore` | `user.agentScore` | v1 emits 0 or 95 only. |
76
+ | `agentProduct` | (off) | Set to `"user.agentProduct"` or similar to enable. |
74
77
 
75
78
  ## Destination filtering recipes
76
79
 
77
80
  Drop all bots:
78
81
 
79
- ```
82
+ ```sql
80
83
  event.user.botScore > 50
81
84
  ```
82
85
 
83
86
  Drop crawlers but keep user-action AI traffic:
84
87
 
85
- ```
88
+ ```sql
86
89
  event.user.botScore > 50 AND event.user.agentProduct NOT LIKE '%-User'
87
90
  ```
88
91
 
89
92
  AI traffic report:
90
93
 
91
- ```
94
+ ```sql
92
95
  event.user.agentScore > 50, grouped by event.user.agentProduct
93
96
  ```
94
97
 
95
98
  ## Not in v1 (planned for v1.1+)
96
99
 
97
- These signals are deferred. The `settings.input` schema reserves the relevant input field names so adding them in v1.1 will not be a breaking change.
98
-
99
- - **Header consistency heuristics** — `Sec-Fetch-*` missing on Chromium UAs, `Sec-CH-UA` major version mismatch with UA, missing `Accept-Language`. Requires a structured-headers parser, GREASE filtering, and a captured-headers fixture suite to avoid false positives on WebView, Tor, corporate proxies, and old Safari.
100
- - **ASN / datacenter-IP detection** — bring-your-own lookup function (the package will stay dependency-free; MaxMind GeoLite ASN's CC-BY-SA license precludes embedding).
101
- - **Reverse DNS verification** for true `verified-bot` status (e.g. confirming Googlebot is actually Google).
102
- - **Web-side runtime checks** `navigator.webdriver`, `userAgentData` from a browser source.
100
+ These signals are deferred. The `settings.input` schema reserves the relevant
101
+ input field names so adding them in v1.1 will not be a breaking change.
102
+
103
+ - **Header consistency heuristics** — `Sec-Fetch-*` missing on Chromium UAs,
104
+ `Sec-CH-UA` major version mismatch with UA, missing `Accept-Language`.
105
+ Requires a structured-headers parser, GREASE filtering, and a captured-headers
106
+ fixture suite to avoid false positives on WebView, Tor, corporate proxies, and
107
+ old Safari.
108
+ - **ASN / datacenter-IP detection** — bring-your-own lookup function (the
109
+ package will stay dependency-free; MaxMind GeoLite ASN's CC-BY-SA license
110
+ precludes embedding).
111
+ - **Reverse DNS verification** for true `verified-bot` status (e.g. confirming
112
+ Googlebot is actually Google).
113
+ - **Web-side runtime checks** — `navigator.webdriver`, `userAgentData` from a
114
+ browser source.
103
115
  - **Behavioral signals** (rate, session shape) — needs a store.
104
- - **TLS / JA4 fingerprinting** — not application-layer reachable; would consume an upstream-injected `ja4` header if provided.
105
- - **agentScore graduation** v1 emits 0 or 95. v1.1 will use intermediate values (e.g. 70 for unverified UA claim, 99 for IP-reverse-DNS verified).
116
+ - **TLS / JA4 fingerprinting** — not application-layer reachable; would consume
117
+ an upstream-injected `ja4` header if provided.
118
+ - **agentScore graduation** — v1 emits 0 or 95. v1.1 will use intermediate
119
+ values (e.g. 70 for unverified UA claim, 99 for IP-reverse-DNS verified).
106
120
 
107
121
  ## Limits
108
122
 
109
- Will not catch: residential-proxy + stealth-patched Chrome + realistic behavior; paid CAPTCHA-solver farms (2Captcha residential, etc.); real-browser-as-a-service providers (Bright Data, ScrapingBee, Browserbase, Hyperbrowser, Browserless). For that threat model use Cloudflare Bot Management, DataDome, or HUMAN.
123
+ Will not catch: residential-proxy + stealth-patched Chrome + realistic behavior;
124
+ paid CAPTCHA-solver farms (2Captcha residential, etc.);
125
+ real-browser-as-a-service providers (Bright Data, ScrapingBee, Browserbase,
126
+ Hyperbrowser, Browserless). For that threat model use Cloudflare Bot Management,
127
+ DataDome, or HUMAN.
110
128
 
111
129
  ## License
112
130
 
package/dist/dev.d.mts CHANGED
@@ -4,61 +4,105 @@ import { Flow, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  input: z.ZodOptional<z.ZodObject<{
7
- userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
7
+ userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
8
8
  key: z.ZodOptional<z.ZodString>;
9
9
  value: z.ZodOptional<z.ZodUnknown>;
10
10
  fn: z.ZodOptional<z.ZodString>;
11
- }, z.core.$strip>]>>;
12
- ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
11
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
13
12
  key: z.ZodOptional<z.ZodString>;
14
13
  value: z.ZodOptional<z.ZodUnknown>;
15
14
  fn: z.ZodOptional<z.ZodString>;
16
- }, z.core.$strip>]>>;
17
- acceptLanguage: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
15
+ }, z.core.$strip>]>>]>>;
16
+ ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
18
17
  key: z.ZodOptional<z.ZodString>;
19
18
  value: z.ZodOptional<z.ZodUnknown>;
20
19
  fn: z.ZodOptional<z.ZodString>;
21
- }, z.core.$strip>]>>;
22
- acceptEncoding: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
20
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
23
21
  key: z.ZodOptional<z.ZodString>;
24
22
  value: z.ZodOptional<z.ZodUnknown>;
25
23
  fn: z.ZodOptional<z.ZodString>;
26
- }, z.core.$strip>]>>;
27
- secFetchSite: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
24
+ }, z.core.$strip>]>>]>>;
25
+ acceptLanguage: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
28
26
  key: z.ZodOptional<z.ZodString>;
29
27
  value: z.ZodOptional<z.ZodUnknown>;
30
28
  fn: z.ZodOptional<z.ZodString>;
31
- }, z.core.$strip>]>>;
32
- secFetchMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
29
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
33
30
  key: z.ZodOptional<z.ZodString>;
34
31
  value: z.ZodOptional<z.ZodUnknown>;
35
32
  fn: z.ZodOptional<z.ZodString>;
36
- }, z.core.$strip>]>>;
37
- secFetchDest: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
33
+ }, z.core.$strip>]>>]>>;
34
+ acceptEncoding: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
35
  key: z.ZodOptional<z.ZodString>;
39
36
  value: z.ZodOptional<z.ZodUnknown>;
40
37
  fn: z.ZodOptional<z.ZodString>;
41
- }, z.core.$strip>]>>;
42
- secFetchUser: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
43
39
  key: z.ZodOptional<z.ZodString>;
44
40
  value: z.ZodOptional<z.ZodUnknown>;
45
41
  fn: z.ZodOptional<z.ZodString>;
46
- }, z.core.$strip>]>>;
47
- secChUa: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
42
+ }, z.core.$strip>]>>]>>;
43
+ secFetchSite: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
48
44
  key: z.ZodOptional<z.ZodString>;
49
45
  value: z.ZodOptional<z.ZodUnknown>;
50
46
  fn: z.ZodOptional<z.ZodString>;
51
- }, z.core.$strip>]>>;
52
- secChUaMobile: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
47
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
53
48
  key: z.ZodOptional<z.ZodString>;
54
49
  value: z.ZodOptional<z.ZodUnknown>;
55
50
  fn: z.ZodOptional<z.ZodString>;
56
- }, z.core.$strip>]>>;
57
- secChUaPlatform: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
51
+ }, z.core.$strip>]>>]>>;
52
+ secFetchMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
58
53
  key: z.ZodOptional<z.ZodString>;
59
54
  value: z.ZodOptional<z.ZodUnknown>;
60
55
  fn: z.ZodOptional<z.ZodString>;
61
- }, z.core.$strip>]>>;
56
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
57
+ key: z.ZodOptional<z.ZodString>;
58
+ value: z.ZodOptional<z.ZodUnknown>;
59
+ fn: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$strip>]>>]>>;
61
+ secFetchDest: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
62
+ key: z.ZodOptional<z.ZodString>;
63
+ value: z.ZodOptional<z.ZodUnknown>;
64
+ fn: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
66
+ key: z.ZodOptional<z.ZodString>;
67
+ value: z.ZodOptional<z.ZodUnknown>;
68
+ fn: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strip>]>>]>>;
70
+ secFetchUser: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
71
+ key: z.ZodOptional<z.ZodString>;
72
+ value: z.ZodOptional<z.ZodUnknown>;
73
+ fn: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
75
+ key: z.ZodOptional<z.ZodString>;
76
+ value: z.ZodOptional<z.ZodUnknown>;
77
+ fn: z.ZodOptional<z.ZodString>;
78
+ }, z.core.$strip>]>>]>>;
79
+ secChUa: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
80
+ key: z.ZodOptional<z.ZodString>;
81
+ value: z.ZodOptional<z.ZodUnknown>;
82
+ fn: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
84
+ key: z.ZodOptional<z.ZodString>;
85
+ value: z.ZodOptional<z.ZodUnknown>;
86
+ fn: z.ZodOptional<z.ZodString>;
87
+ }, z.core.$strip>]>>]>>;
88
+ secChUaMobile: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
89
+ key: z.ZodOptional<z.ZodString>;
90
+ value: z.ZodOptional<z.ZodUnknown>;
91
+ fn: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
93
+ key: z.ZodOptional<z.ZodString>;
94
+ value: z.ZodOptional<z.ZodUnknown>;
95
+ fn: z.ZodOptional<z.ZodString>;
96
+ }, z.core.$strip>]>>]>>;
97
+ secChUaPlatform: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
98
+ key: z.ZodOptional<z.ZodString>;
99
+ value: z.ZodOptional<z.ZodUnknown>;
100
+ fn: z.ZodOptional<z.ZodString>;
101
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
102
+ key: z.ZodOptional<z.ZodString>;
103
+ value: z.ZodOptional<z.ZodUnknown>;
104
+ fn: z.ZodOptional<z.ZodString>;
105
+ }, z.core.$strip>]>>]>>;
62
106
  }, z.core.$strip>>;
63
107
  output: z.ZodOptional<z.ZodObject<{
64
108
  botScore: z.ZodOptional<z.ZodString>;
package/dist/dev.d.ts CHANGED
@@ -4,61 +4,105 @@ import { Flow, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  input: z.ZodOptional<z.ZodObject<{
7
- userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
7
+ userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
8
8
  key: z.ZodOptional<z.ZodString>;
9
9
  value: z.ZodOptional<z.ZodUnknown>;
10
10
  fn: z.ZodOptional<z.ZodString>;
11
- }, z.core.$strip>]>>;
12
- ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
11
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
13
12
  key: z.ZodOptional<z.ZodString>;
14
13
  value: z.ZodOptional<z.ZodUnknown>;
15
14
  fn: z.ZodOptional<z.ZodString>;
16
- }, z.core.$strip>]>>;
17
- acceptLanguage: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
15
+ }, z.core.$strip>]>>]>>;
16
+ ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
18
17
  key: z.ZodOptional<z.ZodString>;
19
18
  value: z.ZodOptional<z.ZodUnknown>;
20
19
  fn: z.ZodOptional<z.ZodString>;
21
- }, z.core.$strip>]>>;
22
- acceptEncoding: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
20
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
23
21
  key: z.ZodOptional<z.ZodString>;
24
22
  value: z.ZodOptional<z.ZodUnknown>;
25
23
  fn: z.ZodOptional<z.ZodString>;
26
- }, z.core.$strip>]>>;
27
- secFetchSite: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
24
+ }, z.core.$strip>]>>]>>;
25
+ acceptLanguage: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
28
26
  key: z.ZodOptional<z.ZodString>;
29
27
  value: z.ZodOptional<z.ZodUnknown>;
30
28
  fn: z.ZodOptional<z.ZodString>;
31
- }, z.core.$strip>]>>;
32
- secFetchMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
29
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
33
30
  key: z.ZodOptional<z.ZodString>;
34
31
  value: z.ZodOptional<z.ZodUnknown>;
35
32
  fn: z.ZodOptional<z.ZodString>;
36
- }, z.core.$strip>]>>;
37
- secFetchDest: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
33
+ }, z.core.$strip>]>>]>>;
34
+ acceptEncoding: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
35
  key: z.ZodOptional<z.ZodString>;
39
36
  value: z.ZodOptional<z.ZodUnknown>;
40
37
  fn: z.ZodOptional<z.ZodString>;
41
- }, z.core.$strip>]>>;
42
- secFetchUser: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
43
39
  key: z.ZodOptional<z.ZodString>;
44
40
  value: z.ZodOptional<z.ZodUnknown>;
45
41
  fn: z.ZodOptional<z.ZodString>;
46
- }, z.core.$strip>]>>;
47
- secChUa: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
42
+ }, z.core.$strip>]>>]>>;
43
+ secFetchSite: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
48
44
  key: z.ZodOptional<z.ZodString>;
49
45
  value: z.ZodOptional<z.ZodUnknown>;
50
46
  fn: z.ZodOptional<z.ZodString>;
51
- }, z.core.$strip>]>>;
52
- secChUaMobile: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
47
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
53
48
  key: z.ZodOptional<z.ZodString>;
54
49
  value: z.ZodOptional<z.ZodUnknown>;
55
50
  fn: z.ZodOptional<z.ZodString>;
56
- }, z.core.$strip>]>>;
57
- secChUaPlatform: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
51
+ }, z.core.$strip>]>>]>>;
52
+ secFetchMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
58
53
  key: z.ZodOptional<z.ZodString>;
59
54
  value: z.ZodOptional<z.ZodUnknown>;
60
55
  fn: z.ZodOptional<z.ZodString>;
61
- }, z.core.$strip>]>>;
56
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
57
+ key: z.ZodOptional<z.ZodString>;
58
+ value: z.ZodOptional<z.ZodUnknown>;
59
+ fn: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$strip>]>>]>>;
61
+ secFetchDest: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
62
+ key: z.ZodOptional<z.ZodString>;
63
+ value: z.ZodOptional<z.ZodUnknown>;
64
+ fn: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
66
+ key: z.ZodOptional<z.ZodString>;
67
+ value: z.ZodOptional<z.ZodUnknown>;
68
+ fn: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strip>]>>]>>;
70
+ secFetchUser: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
71
+ key: z.ZodOptional<z.ZodString>;
72
+ value: z.ZodOptional<z.ZodUnknown>;
73
+ fn: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
75
+ key: z.ZodOptional<z.ZodString>;
76
+ value: z.ZodOptional<z.ZodUnknown>;
77
+ fn: z.ZodOptional<z.ZodString>;
78
+ }, z.core.$strip>]>>]>>;
79
+ secChUa: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
80
+ key: z.ZodOptional<z.ZodString>;
81
+ value: z.ZodOptional<z.ZodUnknown>;
82
+ fn: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
84
+ key: z.ZodOptional<z.ZodString>;
85
+ value: z.ZodOptional<z.ZodUnknown>;
86
+ fn: z.ZodOptional<z.ZodString>;
87
+ }, z.core.$strip>]>>]>>;
88
+ secChUaMobile: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
89
+ key: z.ZodOptional<z.ZodString>;
90
+ value: z.ZodOptional<z.ZodUnknown>;
91
+ fn: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
93
+ key: z.ZodOptional<z.ZodString>;
94
+ value: z.ZodOptional<z.ZodUnknown>;
95
+ fn: z.ZodOptional<z.ZodString>;
96
+ }, z.core.$strip>]>>]>>;
97
+ secChUaPlatform: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
98
+ key: z.ZodOptional<z.ZodString>;
99
+ value: z.ZodOptional<z.ZodUnknown>;
100
+ fn: z.ZodOptional<z.ZodString>;
101
+ }, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
102
+ key: z.ZodOptional<z.ZodString>;
103
+ value: z.ZodOptional<z.ZodUnknown>;
104
+ fn: z.ZodOptional<z.ZodString>;
105
+ }, z.core.$strip>]>>]>>;
62
106
  }, z.core.$strip>>;
63
107
  output: z.ZodOptional<z.ZodObject<{
64
108
  botScore: z.ZodOptional<z.ZodString>;
package/dist/dev.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,i=(e,r)=>{for(var o in r)t(e,o,{get:r[o],enumerable:!0})},a={};i(a,{examples:()=>d,hints:()=>w,schemas:()=>s}),module.exports=(e=a,((e,i,a,s)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let c of o(i))n.call(e,c)||c===a||t(e,c,{get:()=>i[c],enumerable:!(s=r(i,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var s={};i(s,{SettingsSchema:()=>p,settings:()=>u});var c=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),g=l.z.union([l.z.string().describe('Dot-notation path like "ingest.userAgent"'),l.z.object({key:l.z.string().optional(),value:l.z.unknown().optional(),fn:l.z.string().optional()}).describe("Mapping value object")]),p=l.z.object({input:l.z.object({userAgent:g.optional(),ip:g.optional(),acceptLanguage:g.optional(),acceptEncoding:g.optional(),secFetchSite:g.optional(),secFetchMode:g.optional(),secFetchDest:g.optional(),secFetchUser:g.optional(),secChUa:g.optional(),secChUaMobile:g.optional(),secChUaPlatform:g.optional()}).optional().describe("Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics."),output:l.z.object({botScore:l.z.string().optional().describe('Path for bot score (0-99, higher = more bot). Default: "user.botScore". Use "ingest.*" to route to pipeline scratch instead of the event. Empty string or omit = skip.'),agentScore:l.z.string().optional().describe('Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: "user.agentScore".'),agentProduct:l.z.string().optional().describe('Path for matched UA substring (e.g. "ChatGPT-User"). Off by default — set to enable.')}).optional().describe("Output paths for bot/agent annotations.")}).describe("Bot detection transformer: annotates events with bot and AI-agent scores."),u=(0,c.zodToSchema)(p),d={};i(d,{step:()=>h});var h={};i(h,{chatgptUserAgent:()=>f,curlClient:()=>A,gptBotCrawler:()=>v,humanChrome:()=>m,missingUA:()=>S});var b={name:"page view",data:{title:"Home",id:"/"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},m={title:"Human visitor (Chrome)",description:"Modern Chrome UA. No bot or agent signals.",in:{...b},out:[["return",{event:{...b,user:{botScore:0,agentScore:0}}}]]},v={title:"GPTBot training crawler",description:"OpenAI training crawler. Both botScore and agentScore are high.",in:{...b,id:"ev-1700000601"},out:[["return",{event:{...b,id:"ev-1700000601",user:{botScore:95,agentScore:95}}}]]},f={title:"ChatGPT-User (user-action AI)",description:"A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.",in:{...b,id:"ev-1700000602"},out:[["return",{event:{...b,id:"ev-1700000602",user:{botScore:90,agentScore:95}}}]]},A={public:!1,description:"curl client — caught by isbot. agentScore zero.",in:{...b,id:"ev-1700000603"},out:[["return",{event:{...b,id:"ev-1700000603",user:{botScore:80,agentScore:0}}}]]},S={public:!1,description:"No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).",in:{...b,id:"ev-1700000604"},out:[["return",{event:{...b,id:"ev-1700000604",user:{botScore:70,agentScore:0}}}]]},w={"ingest-prerequisite":{text:'The bot transformer reads userAgent from ctx.ingest (path "ingest.userAgent" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{ingest:{userAgent:"req.headers.user-agent"}}}},transformers:{bot:{package:"@walkeros/server-transformer-bot"}}},null,2)}]},"output-routing":{text:"Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.",code:[{lang:"json",code:JSON.stringify({transformers:{bot:{package:"@walkeros/server-transformer-bot",config:{settings:{output:{botScore:"ingest.bot.score",agentScore:"ingest.bot.agent",agentProduct:"user.agentProduct"}}}}}},null,2)}]},"destination-filtering":{text:'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE "%-User". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.'},"detection-scope":{text:"v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN."}};//# sourceMappingURL=dev.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,i=(e,r)=>{for(var o in r)t(e,o,{get:r[o],enumerable:!0})},a={};i(a,{examples:()=>h,hints:()=>w,schemas:()=>s}),module.exports=(e=a,((e,i,a,s)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let c of o(i))n.call(e,c)||c===a||t(e,c,{get:()=>i[c],enumerable:!(s=r(i,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var s={};i(s,{SettingsSchema:()=>p,settings:()=>d});var c=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),g=l.z.union([l.z.string().describe('Dot-notation path like "ingest.userAgent"'),l.z.object({key:l.z.string().optional(),value:l.z.unknown().optional(),fn:l.z.string().optional()}).describe("Mapping value object")]),u=l.z.union([g,l.z.array(g).describe("Array of fallback values, tried in order")]),p=l.z.object({input:l.z.object({userAgent:u.optional(),ip:u.optional(),acceptLanguage:u.optional(),acceptEncoding:u.optional(),secFetchSite:u.optional(),secFetchMode:u.optional(),secFetchDest:u.optional(),secFetchUser:u.optional(),secChUa:u.optional(),secChUaMobile:u.optional(),secChUaPlatform:u.optional()}).optional().describe("Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics."),output:l.z.object({botScore:l.z.string().optional().describe('Path for bot score (0-99, higher = more bot). Default: "user.botScore". Use "ingest.*" to route to pipeline scratch instead of the event. Empty string or omit = skip.'),agentScore:l.z.string().optional().describe('Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: "user.agentScore".'),agentProduct:l.z.string().optional().describe('Path for matched UA substring (e.g. "ChatGPT-User"). Off by default — set to enable.')}).optional().describe("Output paths for bot/agent annotations.")}).describe("Bot detection transformer: annotates events with bot and AI-agent scores."),d=(0,c.zodToSchema)(p),h={};i(h,{step:()=>b});var b={};i(b,{chatgptUserAgent:()=>A,curlClient:()=>S,gptBotCrawler:()=>f,humanChrome:()=>v,missingUA:()=>y});var m={name:"page view",data:{title:"Home",id:"/"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},v={title:"Human visitor (Chrome)",description:"Modern Chrome UA. No bot or agent signals.",in:{...m},out:[["return",{event:{...m,user:{botScore:0,agentScore:0}}}]]},f={title:"GPTBot training crawler",description:"OpenAI training crawler. Both botScore and agentScore are high.",in:{...m,id:"ev-1700000601"},out:[["return",{event:{...m,id:"ev-1700000601",user:{botScore:95,agentScore:95}}}]]},A={title:"ChatGPT-User (user-action AI)",description:"A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.",in:{...m,id:"ev-1700000602"},out:[["return",{event:{...m,id:"ev-1700000602",user:{botScore:90,agentScore:95}}}]]},S={public:!1,description:"curl client — caught by isbot. agentScore zero.",in:{...m,id:"ev-1700000603"},out:[["return",{event:{...m,id:"ev-1700000603",user:{botScore:80,agentScore:0}}}]]},y={public:!1,description:"No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).",in:{...m,id:"ev-1700000604"},out:[["return",{event:{...m,id:"ev-1700000604",user:{botScore:70,agentScore:0}}}]]},w={"ingest-prerequisite":{text:'The bot transformer reads userAgent from ctx.ingest (path "ingest.userAgent" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{ingest:{userAgent:"req.headers.user-agent"}}}},transformers:{bot:{package:"@walkeros/server-transformer-bot"}}},null,2)}]},"output-routing":{text:"Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.",code:[{lang:"json",code:JSON.stringify({transformers:{bot:{package:"@walkeros/server-transformer-bot",config:{settings:{output:{botScore:"ingest.bot.score",agentScore:"ingest.bot.agent",agentProduct:"user.agentProduct"}}}}}},null,2)}]},"destination-filtering":{text:'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE "%-User". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.'},"detection-scope":{text:"v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN."}};//# sourceMappingURL=dev.js.map
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst MappingValueSchema = z.union([\n z.string().describe('Dot-notation path like \"ingest.userAgent\"'),\n z\n .object({\n key: z.string().optional(),\n value: z.unknown().optional(),\n fn: z.string().optional(),\n })\n .describe('Mapping value object'),\n]);\n\nexport const SettingsSchema = z\n .object({\n input: z\n .object({\n userAgent: MappingValueSchema.optional(),\n ip: MappingValueSchema.optional(),\n acceptLanguage: MappingValueSchema.optional(),\n acceptEncoding: MappingValueSchema.optional(),\n secFetchSite: MappingValueSchema.optional(),\n secFetchMode: MappingValueSchema.optional(),\n secFetchDest: MappingValueSchema.optional(),\n secFetchUser: MappingValueSchema.optional(),\n secChUa: MappingValueSchema.optional(),\n secChUaMobile: MappingValueSchema.optional(),\n secChUaPlatform: MappingValueSchema.optional(),\n })\n .optional()\n .describe(\n 'Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics.',\n ),\n output: z\n .object({\n botScore: z\n .string()\n .optional()\n .describe(\n 'Path for bot score (0-99, higher = more bot). Default: \"user.botScore\". Use \"ingest.*\" to route to pipeline scratch instead of the event. Empty string or omit = skip.',\n ),\n agentScore: z\n .string()\n .optional()\n .describe(\n 'Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: \"user.agentScore\".',\n ),\n agentProduct: z\n .string()\n .optional()\n .describe(\n 'Path for matched UA substring (e.g. \"ChatGPT-User\"). Off by default — set to enable.',\n ),\n })\n .optional()\n .describe('Output paths for bot/agent annotations.'),\n })\n .describe(\n 'Bot detection transformer: annotates events with bot and AI-agent scores.',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nconst baseEvent = {\n name: 'page view',\n data: { title: 'Home', id: '/' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' as const },\n};\n\n/** Real Chrome — botScore 0, agentScore 0. */\nexport const humanChrome: Flow.StepExample = {\n title: 'Human visitor (Chrome)',\n description: 'Modern Chrome UA. No bot or agent signals.',\n in: { ...baseEvent },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n user: { botScore: 0, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** GPTBot — training crawler. */\nexport const gptBotCrawler: Flow.StepExample = {\n title: 'GPTBot training crawler',\n description:\n 'OpenAI training crawler. Both botScore and agentScore are high.',\n in: { ...baseEvent, id: 'ev-1700000601' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000601',\n user: { botScore: 95, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** ChatGPT-User — user-action AI agent. */\nexport const chatgptUserAgent: Flow.StepExample = {\n title: 'ChatGPT-User (user-action AI)',\n description:\n 'A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.',\n in: { ...baseEvent, id: 'ev-1700000602' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000602',\n user: { botScore: 90, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** curl — caught by isbot. */\nexport const curlClient: Flow.StepExample = {\n public: false,\n description: 'curl client — caught by isbot. agentScore zero.',\n in: { ...baseEvent, id: 'ev-1700000603' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000603',\n user: { botScore: 80, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** Empty / missing UA — score 70 (suspicious; real browsers rarely strip UA). */\nexport const missingUA: Flow.StepExample = {\n public: false,\n description:\n 'No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).',\n in: { ...baseEvent, id: 'ev-1700000604' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000604',\n user: { botScore: 70, agentScore: 0 },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The bot transformer reads userAgent from ctx.ingest (path \"ingest.userAgent\" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n ingest: {\n userAgent: 'req.headers.user-agent',\n },\n },\n },\n },\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'output-routing': {\n text: 'Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n config: {\n settings: {\n output: {\n botScore: 'ingest.bot.score',\n agentScore: 'ingest.bot.agent',\n agentProduct: 'user.agentProduct',\n },\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'destination-filtering': {\n text: 'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE \"%-User\". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.',\n },\n 'detection-scope': {\n text: 'v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN.',\n },\n};\n"],"mappings":"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","names":["import_dev"]}
1
+ {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst SingleMappingValueSchema = z.union([\n z.string().describe('Dot-notation path like \"ingest.userAgent\"'),\n z\n .object({\n key: z.string().optional(),\n value: z.unknown().optional(),\n fn: z.string().optional(),\n })\n .describe('Mapping value object'),\n]);\n\nconst MappingValueSchema = z.union([\n SingleMappingValueSchema,\n z\n .array(SingleMappingValueSchema)\n .describe('Array of fallback values, tried in order'),\n]);\n\nexport const SettingsSchema = z\n .object({\n input: z\n .object({\n userAgent: MappingValueSchema.optional(),\n ip: MappingValueSchema.optional(),\n acceptLanguage: MappingValueSchema.optional(),\n acceptEncoding: MappingValueSchema.optional(),\n secFetchSite: MappingValueSchema.optional(),\n secFetchMode: MappingValueSchema.optional(),\n secFetchDest: MappingValueSchema.optional(),\n secFetchUser: MappingValueSchema.optional(),\n secChUa: MappingValueSchema.optional(),\n secChUaMobile: MappingValueSchema.optional(),\n secChUaPlatform: MappingValueSchema.optional(),\n })\n .optional()\n .describe(\n 'Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics.',\n ),\n output: z\n .object({\n botScore: z\n .string()\n .optional()\n .describe(\n 'Path for bot score (0-99, higher = more bot). Default: \"user.botScore\". Use \"ingest.*\" to route to pipeline scratch instead of the event. Empty string or omit = skip.',\n ),\n agentScore: z\n .string()\n .optional()\n .describe(\n 'Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: \"user.agentScore\".',\n ),\n agentProduct: z\n .string()\n .optional()\n .describe(\n 'Path for matched UA substring (e.g. \"ChatGPT-User\"). Off by default — set to enable.',\n ),\n })\n .optional()\n .describe('Output paths for bot/agent annotations.'),\n })\n .describe(\n 'Bot detection transformer: annotates events with bot and AI-agent scores.',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nconst baseEvent = {\n name: 'page view',\n data: { title: 'Home', id: '/' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' as const },\n};\n\n/** Real Chrome — botScore 0, agentScore 0. */\nexport const humanChrome: Flow.StepExample = {\n title: 'Human visitor (Chrome)',\n description: 'Modern Chrome UA. No bot or agent signals.',\n in: { ...baseEvent },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n user: { botScore: 0, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** GPTBot — training crawler. */\nexport const gptBotCrawler: Flow.StepExample = {\n title: 'GPTBot training crawler',\n description:\n 'OpenAI training crawler. Both botScore and agentScore are high.',\n in: { ...baseEvent, id: 'ev-1700000601' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000601',\n user: { botScore: 95, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** ChatGPT-User — user-action AI agent. */\nexport const chatgptUserAgent: Flow.StepExample = {\n title: 'ChatGPT-User (user-action AI)',\n description:\n 'A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.',\n in: { ...baseEvent, id: 'ev-1700000602' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000602',\n user: { botScore: 90, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** curl — caught by isbot. */\nexport const curlClient: Flow.StepExample = {\n public: false,\n description: 'curl client — caught by isbot. agentScore zero.',\n in: { ...baseEvent, id: 'ev-1700000603' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000603',\n user: { botScore: 80, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** Empty / missing UA — score 70 (suspicious; real browsers rarely strip UA). */\nexport const missingUA: Flow.StepExample = {\n public: false,\n description:\n 'No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).',\n in: { ...baseEvent, id: 'ev-1700000604' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000604',\n user: { botScore: 70, agentScore: 0 },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The bot transformer reads userAgent from ctx.ingest (path \"ingest.userAgent\" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n ingest: {\n userAgent: 'req.headers.user-agent',\n },\n },\n },\n },\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'output-routing': {\n text: 'Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n config: {\n settings: {\n output: {\n botScore: 'ingest.bot.score',\n agentScore: 'ingest.bot.agent',\n agentProduct: 'user.agentProduct',\n },\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'destination-filtering': {\n text: 'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE \"%-User\". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.',\n },\n 'detection-scope': {\n text: 'v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN.',\n },\n};\n"],"mappings":"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","names":["import_dev"]}
package/dist/dev.mjs CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,r)=>{for(var o in r)e(t,o,{get:r[o],enumerable:!0})},r={};t(r,{SettingsSchema:()=>a,settings:()=>s});import{zodToSchema as o}from"@walkeros/core/dev";import{z as n}from"@walkeros/core/dev";var i=n.union([n.string().describe('Dot-notation path like "ingest.userAgent"'),n.object({key:n.string().optional(),value:n.unknown().optional(),fn:n.string().optional()}).describe("Mapping value object")]),a=n.object({input:n.object({userAgent:i.optional(),ip:i.optional(),acceptLanguage:i.optional(),acceptEncoding:i.optional(),secFetchSite:i.optional(),secFetchMode:i.optional(),secFetchDest:i.optional(),secFetchUser:i.optional(),secChUa:i.optional(),secChUaMobile:i.optional(),secChUaPlatform:i.optional()}).optional().describe("Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics."),output:n.object({botScore:n.string().optional().describe('Path for bot score (0-99, higher = more bot). Default: "user.botScore". Use "ingest.*" to route to pipeline scratch instead of the event. Empty string or omit = skip.'),agentScore:n.string().optional().describe('Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: "user.agentScore".'),agentProduct:n.string().optional().describe('Path for matched UA substring (e.g. "ChatGPT-User"). Off by default — set to enable.')}).optional().describe("Output paths for bot/agent annotations.")}).describe("Bot detection transformer: annotates events with bot and AI-agent scores."),s=o(a),c={};t(c,{step:()=>l});var l={};t(l,{chatgptUserAgent:()=>d,curlClient:()=>h,gptBotCrawler:()=>u,humanChrome:()=>p,missingUA:()=>b});var g={name:"page view",data:{title:"Home",id:"/"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},p={title:"Human visitor (Chrome)",description:"Modern Chrome UA. No bot or agent signals.",in:{...g},out:[["return",{event:{...g,user:{botScore:0,agentScore:0}}}]]},u={title:"GPTBot training crawler",description:"OpenAI training crawler. Both botScore and agentScore are high.",in:{...g,id:"ev-1700000601"},out:[["return",{event:{...g,id:"ev-1700000601",user:{botScore:95,agentScore:95}}}]]},d={title:"ChatGPT-User (user-action AI)",description:"A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.",in:{...g,id:"ev-1700000602"},out:[["return",{event:{...g,id:"ev-1700000602",user:{botScore:90,agentScore:95}}}]]},h={public:!1,description:"curl client — caught by isbot. agentScore zero.",in:{...g,id:"ev-1700000603"},out:[["return",{event:{...g,id:"ev-1700000603",user:{botScore:80,agentScore:0}}}]]},b={public:!1,description:"No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).",in:{...g,id:"ev-1700000604"},out:[["return",{event:{...g,id:"ev-1700000604",user:{botScore:70,agentScore:0}}}]]},m={"ingest-prerequisite":{text:'The bot transformer reads userAgent from ctx.ingest (path "ingest.userAgent" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{ingest:{userAgent:"req.headers.user-agent"}}}},transformers:{bot:{package:"@walkeros/server-transformer-bot"}}},null,2)}]},"output-routing":{text:"Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.",code:[{lang:"json",code:JSON.stringify({transformers:{bot:{package:"@walkeros/server-transformer-bot",config:{settings:{output:{botScore:"ingest.bot.score",agentScore:"ingest.bot.agent",agentProduct:"user.agentProduct"}}}}}},null,2)}]},"destination-filtering":{text:'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE "%-User". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.'},"detection-scope":{text:"v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN."}};export{c as examples,m as hints,r as schemas};//# sourceMappingURL=dev.mjs.map
1
+ var e=Object.defineProperty,t=(t,r)=>{for(var o in r)e(t,o,{get:r[o],enumerable:!0})},r={};t(r,{SettingsSchema:()=>s,settings:()=>c});import{zodToSchema as o}from"@walkeros/core/dev";import{z as n}from"@walkeros/core/dev";var i=n.union([n.string().describe('Dot-notation path like "ingest.userAgent"'),n.object({key:n.string().optional(),value:n.unknown().optional(),fn:n.string().optional()}).describe("Mapping value object")]),a=n.union([i,n.array(i).describe("Array of fallback values, tried in order")]),s=n.object({input:n.object({userAgent:a.optional(),ip:a.optional(),acceptLanguage:a.optional(),acceptEncoding:a.optional(),secFetchSite:a.optional(),secFetchMode:a.optional(),secFetchDest:a.optional(),secFetchUser:a.optional(),secChUa:a.optional(),secChUaMobile:a.optional(),secChUaPlatform:a.optional()}).optional().describe("Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics."),output:n.object({botScore:n.string().optional().describe('Path for bot score (0-99, higher = more bot). Default: "user.botScore". Use "ingest.*" to route to pipeline scratch instead of the event. Empty string or omit = skip.'),agentScore:n.string().optional().describe('Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: "user.agentScore".'),agentProduct:n.string().optional().describe('Path for matched UA substring (e.g. "ChatGPT-User"). Off by default — set to enable.')}).optional().describe("Output paths for bot/agent annotations.")}).describe("Bot detection transformer: annotates events with bot and AI-agent scores."),c=o(s),l={};t(l,{step:()=>g});var g={};t(g,{chatgptUserAgent:()=>h,curlClient:()=>b,gptBotCrawler:()=>d,humanChrome:()=>u,missingUA:()=>m});var p={name:"page view",data:{title:"Home",id:"/"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},u={title:"Human visitor (Chrome)",description:"Modern Chrome UA. No bot or agent signals.",in:{...p},out:[["return",{event:{...p,user:{botScore:0,agentScore:0}}}]]},d={title:"GPTBot training crawler",description:"OpenAI training crawler. Both botScore and agentScore are high.",in:{...p,id:"ev-1700000601"},out:[["return",{event:{...p,id:"ev-1700000601",user:{botScore:95,agentScore:95}}}]]},h={title:"ChatGPT-User (user-action AI)",description:"A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.",in:{...p,id:"ev-1700000602"},out:[["return",{event:{...p,id:"ev-1700000602",user:{botScore:90,agentScore:95}}}]]},b={public:!1,description:"curl client — caught by isbot. agentScore zero.",in:{...p,id:"ev-1700000603"},out:[["return",{event:{...p,id:"ev-1700000603",user:{botScore:80,agentScore:0}}}]]},m={public:!1,description:"No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).",in:{...p,id:"ev-1700000604"},out:[["return",{event:{...p,id:"ev-1700000604",user:{botScore:70,agentScore:0}}}]]},v={"ingest-prerequisite":{text:'The bot transformer reads userAgent from ctx.ingest (path "ingest.userAgent" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{ingest:{userAgent:"req.headers.user-agent"}}}},transformers:{bot:{package:"@walkeros/server-transformer-bot"}}},null,2)}]},"output-routing":{text:"Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.",code:[{lang:"json",code:JSON.stringify({transformers:{bot:{package:"@walkeros/server-transformer-bot",config:{settings:{output:{botScore:"ingest.bot.score",agentScore:"ingest.bot.agent",agentProduct:"user.agentProduct"}}}}}},null,2)}]},"destination-filtering":{text:'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE "%-User". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.'},"detection-scope":{text:"v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN."}};export{l as examples,v as hints,r as schemas};//# sourceMappingURL=dev.mjs.map
package/dist/dev.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst MappingValueSchema = z.union([\n z.string().describe('Dot-notation path like \"ingest.userAgent\"'),\n z\n .object({\n key: z.string().optional(),\n value: z.unknown().optional(),\n fn: z.string().optional(),\n })\n .describe('Mapping value object'),\n]);\n\nexport const SettingsSchema = z\n .object({\n input: z\n .object({\n userAgent: MappingValueSchema.optional(),\n ip: MappingValueSchema.optional(),\n acceptLanguage: MappingValueSchema.optional(),\n acceptEncoding: MappingValueSchema.optional(),\n secFetchSite: MappingValueSchema.optional(),\n secFetchMode: MappingValueSchema.optional(),\n secFetchDest: MappingValueSchema.optional(),\n secFetchUser: MappingValueSchema.optional(),\n secChUa: MappingValueSchema.optional(),\n secChUaMobile: MappingValueSchema.optional(),\n secChUaPlatform: MappingValueSchema.optional(),\n })\n .optional()\n .describe(\n 'Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics.',\n ),\n output: z\n .object({\n botScore: z\n .string()\n .optional()\n .describe(\n 'Path for bot score (0-99, higher = more bot). Default: \"user.botScore\". Use \"ingest.*\" to route to pipeline scratch instead of the event. Empty string or omit = skip.',\n ),\n agentScore: z\n .string()\n .optional()\n .describe(\n 'Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: \"user.agentScore\".',\n ),\n agentProduct: z\n .string()\n .optional()\n .describe(\n 'Path for matched UA substring (e.g. \"ChatGPT-User\"). Off by default — set to enable.',\n ),\n })\n .optional()\n .describe('Output paths for bot/agent annotations.'),\n })\n .describe(\n 'Bot detection transformer: annotates events with bot and AI-agent scores.',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nconst baseEvent = {\n name: 'page view',\n data: { title: 'Home', id: '/' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' as const },\n};\n\n/** Real Chrome — botScore 0, agentScore 0. */\nexport const humanChrome: Flow.StepExample = {\n title: 'Human visitor (Chrome)',\n description: 'Modern Chrome UA. No bot or agent signals.',\n in: { ...baseEvent },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n user: { botScore: 0, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** GPTBot — training crawler. */\nexport const gptBotCrawler: Flow.StepExample = {\n title: 'GPTBot training crawler',\n description:\n 'OpenAI training crawler. Both botScore and agentScore are high.',\n in: { ...baseEvent, id: 'ev-1700000601' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000601',\n user: { botScore: 95, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** ChatGPT-User — user-action AI agent. */\nexport const chatgptUserAgent: Flow.StepExample = {\n title: 'ChatGPT-User (user-action AI)',\n description:\n 'A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.',\n in: { ...baseEvent, id: 'ev-1700000602' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000602',\n user: { botScore: 90, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** curl — caught by isbot. */\nexport const curlClient: Flow.StepExample = {\n public: false,\n description: 'curl client — caught by isbot. agentScore zero.',\n in: { ...baseEvent, id: 'ev-1700000603' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000603',\n user: { botScore: 80, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** Empty / missing UA — score 70 (suspicious; real browsers rarely strip UA). */\nexport const missingUA: Flow.StepExample = {\n public: false,\n description:\n 'No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).',\n in: { ...baseEvent, id: 'ev-1700000604' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000604',\n user: { botScore: 70, agentScore: 0 },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The bot transformer reads userAgent from ctx.ingest (path \"ingest.userAgent\" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n ingest: {\n userAgent: 'req.headers.user-agent',\n },\n },\n },\n },\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'output-routing': {\n text: 'Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n config: {\n settings: {\n output: {\n botScore: 'ingest.bot.score',\n agentScore: 'ingest.bot.agent',\n agentProduct: 'user.agentProduct',\n },\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'destination-filtering': {\n text: 'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE \"%-User\". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.',\n },\n 'detection-scope': {\n text: 'v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN.',\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst SingleMappingValueSchema = z.union([\n z.string().describe('Dot-notation path like \"ingest.userAgent\"'),\n z\n .object({\n key: z.string().optional(),\n value: z.unknown().optional(),\n fn: z.string().optional(),\n })\n .describe('Mapping value object'),\n]);\n\nconst MappingValueSchema = z.union([\n SingleMappingValueSchema,\n z\n .array(SingleMappingValueSchema)\n .describe('Array of fallback values, tried in order'),\n]);\n\nexport const SettingsSchema = z\n .object({\n input: z\n .object({\n userAgent: MappingValueSchema.optional(),\n ip: MappingValueSchema.optional(),\n acceptLanguage: MappingValueSchema.optional(),\n acceptEncoding: MappingValueSchema.optional(),\n secFetchSite: MappingValueSchema.optional(),\n secFetchMode: MappingValueSchema.optional(),\n secFetchDest: MappingValueSchema.optional(),\n secFetchUser: MappingValueSchema.optional(),\n secChUa: MappingValueSchema.optional(),\n secChUaMobile: MappingValueSchema.optional(),\n secChUaPlatform: MappingValueSchema.optional(),\n })\n .optional()\n .describe(\n 'Input signal sources, resolved via getMappingValue against { event, ingest }. v1 only reads userAgent; other fields reserved for v1.1 header heuristics.',\n ),\n output: z\n .object({\n botScore: z\n .string()\n .optional()\n .describe(\n 'Path for bot score (0-99, higher = more bot). Default: \"user.botScore\". Use \"ingest.*\" to route to pipeline scratch instead of the event. Empty string or omit = skip.',\n ),\n agentScore: z\n .string()\n .optional()\n .describe(\n 'Path for AI agent score (0-99). v1 emits 0 (no match) or 95 (UA-map match). Default: \"user.agentScore\".',\n ),\n agentProduct: z\n .string()\n .optional()\n .describe(\n 'Path for matched UA substring (e.g. \"ChatGPT-User\"). Off by default — set to enable.',\n ),\n })\n .optional()\n .describe('Output paths for bot/agent annotations.'),\n })\n .describe(\n 'Bot detection transformer: annotates events with bot and AI-agent scores.',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nconst baseEvent = {\n name: 'page view',\n data: { title: 'Home', id: '/' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' as const },\n};\n\n/** Real Chrome — botScore 0, agentScore 0. */\nexport const humanChrome: Flow.StepExample = {\n title: 'Human visitor (Chrome)',\n description: 'Modern Chrome UA. No bot or agent signals.',\n in: { ...baseEvent },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n user: { botScore: 0, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** GPTBot — training crawler. */\nexport const gptBotCrawler: Flow.StepExample = {\n title: 'GPTBot training crawler',\n description:\n 'OpenAI training crawler. Both botScore and agentScore are high.',\n in: { ...baseEvent, id: 'ev-1700000601' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000601',\n user: { botScore: 95, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** ChatGPT-User — user-action AI agent. */\nexport const chatgptUserAgent: Flow.StepExample = {\n title: 'ChatGPT-User (user-action AI)',\n description:\n 'A real human routed an AI to fetch this page. botScore high but lower than crawlers — agentProduct lets destinations keep this traffic.',\n in: { ...baseEvent, id: 'ev-1700000602' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000602',\n user: { botScore: 90, agentScore: 95 },\n },\n },\n ],\n ],\n};\n\n/** curl — caught by isbot. */\nexport const curlClient: Flow.StepExample = {\n public: false,\n description: 'curl client — caught by isbot. agentScore zero.',\n in: { ...baseEvent, id: 'ev-1700000603' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000603',\n user: { botScore: 80, agentScore: 0 },\n },\n },\n ],\n ],\n};\n\n/** Empty / missing UA — score 70 (suspicious; real browsers rarely strip UA). */\nexport const missingUA: Flow.StepExample = {\n public: false,\n description:\n 'No User-Agent — baseline 70 (UA stripping is overwhelmingly bots or hardened privacy tools).',\n in: { ...baseEvent, id: 'ev-1700000604' },\n out: [\n [\n 'return',\n {\n event: {\n ...baseEvent,\n id: 'ev-1700000604',\n user: { botScore: 70, agentScore: 0 },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The bot transformer reads userAgent from ctx.ingest (path \"ingest.userAgent\" by default). The upstream server source must populate it via config.ingest mapping, otherwise the UA is empty and every event scores 70 (baseline for missing UA).',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n ingest: {\n userAgent: 'req.headers.user-agent',\n },\n },\n },\n },\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'output-routing': {\n text: 'Outputs default to event.user.botScore and event.user.agentScore. Redirect to ingest.* to keep the analytics event clean while still routing on the score downstream. Empty string (or omit) skips writing that field entirely. agentProduct is off by default — set it to enable writing the matched UA substring.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n transformers: {\n bot: {\n package: '@walkeros/server-transformer-bot',\n config: {\n settings: {\n output: {\n botScore: 'ingest.bot.score',\n agentScore: 'ingest.bot.agent',\n agentProduct: 'user.agentProduct',\n },\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'destination-filtering': {\n text: 'Recommended destination-mapping recipes. Drop all bots: botScore > 50. Drop crawlers but keep user-action AI traffic: botScore > 50 AND agentProduct NOT LIKE \"%-User\". AI traffic report: group by agentProduct WHERE agentScore > 50. The transformer never drops events — filtering is always a destination decision.',\n },\n 'detection-scope': {\n text: 'v1 is UA-only: wraps isbot (curl, wget, headless Chrome defaults, well-known crawlers) plus a curated AI-agent UA map (OpenAI, Anthropic, Perplexity, Mistral, Meta, Google, Apple, Amazon, DuckDuckGo, ByteDance, Common Crawl). It will NOT catch: residential-proxy + stealth Chrome + realistic behavior; reverse-DNS-verified search engines; client-side runtime tells. v1.1 adds header consistency heuristics (Sec-Fetch, Sec-CH-UA, Accept-Language) with proper GREASE handling. For commercial-grade detection use Cloudflare Bot Management, DataDome, or HUMAN.',\n },\n};\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,o=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,c=Object.prototype.hasOwnProperty,a={};((e,o)=>{for(var r in o)t(e,r,{get:o[r],enumerable:!0})})(a,{default:()=>d,transformerBot:()=>d}),module.exports=(e=a,((e,a,n,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let p of r(a))c.call(e,p)||p===n||t(e,p,{get:()=>a[p],enumerable:!(s=o(a,p))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n=require("@walkeros/core"),s=require("isbot"),p=[{match:"ChatGPT-User",product:"ChatGPT-User",purpose:"user-action"},{match:"ChatGPT-Agent",product:"ChatGPT-Agent",purpose:"user-action"},{match:"OAI-SearchBot",product:"OAI-SearchBot",purpose:"search-index"},{match:"GPTBot",product:"GPTBot",purpose:"training"},{match:"Claude-SearchBot",product:"Claude-SearchBot",purpose:"search-index"},{match:"Claude-User",product:"Claude-User",purpose:"user-action"},{match:"Claude-Code",product:"Claude-Code",purpose:"user-action"},{match:"ClaudeBot",product:"ClaudeBot",purpose:"training"},{match:"anthropic-ai",product:"anthropic-ai",purpose:"training"},{match:"Perplexity-User",product:"Perplexity-User",purpose:"user-action"},{match:"PerplexityBot",product:"PerplexityBot",purpose:"search-index"},{match:"MistralAI-User",product:"MistralAI-User",purpose:"user-action"},{match:"Meta-ExternalFetcher",product:"Meta-ExternalFetcher",purpose:"user-action"},{match:"Meta-ExternalAgent",product:"Meta-ExternalAgent",purpose:"training"},{match:"Google-CloudVertexBot",product:"Google-CloudVertexBot",purpose:"training"},{match:"Google-Extended",product:"Google-Extended",purpose:"training"},{match:"Applebot-Extended",product:"Applebot-Extended",purpose:"training"},{match:"Amazonbot",product:"Amazonbot",purpose:"training"},{match:"DuckAssistBot",product:"DuckAssistBot",purpose:"user-action"},{match:"Bytespider",product:"Bytespider",purpose:"training"},{match:"CCBot",product:"CCBot",purpose:"training"}];function u(e){if(!e)return{botScore:70,agentScore:0,agentProduct:void 0};const t=function(e){const t=e.toLowerCase(),o=p.find(e=>t.includes(e.match.toLowerCase()));return{isBot:!e||(0,s.isbot)(e)||void 0!==o,agent:o?{product:o.product,purpose:o.purpose}:void 0}}(e);let o=0;return t.agent?o="user-action"===t.agent.purpose?90:95:t.isBot&&(o=80),{botScore:o,agentScore:t.agent?95:0,agentProduct:t.agent?.product}}var i={userAgent:"ingest.userAgent",ip:"ingest.ip",acceptLanguage:"ingest.acceptLanguage",acceptEncoding:"ingest.acceptEncoding",secFetchSite:"ingest.secFetchSite",secFetchMode:"ingest.secFetchMode",secFetchDest:"ingest.secFetchDest",secFetchUser:"ingest.secFetchUser",secChUa:"ingest.secChUa",secChUaMobile:"ingest.secChUaMobile",secChUaPlatform:"ingest.secChUaPlatform"},g={botScore:"user.botScore",agentScore:"user.agentScore",agentProduct:""};var d=e=>{const{config:t}=e,o=t.settings??{},r={...i,...o.input??{}},c={...g,...o.output??{}};return{type:"bot",config:t,async push(e,t){const{ingest:o,collector:a}=t,s={event:e,ingest:o},p=await(0,n.getMappingValue)(s,r.userAgent,{collector:a}),i=u("string"==typeof p?p:"");let g=e;const d=(e,t)=>{e&&void 0!==t&&(e.startsWith("ingest.")?function(e,t,o){const r=t.split(".");let c=e;for(let e=0;e<r.length-1;e++){const t=r[e],o=c[t];"object"==typeof o&&null!==o||(c[t]={}),c=c[t]}c[r[r.length-1]]=o}(o,e.slice(7),t):g=(0,n.setByPath)(g,e,t))};return d(c.botScore??"",i.botScore),d(c.agentScore??"",i.agentScore),d(c.agentProduct??"",i.agentProduct),{event:g}}}};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.defineProperty,o=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,c=Object.prototype.hasOwnProperty,n={};((e,o)=>{for(var r in o)t(e,r,{get:o[r],enumerable:!0})})(n,{default:()=>d,transformerBot:()=>d}),module.exports=(e=n,((e,n,a,s)=>{if(n&&"object"==typeof n||"function"==typeof n)for(let p of r(n))c.call(e,p)||p===a||t(e,p,{get:()=>n[p],enumerable:!(s=o(n,p))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var a=require("@walkeros/core"),s=require("isbot"),p=[{match:"ChatGPT-User",product:"ChatGPT-User",purpose:"user-action"},{match:"ChatGPT-Agent",product:"ChatGPT-Agent",purpose:"user-action"},{match:"OAI-SearchBot",product:"OAI-SearchBot",purpose:"search-index"},{match:"GPTBot",product:"GPTBot",purpose:"training"},{match:"Claude-SearchBot",product:"Claude-SearchBot",purpose:"search-index"},{match:"Claude-User",product:"Claude-User",purpose:"user-action"},{match:"Claude-Code",product:"Claude-Code",purpose:"user-action"},{match:"ClaudeBot",product:"ClaudeBot",purpose:"training"},{match:"anthropic-ai",product:"anthropic-ai",purpose:"training"},{match:"Perplexity-User",product:"Perplexity-User",purpose:"user-action"},{match:"PerplexityBot",product:"PerplexityBot",purpose:"search-index"},{match:"MistralAI-User",product:"MistralAI-User",purpose:"user-action"},{match:"Meta-ExternalFetcher",product:"Meta-ExternalFetcher",purpose:"user-action"},{match:"Meta-ExternalAgent",product:"Meta-ExternalAgent",purpose:"training"},{match:"Google-CloudVertexBot",product:"Google-CloudVertexBot",purpose:"training"},{match:"Google-Extended",product:"Google-Extended",purpose:"training"},{match:"Applebot-Extended",product:"Applebot-Extended",purpose:"training"},{match:"Amazonbot",product:"Amazonbot",purpose:"training"},{match:"DuckAssistBot",product:"DuckAssistBot",purpose:"user-action"},{match:"Bytespider",product:"Bytespider",purpose:"training"},{match:"CCBot",product:"CCBot",purpose:"training"}];function u(e){if(!e)return{botScore:70,agentScore:0,agentProduct:void 0};const t=function(e){const t=e.toLowerCase(),o=p.find(e=>t.includes(e.match.toLowerCase()));return{isBot:!e||(0,s.isbot)(e)||void 0!==o,agent:o?{product:o.product,purpose:o.purpose}:void 0}}(e);let o=0;return t.agent?o="user-action"===t.agent.purpose?90:95:t.isBot&&(o=80),{botScore:o,agentScore:t.agent?95:0,agentProduct:t.agent?.product}}var i={userAgent:"ingest.userAgent",ip:"ingest.ip",acceptLanguage:"ingest.acceptLanguage",acceptEncoding:"ingest.acceptEncoding",secFetchSite:"ingest.secFetchSite",secFetchMode:"ingest.secFetchMode",secFetchDest:"ingest.secFetchDest",secFetchUser:"ingest.secFetchUser",secChUa:"ingest.secChUa",secChUaMobile:"ingest.secChUaMobile",secChUaPlatform:"ingest.secChUaPlatform"},g={botScore:"user.botScore",agentScore:"user.agentScore",agentProduct:""};var d=e=>{const{config:t}=e,o=t.settings??{},r={...i,...o.input??{}},c={...g,...o.output??{}};return{type:"bot",config:t,async push(e,t){const{ingest:o,collector:n}=t,s={event:e,ingest:o},p=await(0,a.getMappingValue)(s,r.userAgent,{collector:n}),i=u("string"==typeof p?p:"");let g=e;const d=(e,t)=>{if(e&&void 0!==t)if(e.startsWith("ingest.")){const r=e.slice(7);if(!r)return;!function(e,t,o){const r=t.split(".");let c=e;for(let e=0;e<r.length-1;e++){const t=r[e],o=c[t];"object"==typeof o&&null!==o||(c[t]={}),c=c[t]}c[r[r.length-1]]=o}(o,r,t)}else g=(0,a.setByPath)(g,e,t)};return d(c.botScore??"",i.botScore),d(c.agentScore??"",i.agentScore),d(c.agentProduct??"",i.agentProduct),{event:g}}}};//# sourceMappingURL=index.js.map