@walkeros/server-transformer-bot 4.1.0 → 4.1.1
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 +15 -0
- package/README.md +44 -26
- package/dist/dev.d.mts +66 -22
- package/dist/dev.d.ts +66 -22
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +474 -144
- package/package.json +2 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @walkeros/server-transformer-bot
|
|
2
|
+
|
|
3
|
+
## 4.1.1
|
|
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
|
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),
|
|
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
|
|
57
|
-
|
|
58
|
-
| Real browser (Chrome, Firefox, Safari, Edge)
|
|
59
|
-
| Empty / missing User-Agent
|
|
60
|
-
| curl / wget / python-requests / well-known crawlers
|
|
61
|
-
| AI training crawlers (GPTBot, ClaudeBot, CCBot, Bytespider, etc.)
|
|
62
|
-
| AI search-index crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) | 95
|
|
63
|
-
| AI user-action agents (ChatGPT-User, Claude-User, Perplexity-User, etc.)
|
|
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
|
|
70
|
-
|
|
71
|
-
| `botScore`
|
|
72
|
-
| `agentScore`
|
|
73
|
-
| `agentProduct` | (off)
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- **
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
105
|
-
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:()=>
|
|
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAElB,IAAM,2BAA2B,aAAE,MAAM;AAAA,EACvC,aAAE,OAAO,EAAE,SAAS,2CAA2C;AAAA,EAC/D,aACG,OAAO;AAAA,IACN,KAAK,aAAE,OAAO,EAAE,SAAS;AAAA,IACzB,OAAO,aAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,IAAI,aAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,CAAC,EACA,SAAS,sBAAsB;AACpC,CAAC;AAED,IAAM,qBAAqB,aAAE,MAAM;AAAA,EACjC;AAAA,EACA,aACG,MAAM,wBAAwB,EAC9B,SAAS,0CAA0C;AACxD,CAAC;AAEM,IAAM,iBAAiB,aAC3B,OAAO;AAAA,EACN,OAAO,aACJ,OAAO;AAAA,IACN,WAAW,mBAAmB,SAAS;AAAA,IACvC,IAAI,mBAAmB,SAAS;AAAA,IAChC,gBAAgB,mBAAmB,SAAS;AAAA,IAC5C,gBAAgB,mBAAmB,SAAS;AAAA,IAC5C,cAAc,mBAAmB,SAAS;AAAA,IAC1C,cAAc,mBAAmB,SAAS;AAAA,IAC1C,cAAc,mBAAmB,SAAS;AAAA,IAC1C,cAAc,mBAAmB,SAAS;AAAA,IAC1C,SAAS,mBAAmB,SAAS;AAAA,IACrC,eAAe,mBAAmB,SAAS;AAAA,IAC3C,iBAAiB,mBAAmB,SAAS;AAAA,EAC/C,CAAC,EACA,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,QAAQ,aACL,OAAO;AAAA,IACN,UAAU,aACP,OAAO,EACP,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,YAAY,aACT,OAAO,EACP,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,cAAc,aACX,OAAO,EACP,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,EACJ,CAAC,EACA,SAAS,EACT,SAAS,yCAAyC;AACvD,CAAC,EACA;AAAA,EACC;AACF;;;AD9DK,IAAM,eAAW,yBAAY,cAAc;;;AEJlD;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAM,YAAY;AAAA,EAChB,MAAM;AAAA,EACN,MAAM,EAAE,OAAO,QAAQ,IAAI,IAAI;AAAA,EAC/B,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAkB;AACzD;AAGO,IAAM,cAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,GAAG,UAAU;AAAA,EACnB,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,GAAG;AAAA,UACH,MAAM,EAAE,UAAU,GAAG,YAAY,EAAE;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,gBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI,EAAE,GAAG,WAAW,IAAI,gBAAgB;AAAA,EACxC,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,GAAG;AAAA,UACH,IAAI;AAAA,UACJ,MAAM,EAAE,UAAU,IAAI,YAAY,GAAG;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,mBAAqC;AAAA,EAChD,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI,EAAE,GAAG,WAAW,IAAI,gBAAgB;AAAA,EACxC,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,GAAG;AAAA,UACH,IAAI;AAAA,UACJ,MAAM,EAAE,UAAU,IAAI,YAAY,GAAG;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,aAA+B;AAAA,EAC1C,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,IAAI,EAAE,GAAG,WAAW,IAAI,gBAAgB;AAAA,EACxC,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,GAAG;AAAA,UACH,IAAI;AAAA,UACJ,MAAM,EAAE,UAAU,IAAI,YAAY,EAAE;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,YAA8B;AAAA,EACzC,QAAQ;AAAA,EACR,aACE;AAAA,EACF,IAAI,EAAE,GAAG,WAAW,IAAI,gBAAgB;AAAA,EACxC,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,GAAG;AAAA,UACH,IAAI;AAAA,UACJ,MAAM,EAAE,UAAU,IAAI,YAAY,EAAE;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC1GO,IAAM,QAAoB;AAAA,EAC/B,uBAAuB;AAAA,IACrB,MAAM;AAAA,IACN,MAAM;AAAA,MACJ;AAAA,QACE,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,UACT;AAAA,YACE,SAAS;AAAA,cACP,SAAS;AAAA,gBACP,SAAS;AAAA,gBACT,QAAQ;AAAA,kBACN,QAAQ;AAAA,oBACN,WAAW;AAAA,kBACb;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,YACA,cAAc;AAAA,cACZ,KAAK;AAAA,gBACH,SAAS;AAAA,cACX;AAAA,YACF;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB,MAAM;AAAA,IACN,MAAM;AAAA,MACJ;AAAA,QACE,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,UACT;AAAA,YACE,cAAc;AAAA,cACZ,KAAK;AAAA,gBACH,SAAS;AAAA,gBACT,QAAQ;AAAA,kBACN,UAAU;AAAA,oBACR,QAAQ;AAAA,sBACN,UAAU;AAAA,sBACV,YAAY;AAAA,sBACZ,cAAc;AAAA,oBAChB;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,yBAAyB;AAAA,IACvB,MAAM;AAAA,EACR;AAAA,EACA,mBAAmB;AAAA,IACjB,MAAM;AAAA,EACR;AACF;","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:()=>
|
|
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,
|
|
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
|