vektor-slipstream 1.4.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -306
- package/package.json +14 -146
- package/CHANGELOG.md +0 -139
- package/LICENSE +0 -33
- package/TENETS.md +0 -189
- package/audn-log.js +0 -143
- package/axon.js +0 -389
- package/boot-patch.js +0 -33
- package/boot-screen.html +0 -210
- package/briefing.js +0 -150
- package/cerebellum.js +0 -439
- package/cloak-behaviour.js +0 -596
- package/cloak-captcha.js +0 -541
- package/cloak-core.js +0 -499
- package/cloak-identity.js +0 -484
- package/cloak-index.js +0 -261
- package/cloak-llms.js +0 -163
- package/cloak-pattern-store.js +0 -471
- package/cloak-recorder-auto.js +0 -297
- package/cloak-recorder-snippet.js +0 -119
- package/cloak-turbo-quant.js +0 -357
- package/cloak-warmup.js +0 -240
- package/cortex.js +0 -221
- package/detect-hardware.js +0 -181
- package/entity-resolver.js +0 -298
- package/errors.js +0 -66
- package/examples/example-claude-mcp.js +0 -220
- package/examples/example-langchain-researcher.js +0 -82
- package/examples/example-openai-assistant.js +0 -84
- package/examples/examples-README.md +0 -161
- package/export-import.js +0 -221
- package/forget.js +0 -148
- package/inspect.js +0 -199
- package/mistral/README-mistral.md +0 -123
- package/mistral/mistral-bridge.js +0 -218
- package/mistral/mistral-setup.js +0 -220
- package/mistral/vektor-tool-manifest.json +0 -41
- package/models/model_quantized.onnx +0 -0
- package/models/vocab.json +0 -1
- package/namespace.js +0 -186
- package/pin.js +0 -91
- package/slipstream-core-extended.js +0 -134
- package/slipstream-core.js +0 -1
- package/slipstream-db.js +0 -140
- package/slipstream-embedder.js +0 -338
- package/sovereign.js +0 -142
- package/token.js +0 -322
- package/types/index.d.ts +0 -269
- package/vektor-banner-loader.js +0 -109
- package/vektor-cli.js +0 -259
- package/vektor-licence-prompt.js +0 -128
- package/vektor-licence.js +0 -192
- package/vektor-setup.js +0 -270
- package/vektor-slipstream.dxt +0 -0
- package/vektor-tui.js +0 -373
- package/visualize.js +0 -235
package/cloak-behaviour.js
DELETED
|
@@ -1,596 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* cloak-behaviour.js — Human behaviour noise injection for Playwright
|
|
5
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
-
* Records and replays real human mouse/scroll/keyboard patterns inside
|
|
7
|
-
* Playwright sessions. Defeats reCAPTCHA v3, Cloudflare, DataDome and
|
|
8
|
-
* similar behavioural fingerprinting systems.
|
|
9
|
-
*
|
|
10
|
-
* How it works:
|
|
11
|
-
* 1. Pre-recorded sessions: timestamped event streams captured from real
|
|
12
|
-
* human browsing, normalised to viewport-relative coordinates (0-1 range)
|
|
13
|
-
* so they scale to any window size on replay.
|
|
14
|
-
*
|
|
15
|
-
* 2. Replay engine: walks the event stream, reconstructing real timing gaps
|
|
16
|
-
* via page.waitForTimeout(). Events go through Playwright's CDP layer —
|
|
17
|
-
* identical to hardware input at the browser fingerprinting level.
|
|
18
|
-
*
|
|
19
|
-
* 3. Procedural synthesis: for cases without a matching recorded pattern,
|
|
20
|
-
* generates statistically realistic movement using Gaussian velocity
|
|
21
|
-
* curves, Bezier paths, and natural micro-pause distributions.
|
|
22
|
-
*
|
|
23
|
-
* Usage:
|
|
24
|
-
* const { injectBehaviour, replayPattern, randomPattern } = require('./cloak-behaviour');
|
|
25
|
-
*
|
|
26
|
-
* // High-level: auto-pick a pattern for the context
|
|
27
|
-
* await injectBehaviour(page, 'reading'); // reading a page
|
|
28
|
-
* await injectBehaviour(page, 'form'); // filling a form
|
|
29
|
-
* await injectBehaviour(page, 'shopping'); // browsing a product list
|
|
30
|
-
* await injectBehaviour(page, 'login'); // login page interaction
|
|
31
|
-
*
|
|
32
|
-
* // Low-level: replay a specific named pattern
|
|
33
|
-
* await replayPattern(page, 'deep-scroll-read');
|
|
34
|
-
*
|
|
35
|
-
* // Record your own: paste cloak-recorder-snippet.js into browser DevTools
|
|
36
|
-
* // then load the JSON with: loadCustomPattern('my-pattern', json)
|
|
37
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
const path = require('path');
|
|
41
|
-
const fs = require('fs');
|
|
42
|
-
const os = require('os');
|
|
43
|
-
|
|
44
|
-
// ── Pre-recorded pattern library ──────────────────────────────────────────────
|
|
45
|
-
// Coordinates are NORMALISED: x/y in 0.0–1.0 range relative to viewport.
|
|
46
|
-
// Timing gaps (dt) are in milliseconds.
|
|
47
|
-
// Captured from real human browsing sessions, then sanitised.
|
|
48
|
-
|
|
49
|
-
const PATTERN_LIBRARY = {
|
|
50
|
-
|
|
51
|
-
// ── Pattern: reading — user lands, reads top of page, scrolls slowly ────────
|
|
52
|
-
'reading': {
|
|
53
|
-
category: 'reading',
|
|
54
|
-
viewport: { w: 1440, h: 900 },
|
|
55
|
-
durationMs: 8200,
|
|
56
|
-
description: 'Land on page, read hero content, slow scroll through body',
|
|
57
|
-
events: [
|
|
58
|
-
// Initial landing — cursor in upper-middle, small drift while reading
|
|
59
|
-
{ dt: 0, type: 'move', x: 0.52, y: 0.18 },
|
|
60
|
-
{ dt: 312, type: 'move', x: 0.53, y: 0.19 },
|
|
61
|
-
{ dt: 489, type: 'move', x: 0.54, y: 0.19 },
|
|
62
|
-
{ dt: 823, type: 'move', x: 0.53, y: 0.20 },
|
|
63
|
-
// Pause — reading headline
|
|
64
|
-
{ dt: 1640, type: 'pause' },
|
|
65
|
-
{ dt: 2180, type: 'move', x: 0.41, y: 0.28 },
|
|
66
|
-
{ dt: 2290, type: 'move', x: 0.43, y: 0.30 },
|
|
67
|
-
// Slow scroll — reading body
|
|
68
|
-
{ dt: 2800, type: 'scroll', dy: 80 },
|
|
69
|
-
{ dt: 2980, type: 'scroll', dy: 100 },
|
|
70
|
-
{ dt: 3190, type: 'scroll', dy: 90 },
|
|
71
|
-
{ dt: 3510, type: 'scroll', dy: 60 },
|
|
72
|
-
// Pause mid-page — reading
|
|
73
|
-
{ dt: 4100, type: 'pause' },
|
|
74
|
-
{ dt: 4380, type: 'move', x: 0.38, y: 0.44 },
|
|
75
|
-
{ dt: 4490, type: 'move', x: 0.40, y: 0.46 },
|
|
76
|
-
{ dt: 4920, type: 'scroll', dy: 120 },
|
|
77
|
-
{ dt: 5100, type: 'scroll', dy: 110 },
|
|
78
|
-
{ dt: 5340, type: 'scroll', dy: 130 },
|
|
79
|
-
{ dt: 5600, type: 'scroll', dy: 80 },
|
|
80
|
-
// Brief hover over a link (not clicking)
|
|
81
|
-
{ dt: 6100, type: 'move', x: 0.45, y: 0.58 },
|
|
82
|
-
{ dt: 6210, type: 'move', x: 0.46, y: 0.59 },
|
|
83
|
-
{ dt: 6320, type: 'move', x: 0.47, y: 0.59 },
|
|
84
|
-
{ dt: 6800, type: 'pause' },
|
|
85
|
-
{ dt: 7200, type: 'scroll', dy: 90 },
|
|
86
|
-
{ dt: 7380, type: 'scroll', dy: 70 },
|
|
87
|
-
{ dt: 7900, type: 'move', x: 0.50, y: 0.65 },
|
|
88
|
-
{ dt: 8100, type: 'move', x: 0.51, y: 0.66 },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// ── Pattern: form — navigate to form, fill fields with natural timing ────────
|
|
93
|
-
'form': {
|
|
94
|
-
category: 'form',
|
|
95
|
-
viewport: { w: 1440, h: 900 },
|
|
96
|
-
durationMs: 9800,
|
|
97
|
-
description: 'Approach form, click fields with realistic inter-key timing',
|
|
98
|
-
events: [
|
|
99
|
-
{ dt: 0, type: 'move', x: 0.50, y: 0.30 },
|
|
100
|
-
{ dt: 280, type: 'move', x: 0.48, y: 0.35 },
|
|
101
|
-
{ dt: 490, type: 'move', x: 0.47, y: 0.38 },
|
|
102
|
-
// Approach first input field
|
|
103
|
-
{ dt: 820, type: 'move', x: 0.45, y: 0.42 },
|
|
104
|
-
{ dt: 940, type: 'move', x: 0.44, y: 0.43 },
|
|
105
|
-
{ dt: 1100, type: 'click', x: 0.44, y: 0.43 },
|
|
106
|
-
// Typing pause (realistic inter-keystroke)
|
|
107
|
-
{ dt: 1420, type: 'pause' },
|
|
108
|
-
{ dt: 1680, type: 'pause' },
|
|
109
|
-
{ dt: 2100, type: 'pause' },
|
|
110
|
-
// Tab to next field — slight mouse movement
|
|
111
|
-
{ dt: 2800, type: 'move', x: 0.44, y: 0.52 },
|
|
112
|
-
{ dt: 2960, type: 'move', x: 0.44, y: 0.53 },
|
|
113
|
-
{ dt: 3100, type: 'click', x: 0.44, y: 0.53 },
|
|
114
|
-
{ dt: 3380, type: 'pause' },
|
|
115
|
-
{ dt: 3720, type: 'pause' },
|
|
116
|
-
{ dt: 4200, type: 'pause' },
|
|
117
|
-
// Brief scroll to see submit button
|
|
118
|
-
{ dt: 4900, type: 'scroll', dy: 80 },
|
|
119
|
-
{ dt: 5100, type: 'move', x: 0.44, y: 0.63 },
|
|
120
|
-
{ dt: 5280, type: 'move', x: 0.44, y: 0.64 },
|
|
121
|
-
{ dt: 5400, type: 'click', x: 0.44, y: 0.64 },
|
|
122
|
-
{ dt: 5680, type: 'pause' },
|
|
123
|
-
{ dt: 6100, type: 'pause' },
|
|
124
|
-
// Hover over submit — realistic pre-click hesitation
|
|
125
|
-
{ dt: 7200, type: 'move', x: 0.38, y: 0.74 },
|
|
126
|
-
{ dt: 7380, type: 'move', x: 0.39, y: 0.74 },
|
|
127
|
-
{ dt: 7540, type: 'move', x: 0.40, y: 0.75 },
|
|
128
|
-
{ dt: 7820, type: 'pause' },
|
|
129
|
-
// Submit click
|
|
130
|
-
{ dt: 8400, type: 'move', x: 0.40, y: 0.75 },
|
|
131
|
-
{ dt: 8600, type: 'click', x: 0.40, y: 0.75 },
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
// ── Pattern: shopping — browse product grid, hover items, scroll ─────────────
|
|
136
|
-
'shopping': {
|
|
137
|
-
category: 'shopping',
|
|
138
|
-
viewport: { w: 1440, h: 900 },
|
|
139
|
-
durationMs: 11400,
|
|
140
|
-
description: 'Browse product listing, hover items, deep scroll, occasional backtrack',
|
|
141
|
-
events: [
|
|
142
|
-
{ dt: 0, type: 'move', x: 0.22, y: 0.28 },
|
|
143
|
-
{ dt: 290, type: 'move', x: 0.24, y: 0.30 },
|
|
144
|
-
// Hover first product
|
|
145
|
-
{ dt: 580, type: 'move', x: 0.25, y: 0.32 },
|
|
146
|
-
{ dt: 790, type: 'pause' },
|
|
147
|
-
// Move to second product
|
|
148
|
-
{ dt: 1400, type: 'move', x: 0.52, y: 0.32 },
|
|
149
|
-
{ dt: 1590, type: 'move', x: 0.53, y: 0.33 },
|
|
150
|
-
{ dt: 1780, type: 'pause' },
|
|
151
|
-
// Scroll down
|
|
152
|
-
{ dt: 2300, type: 'scroll', dy: 150 },
|
|
153
|
-
{ dt: 2490, type: 'scroll', dy: 180 },
|
|
154
|
-
{ dt: 2680, type: 'scroll', dy: 160 },
|
|
155
|
-
{ dt: 2990, type: 'scroll', dy: 100 },
|
|
156
|
-
// Hover product in next row
|
|
157
|
-
{ dt: 3500, type: 'move', x: 0.25, y: 0.48 },
|
|
158
|
-
{ dt: 3680, type: 'move', x: 0.26, y: 0.49 },
|
|
159
|
-
{ dt: 3900, type: 'pause' },
|
|
160
|
-
// Quick back-scroll — reconsidering
|
|
161
|
-
{ dt: 4600, type: 'scroll', dy: -120 },
|
|
162
|
-
{ dt: 4780, type: 'scroll', dy: -80 },
|
|
163
|
-
{ dt: 5100, type: 'move', x: 0.52, y: 0.38 },
|
|
164
|
-
{ dt: 5280, type: 'pause' },
|
|
165
|
-
// Continue scrolling down
|
|
166
|
-
{ dt: 5900, type: 'scroll', dy: 200 },
|
|
167
|
-
{ dt: 6100, type: 'scroll', dy: 220 },
|
|
168
|
-
{ dt: 6300, type: 'scroll', dy: 180 },
|
|
169
|
-
{ dt: 6700, type: 'move', x: 0.35, y: 0.55 },
|
|
170
|
-
{ dt: 6900, type: 'pause' },
|
|
171
|
-
{ dt: 7600, type: 'scroll', dy: 140 },
|
|
172
|
-
{ dt: 7800, type: 'scroll', dy: 120 },
|
|
173
|
-
// Slow down — reading product details
|
|
174
|
-
{ dt: 8400, type: 'move', x: 0.35, y: 0.62 },
|
|
175
|
-
{ dt: 8600, type: 'move', x: 0.36, y: 0.63 },
|
|
176
|
-
{ dt: 9100, type: 'pause' },
|
|
177
|
-
{ dt: 9800, type: 'scroll', dy: 80 },
|
|
178
|
-
{ dt: 10100, type: 'move', x: 0.38, y: 0.68 },
|
|
179
|
-
{ dt: 10400, type: 'pause' },
|
|
180
|
-
{ dt: 11000, type: 'scroll', dy: 60 },
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
// ── Pattern: login — approach login form with careful deliberate movement ─────
|
|
185
|
-
'login': {
|
|
186
|
-
category: 'login',
|
|
187
|
-
viewport: { w: 1440, h: 900 },
|
|
188
|
-
durationMs: 7600,
|
|
189
|
-
description: 'Navigate to login form, click email, type, tab to password, submit',
|
|
190
|
-
events: [
|
|
191
|
-
// Land — brief orientation pause
|
|
192
|
-
{ dt: 0, type: 'move', x: 0.50, y: 0.25 },
|
|
193
|
-
{ dt: 420, type: 'pause' },
|
|
194
|
-
// Move to email field — deliberate
|
|
195
|
-
{ dt: 1100, type: 'move', x: 0.50, y: 0.40 },
|
|
196
|
-
{ dt: 1290, type: 'move', x: 0.50, y: 0.42 },
|
|
197
|
-
{ dt: 1480, type: 'move', x: 0.50, y: 0.43 },
|
|
198
|
-
{ dt: 1680, type: 'click', x: 0.50, y: 0.43 },
|
|
199
|
-
// Typing email — realistic gaps
|
|
200
|
-
{ dt: 1980, type: 'pause' },
|
|
201
|
-
{ dt: 2280, type: 'pause' },
|
|
202
|
-
{ dt: 2680, type: 'pause' },
|
|
203
|
-
{ dt: 3100, type: 'pause' },
|
|
204
|
-
// Tab/click to password field
|
|
205
|
-
{ dt: 3600, type: 'move', x: 0.50, y: 0.52 },
|
|
206
|
-
{ dt: 3780, type: 'move', x: 0.50, y: 0.53 },
|
|
207
|
-
{ dt: 3920, type: 'click', x: 0.50, y: 0.53 },
|
|
208
|
-
// Typing password — slightly slower (more careful)
|
|
209
|
-
{ dt: 4280, type: 'pause' },
|
|
210
|
-
{ dt: 4620, type: 'pause' },
|
|
211
|
-
{ dt: 4980, type: 'pause' },
|
|
212
|
-
// Hover login button — slight hesitation
|
|
213
|
-
{ dt: 5600, type: 'move', x: 0.50, y: 0.64 },
|
|
214
|
-
{ dt: 5780, type: 'move', x: 0.50, y: 0.65 },
|
|
215
|
-
{ dt: 6100, type: 'pause' },
|
|
216
|
-
// Click
|
|
217
|
-
{ dt: 6700, type: 'move', x: 0.50, y: 0.65 },
|
|
218
|
-
{ dt: 6900, type: 'click', x: 0.50, y: 0.65 },
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
// ── Pattern: deep-scroll-read — long article reading ────────────────────────
|
|
223
|
-
'deep-scroll-read': {
|
|
224
|
-
category: 'reading',
|
|
225
|
-
viewport: { w: 1440, h: 900 },
|
|
226
|
-
durationMs: 14200,
|
|
227
|
-
description: 'Long article read — variable scroll speed, selection hover, backtrack',
|
|
228
|
-
events: [
|
|
229
|
-
{ dt: 0, type: 'move', x: 0.50, y: 0.15 },
|
|
230
|
-
{ dt: 380, type: 'pause' },
|
|
231
|
-
{ dt: 900, type: 'scroll', dy: 60 },
|
|
232
|
-
{ dt: 1080, type: 'scroll', dy: 80 },
|
|
233
|
-
{ dt: 1380, type: 'scroll', dy: 100 },
|
|
234
|
-
{ dt: 1780, type: 'pause' },
|
|
235
|
-
{ dt: 2100, type: 'move', x: 0.38, y: 0.35 },
|
|
236
|
-
{ dt: 2400, type: 'scroll', dy: 90 },
|
|
237
|
-
{ dt: 2590, type: 'scroll', dy: 110 },
|
|
238
|
-
{ dt: 2890, type: 'scroll', dy: 130 },
|
|
239
|
-
{ dt: 3200, type: 'scroll', dy: 100 },
|
|
240
|
-
{ dt: 3600, type: 'pause' },
|
|
241
|
-
// Speed up — scanning
|
|
242
|
-
{ dt: 4000, type: 'scroll', dy: 200 },
|
|
243
|
-
{ dt: 4180, type: 'scroll', dy: 240 },
|
|
244
|
-
{ dt: 4340, type: 'scroll', dy: 220 },
|
|
245
|
-
{ dt: 4500, type: 'scroll', dy: 180 },
|
|
246
|
-
// Slow down — interesting section
|
|
247
|
-
{ dt: 5200, type: 'scroll', dy: 60 },
|
|
248
|
-
{ dt: 5500, type: 'move', x: 0.35, y: 0.50 },
|
|
249
|
-
{ dt: 5780, type: 'pause' },
|
|
250
|
-
{ dt: 6300, type: 'scroll', dy: 50 },
|
|
251
|
-
{ dt: 6600, type: 'scroll', dy: 70 },
|
|
252
|
-
{ dt: 7100, type: 'pause' },
|
|
253
|
-
// Backtrack — re-reading
|
|
254
|
-
{ dt: 7700, type: 'scroll', dy: -150 },
|
|
255
|
-
{ dt: 7880, type: 'scroll', dy: -120 },
|
|
256
|
-
{ dt: 8100, type: 'pause' },
|
|
257
|
-
{ dt: 8600, type: 'move', x: 0.40, y: 0.44 },
|
|
258
|
-
{ dt: 9000, type: 'scroll', dy: 80 },
|
|
259
|
-
{ dt: 9200, type: 'scroll', dy: 100 },
|
|
260
|
-
{ dt: 9500, type: 'scroll', dy: 120 },
|
|
261
|
-
{ dt: 9800, type: 'scroll', dy: 140 },
|
|
262
|
-
{ dt: 10200, type: 'pause' },
|
|
263
|
-
{ dt: 10700, type: 'scroll', dy: 160 },
|
|
264
|
-
{ dt: 10900, type: 'scroll', dy: 180 },
|
|
265
|
-
{ dt: 11200, type: 'scroll', dy: 160 },
|
|
266
|
-
{ dt: 11600, type: 'move', x: 0.42, y: 0.60 },
|
|
267
|
-
{ dt: 12000, type: 'pause' },
|
|
268
|
-
{ dt: 12500, type: 'scroll', dy: 100 },
|
|
269
|
-
{ dt: 12700, type: 'scroll', dy: 80 },
|
|
270
|
-
{ dt: 13100, type: 'move', x: 0.44, y: 0.68 },
|
|
271
|
-
{ dt: 13500, type: 'pause' },
|
|
272
|
-
{ dt: 13900, type: 'scroll', dy: 60 },
|
|
273
|
-
],
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
// ── Pattern: idle-hover — waiting on page, occasional movement ───────────────
|
|
277
|
-
'idle-hover': {
|
|
278
|
-
category: 'idle',
|
|
279
|
-
viewport: { w: 1440, h: 900 },
|
|
280
|
-
durationMs: 6000,
|
|
281
|
-
description: 'User reading without scrolling — micro-movements only',
|
|
282
|
-
events: [
|
|
283
|
-
{ dt: 0, type: 'move', x: 0.50, y: 0.40 },
|
|
284
|
-
{ dt: 680, type: 'move', x: 0.51, y: 0.40 },
|
|
285
|
-
{ dt: 1240, type: 'move', x: 0.51, y: 0.41 },
|
|
286
|
-
{ dt: 2100, type: 'pause' },
|
|
287
|
-
{ dt: 2800, type: 'move', x: 0.52, y: 0.41 },
|
|
288
|
-
{ dt: 3200, type: 'move', x: 0.52, y: 0.42 },
|
|
289
|
-
{ dt: 3900, type: 'pause' },
|
|
290
|
-
{ dt: 4600, type: 'move', x: 0.51, y: 0.42 },
|
|
291
|
-
{ dt: 5100, type: 'move', x: 0.50, y: 0.43 },
|
|
292
|
-
{ dt: 5600, type: 'pause' },
|
|
293
|
-
],
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// User-loaded custom patterns (from cloak-recorder)
|
|
298
|
-
const _customPatterns = new Map();
|
|
299
|
-
|
|
300
|
-
// ── Category → pattern name map ───────────────────────────────────────────────
|
|
301
|
-
|
|
302
|
-
const CATEGORY_PATTERNS = {
|
|
303
|
-
reading: ['reading', 'deep-scroll-read'],
|
|
304
|
-
form: ['form'],
|
|
305
|
-
shopping: ['shopping'],
|
|
306
|
-
login: ['login'],
|
|
307
|
-
idle: ['idle-hover'],
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// ── Replay engine ─────────────────────────────────────────────────────────────
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Replay a named pattern on a Playwright page.
|
|
314
|
-
* Scales normalised coordinates to actual viewport dimensions.
|
|
315
|
-
* Adds ±15% jitter to all timing gaps for natural variation.
|
|
316
|
-
*
|
|
317
|
-
* @param {import('playwright').Page} page
|
|
318
|
-
* @param {string} patternName
|
|
319
|
-
* @param {object} opts
|
|
320
|
-
* @param {number} opts.speedFactor - 1.0 = real speed, 0.5 = 2x faster, 2.0 = 2x slower
|
|
321
|
-
* @param {boolean} opts.jitter - add timing jitter (default true)
|
|
322
|
-
* @param {boolean} opts.skipClicks - replay moves/scrolls only, skip clicks (default false)
|
|
323
|
-
*/
|
|
324
|
-
async function replayPattern(page, patternName, opts = {}) {
|
|
325
|
-
const pattern = _customPatterns.get(patternName) || PATTERN_LIBRARY[patternName];
|
|
326
|
-
if (!pattern) throw new Error(`cloak-behaviour: unknown pattern "${patternName}"`);
|
|
327
|
-
|
|
328
|
-
const vp = page.viewportSize() || { width: 1440, height: 900 };
|
|
329
|
-
const speedFactor = opts.speedFactor ?? 1.0;
|
|
330
|
-
const jitter = opts.jitter ?? true;
|
|
331
|
-
const skipClicks = opts.skipClicks ?? false;
|
|
332
|
-
|
|
333
|
-
let lastT = 0;
|
|
334
|
-
|
|
335
|
-
for (const event of pattern.events) {
|
|
336
|
-
// Calculate gap from last event
|
|
337
|
-
let gap = (event.dt - lastT) * speedFactor;
|
|
338
|
-
lastT = event.dt;
|
|
339
|
-
|
|
340
|
-
// Add ±15% timing jitter (makes replay non-deterministic)
|
|
341
|
-
if (jitter && gap > 0) {
|
|
342
|
-
gap = Math.max(16, gap * (0.85 + Math.random() * 0.30));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (gap > 0) {
|
|
346
|
-
await page.waitForTimeout(Math.round(gap));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
switch (event.type) {
|
|
350
|
-
case 'move': {
|
|
351
|
-
const x = Math.round(event.x * vp.width);
|
|
352
|
-
const y = Math.round(event.y * vp.height);
|
|
353
|
-
// steps = distance-proportional for smooth arcs
|
|
354
|
-
const steps = Math.max(3, Math.round(8 + Math.random() * 8));
|
|
355
|
-
await page.mouse.move(x, y, { steps }).catch(() => {});
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
case 'scroll': {
|
|
360
|
-
const x = Math.round((event.x ?? 0.5) * vp.width);
|
|
361
|
-
const y = Math.round((event.y ?? 0.5) * vp.height);
|
|
362
|
-
// Add scroll delta jitter
|
|
363
|
-
const dy = jitter
|
|
364
|
-
? Math.round(event.dy * (0.85 + Math.random() * 0.30))
|
|
365
|
-
: event.dy;
|
|
366
|
-
await page.mouse.wheel(0, dy).catch(() => {});
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
case 'click': {
|
|
371
|
-
if (skipClicks) break;
|
|
372
|
-
const x = Math.round(event.x * vp.width);
|
|
373
|
-
const y = Math.round(event.y * vp.height);
|
|
374
|
-
await page.mouse.move(x, y, { steps: 4 }).catch(() => {});
|
|
375
|
-
await page.waitForTimeout(40 + Math.round(Math.random() * 60));
|
|
376
|
-
await page.mouse.down().catch(() => {});
|
|
377
|
-
await page.waitForTimeout(60 + Math.round(Math.random() * 80));
|
|
378
|
-
await page.mouse.up().catch(() => {});
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
case 'pause':
|
|
383
|
-
// Pure wait — already handled by gap above
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return {
|
|
389
|
-
pattern: patternName,
|
|
390
|
-
durationMs: Math.round(pattern.durationMs * speedFactor),
|
|
391
|
-
events: pattern.events.length,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// ── High-level API ────────────────────────────────────────────────────────────
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Pick and replay a behaviour pattern appropriate for the given context.
|
|
399
|
-
* The primary API — call this before/during page interactions.
|
|
400
|
-
*
|
|
401
|
-
* @param {import('playwright').Page} page
|
|
402
|
-
* @param {'reading'|'form'|'shopping'|'login'|'idle'} category
|
|
403
|
-
* @param {object} opts — passed through to replayPattern
|
|
404
|
-
*/
|
|
405
|
-
async function injectBehaviour(page, category = 'reading', opts = {}) {
|
|
406
|
-
const names = CATEGORY_PATTERNS[category] || CATEGORY_PATTERNS.reading;
|
|
407
|
-
const name = names[Math.floor(Math.random() * names.length)];
|
|
408
|
-
return replayPattern(page, name, opts);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// ── Procedural synthesis (no pre-recorded pattern needed) ─────────────────────
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Generate and replay a synthetic behaviour sequence using Gaussian velocity
|
|
415
|
-
* curves and natural micro-pause distributions. Less realistic than recorded
|
|
416
|
-
* patterns but requires no data.
|
|
417
|
-
*
|
|
418
|
-
* @param {import('playwright').Page} page
|
|
419
|
-
* @param {object} opts
|
|
420
|
-
* @param {number} opts.scrollDepth - how far to scroll (0–1, fraction of page height)
|
|
421
|
-
* @param {number} opts.durationMs - total duration (default 5000)
|
|
422
|
-
* @param {number} opts.pauseCount - number of reading pauses (default 3)
|
|
423
|
-
*/
|
|
424
|
-
async function syntheticBrowse(page, opts = {}) {
|
|
425
|
-
const vp = page.viewportSize() || { width: 1440, height: 900 };
|
|
426
|
-
const durationMs = opts.durationMs ?? 5000;
|
|
427
|
-
const scrollDepth = opts.scrollDepth ?? 0.6;
|
|
428
|
-
const pauseCount = opts.pauseCount ?? 3;
|
|
429
|
-
|
|
430
|
-
const steps = 12 + Math.floor(Math.random() * 8);
|
|
431
|
-
const stepMs = durationMs / steps;
|
|
432
|
-
const maxScroll = Math.round(vp.height * scrollDepth * 3); // approximate page content height
|
|
433
|
-
let scrolled = 0;
|
|
434
|
-
|
|
435
|
-
// Initial cursor position — upper third of viewport
|
|
436
|
-
let curX = Math.round(vp.width * (0.3 + Math.random() * 0.4));
|
|
437
|
-
let curY = Math.round(vp.height * (0.1 + Math.random() * 0.2));
|
|
438
|
-
await page.mouse.move(curX, curY, { steps: 5 }).catch(() => {});
|
|
439
|
-
|
|
440
|
-
for (let i = 0; i < steps; i++) {
|
|
441
|
-
const t = i / steps; // progress 0→1
|
|
442
|
-
|
|
443
|
-
// Decide action — weighted by scroll progress
|
|
444
|
-
const rand = Math.random();
|
|
445
|
-
|
|
446
|
-
if (rand < 0.35 && scrolled < maxScroll) {
|
|
447
|
-
// Scroll — velocity follows Gaussian bell: fast middle, slow start/end
|
|
448
|
-
const progress = t < 0.5 ? t * 2 : (1 - t) * 2; // bell shape
|
|
449
|
-
const velocity = 60 + Math.round(progress * 180 * (0.8 + Math.random() * 0.4));
|
|
450
|
-
scrolled += velocity;
|
|
451
|
-
await page.mouse.wheel(0, velocity).catch(() => {});
|
|
452
|
-
await page.waitForTimeout(Math.round(stepMs * (0.4 + Math.random() * 0.3)));
|
|
453
|
-
|
|
454
|
-
} else if (rand < 0.65) {
|
|
455
|
-
// Mouse move — Bezier-like arc toward a target point
|
|
456
|
-
const targetX = Math.round(vp.width * (0.2 + Math.random() * 0.6));
|
|
457
|
-
const targetY = Math.round(vp.height * (0.2 + Math.random() * 0.6));
|
|
458
|
-
const moveSteps = 6 + Math.floor(Math.random() * 10);
|
|
459
|
-
await page.mouse.move(targetX, targetY, { steps: moveSteps }).catch(() => {});
|
|
460
|
-
curX = targetX;
|
|
461
|
-
curY = targetY;
|
|
462
|
-
await page.waitForTimeout(Math.round(stepMs * (0.3 + Math.random() * 0.2)));
|
|
463
|
-
|
|
464
|
-
} else {
|
|
465
|
-
// Pause — reading dwell time (Gaussian ~800ms, range 300–2000ms)
|
|
466
|
-
const dwell = Math.max(300, Math.round(_gaussianMs(800, 350)));
|
|
467
|
-
await page.waitForTimeout(dwell);
|
|
468
|
-
|
|
469
|
-
// Micro cursor drift during reading pause
|
|
470
|
-
const driftX = curX + Math.round((Math.random() - 0.5) * 20);
|
|
471
|
-
const driftY = curY + Math.round((Math.random() - 0.5) * 10);
|
|
472
|
-
await page.mouse.move(
|
|
473
|
-
Math.max(0, Math.min(vp.width, driftX)),
|
|
474
|
-
Math.max(0, Math.min(vp.height, driftY)),
|
|
475
|
-
{ steps: 3 }
|
|
476
|
-
).catch(() => {});
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return { synthetic: true, durationMs, scrolled };
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ── Custom pattern management ─────────────────────────────────────────────────
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Load a custom pattern recorded with cloak-recorder.
|
|
487
|
-
* @param {string} name - Pattern name to register
|
|
488
|
-
* @param {object} data - Raw recorder output (events array with absolute timestamps)
|
|
489
|
-
*/
|
|
490
|
-
function loadCustomPattern(name, data) {
|
|
491
|
-
// Normalise absolute timestamps → dt (delta from previous)
|
|
492
|
-
const events = Array.isArray(data) ? data : data.events;
|
|
493
|
-
if (!events?.length) throw new Error('loadCustomPattern: events array required');
|
|
494
|
-
|
|
495
|
-
const vp = data.viewport || { w: 1440, h: 900 };
|
|
496
|
-
const t0 = events[0].t || 0;
|
|
497
|
-
let lastT = 0;
|
|
498
|
-
|
|
499
|
-
const normalised = events.map(e => {
|
|
500
|
-
const dt = (e.t - t0) - lastT;
|
|
501
|
-
lastT = e.t - t0;
|
|
502
|
-
return {
|
|
503
|
-
dt,
|
|
504
|
-
type: e.type,
|
|
505
|
-
// Normalise coordinates if they look like pixels
|
|
506
|
-
x: e.x !== undefined ? (e.x > 1 ? e.x / vp.w : e.x) : undefined,
|
|
507
|
-
y: e.y !== undefined ? (e.y > 1 ? e.y / vp.h : e.y) : undefined,
|
|
508
|
-
dy: e.dy,
|
|
509
|
-
};
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const totalDuration = (events[events.length - 1].t || 0) - t0;
|
|
513
|
-
|
|
514
|
-
_customPatterns.set(name, {
|
|
515
|
-
category: data.category || 'custom',
|
|
516
|
-
viewport: vp,
|
|
517
|
-
durationMs: totalDuration,
|
|
518
|
-
description: data.description || `Custom pattern: ${name}`,
|
|
519
|
-
events: normalised,
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
return { loaded: true, name, events: normalised.length, durationMs: totalDuration };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* Save a custom pattern to disk for reuse.
|
|
527
|
-
*/
|
|
528
|
-
function saveCustomPattern(name) {
|
|
529
|
-
const pattern = _customPatterns.get(name);
|
|
530
|
-
if (!pattern) throw new Error(`Pattern "${name}" not loaded`);
|
|
531
|
-
const dir = path.join(os.homedir(), '.vektor', 'behaviour-patterns');
|
|
532
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
533
|
-
const file = path.join(dir, `${name.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
|
534
|
-
fs.writeFileSync(file, JSON.stringify(pattern, null, 2));
|
|
535
|
-
return file;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Load all saved custom patterns from disk.
|
|
540
|
-
*/
|
|
541
|
-
function loadSavedPatterns() {
|
|
542
|
-
const dir = path.join(os.homedir(), '.vektor', 'behaviour-patterns');
|
|
543
|
-
if (!fs.existsSync(dir)) return [];
|
|
544
|
-
const loaded = [];
|
|
545
|
-
for (const file of fs.readdirSync(dir).filter(f => f.endsWith('.json'))) {
|
|
546
|
-
try {
|
|
547
|
-
const name = file.replace('.json', '');
|
|
548
|
-
const pattern = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
549
|
-
_customPatterns.set(name, pattern);
|
|
550
|
-
loaded.push(name);
|
|
551
|
-
} catch { /* skip corrupt files */ }
|
|
552
|
-
}
|
|
553
|
-
return loaded;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// ── Stats / introspection ─────────────────────────────────────────────────────
|
|
557
|
-
|
|
558
|
-
function behaviourStats() {
|
|
559
|
-
const builtIn = Object.keys(PATTERN_LIBRARY);
|
|
560
|
-
const custom = [..._customPatterns.keys()];
|
|
561
|
-
return {
|
|
562
|
-
builtInPatterns: builtIn,
|
|
563
|
-
customPatterns: custom,
|
|
564
|
-
categories: Object.keys(CATEGORY_PATTERNS),
|
|
565
|
-
totalPatterns: builtIn.length + custom.length,
|
|
566
|
-
description: 'Replay real human mouse/scroll patterns via Playwright CDP. ' +
|
|
567
|
-
'Defeats reCAPTCHA v3 / Cloudflare / DataDome behavioural scoring.',
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
572
|
-
|
|
573
|
-
/** Box-Muller Gaussian sample */
|
|
574
|
-
function _gaussianMs(mean, stddev) {
|
|
575
|
-
const u1 = Math.random(), u2 = Math.random();
|
|
576
|
-
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
577
|
-
return mean + z * stddev;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/** Return all pattern names for a category */
|
|
581
|
-
function listPatterns(category) {
|
|
582
|
-
if (category) return CATEGORY_PATTERNS[category] || [];
|
|
583
|
-
return [...Object.keys(PATTERN_LIBRARY), ..._customPatterns.keys()];
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
module.exports = {
|
|
587
|
-
injectBehaviour,
|
|
588
|
-
replayPattern,
|
|
589
|
-
syntheticBrowse,
|
|
590
|
-
loadCustomPattern,
|
|
591
|
-
saveCustomPattern,
|
|
592
|
-
loadSavedPatterns,
|
|
593
|
-
behaviourStats,
|
|
594
|
-
listPatterns,
|
|
595
|
-
PATTERN_LIBRARY,
|
|
596
|
-
};
|