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.
Files changed (56) hide show
  1. package/README.md +67 -306
  2. package/package.json +14 -146
  3. package/CHANGELOG.md +0 -139
  4. package/LICENSE +0 -33
  5. package/TENETS.md +0 -189
  6. package/audn-log.js +0 -143
  7. package/axon.js +0 -389
  8. package/boot-patch.js +0 -33
  9. package/boot-screen.html +0 -210
  10. package/briefing.js +0 -150
  11. package/cerebellum.js +0 -439
  12. package/cloak-behaviour.js +0 -596
  13. package/cloak-captcha.js +0 -541
  14. package/cloak-core.js +0 -499
  15. package/cloak-identity.js +0 -484
  16. package/cloak-index.js +0 -261
  17. package/cloak-llms.js +0 -163
  18. package/cloak-pattern-store.js +0 -471
  19. package/cloak-recorder-auto.js +0 -297
  20. package/cloak-recorder-snippet.js +0 -119
  21. package/cloak-turbo-quant.js +0 -357
  22. package/cloak-warmup.js +0 -240
  23. package/cortex.js +0 -221
  24. package/detect-hardware.js +0 -181
  25. package/entity-resolver.js +0 -298
  26. package/errors.js +0 -66
  27. package/examples/example-claude-mcp.js +0 -220
  28. package/examples/example-langchain-researcher.js +0 -82
  29. package/examples/example-openai-assistant.js +0 -84
  30. package/examples/examples-README.md +0 -161
  31. package/export-import.js +0 -221
  32. package/forget.js +0 -148
  33. package/inspect.js +0 -199
  34. package/mistral/README-mistral.md +0 -123
  35. package/mistral/mistral-bridge.js +0 -218
  36. package/mistral/mistral-setup.js +0 -220
  37. package/mistral/vektor-tool-manifest.json +0 -41
  38. package/models/model_quantized.onnx +0 -0
  39. package/models/vocab.json +0 -1
  40. package/namespace.js +0 -186
  41. package/pin.js +0 -91
  42. package/slipstream-core-extended.js +0 -134
  43. package/slipstream-core.js +0 -1
  44. package/slipstream-db.js +0 -140
  45. package/slipstream-embedder.js +0 -338
  46. package/sovereign.js +0 -142
  47. package/token.js +0 -322
  48. package/types/index.d.ts +0 -269
  49. package/vektor-banner-loader.js +0 -109
  50. package/vektor-cli.js +0 -259
  51. package/vektor-licence-prompt.js +0 -128
  52. package/vektor-licence.js +0 -192
  53. package/vektor-setup.js +0 -270
  54. package/vektor-slipstream.dxt +0 -0
  55. package/vektor-tui.js +0 -373
  56. package/visualize.js +0 -235
@@ -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
- };