aurix-ai 0.1.0 → 2.1.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.
@@ -22,6 +22,116 @@ function warn(msg, details) {
22
22
  lines.push(` ${k}: ${v}`);
23
23
  return lines.join('\n');
24
24
  }
25
+ // ─── Human-Like Mouse Utilities ────────────────────────────────────────────
26
+ function bezierPoint(t, points) {
27
+ if (points.length === 1)
28
+ return points[0];
29
+ const next = [];
30
+ for (let i = 0; i < points.length - 1; i++) {
31
+ next.push([
32
+ points[i][0] + (points[i + 1][0] - points[i][0]) * t,
33
+ points[i][1] + (points[i + 1][1] - points[i][1]) * t,
34
+ ]);
35
+ }
36
+ return bezierPoint(t, next);
37
+ }
38
+ function easeInOut(t) {
39
+ return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
40
+ }
41
+ async function humanMove(x, y, page) {
42
+ const mouse = page.mouse;
43
+ const vp = page.viewportSize() || { width: 1280, height: 720 };
44
+ // Start from a random position if we don't know current pos
45
+ const startX = Math.random() * vp.width * 0.3;
46
+ const startY = Math.random() * vp.height * 0.3;
47
+ // Generate 2-4 control points for bezier curve
48
+ const numControls = 2 + Math.floor(Math.random() * 3);
49
+ const controlPoints = [[startX, startY]];
50
+ for (let i = 0; i < numControls; i++) {
51
+ const frac = (i + 1) / (numControls + 1);
52
+ const cx = startX + (x - startX) * frac + (Math.random() - 0.5) * 80;
53
+ const cy = startY + (y - startY) * frac + (Math.random() - 0.5) * 60;
54
+ controlPoints.push([cx, cy]);
55
+ }
56
+ controlPoints.push([x, y]);
57
+ // Step through the curve with eased timing
58
+ const totalSteps = 25 + Math.floor(Math.random() * 20);
59
+ for (let step = 0; step <= totalSteps; step++) {
60
+ const rawT = step / totalSteps;
61
+ const t = easeInOut(rawT);
62
+ const [px, py] = bezierPoint(t, controlPoints);
63
+ // Sine-wave micro-tremor (not uniform random)
64
+ const tremor = Math.sin(step * 0.3 + Math.random() * 0.5) * 0.4;
65
+ const tremorY = Math.cos(step * 0.25 + Math.random() * 0.5) * 0.3;
66
+ await mouse.move(px + tremor, py + tremorY);
67
+ // Variable step delay: faster in middle, slower at start/end
68
+ const speedFactor = 1 - Math.abs(rawT - 0.5) * 2;
69
+ const delay = 8 + Math.random() * 12 + speedFactor * 5;
70
+ await page.waitForTimeout(delay);
71
+ }
72
+ // Occasional overshoot + correction
73
+ if (Math.random() > 0.6) {
74
+ const overX = x + (Math.random() - 0.5) * 8;
75
+ const overY = y + (Math.random() - 0.5) * 8;
76
+ await mouse.move(overX, overY);
77
+ await page.waitForTimeout(30 + Math.random() * 40);
78
+ await mouse.move(x, y);
79
+ await page.waitForTimeout(20 + Math.random() * 30);
80
+ }
81
+ }
82
+ async function warmupBehavior(page) {
83
+ const vp = page.viewportSize() || { width: 1280, height: 720 };
84
+ const spots = 3 + Math.floor(Math.random() * 3);
85
+ for (let i = 0; i < spots; i++) {
86
+ const rx = Math.random() * vp.width;
87
+ const ry = Math.random() * vp.height;
88
+ await humanMove(rx, ry, page);
89
+ await page.waitForTimeout(200 + Math.random() * 600);
90
+ }
91
+ // Small scroll
92
+ if (Math.random() > 0.4) {
93
+ const scrollDelta = Math.floor(Math.random() * 200) - 100;
94
+ await page.mouse.wheel(0, scrollDelta);
95
+ await page.waitForTimeout(300 + Math.random() * 500);
96
+ }
97
+ }
98
+ async function humanHold(x, y, duration, page) {
99
+ const mouse = page.mouse;
100
+ const holdSteps = Math.floor(duration / 80);
101
+ const breathFreq = 0.15 + Math.random() * 0.1;
102
+ const breathAmpX = 0.3 + Math.random() * 0.4;
103
+ const breathAmpY = 0.2 + Math.random() * 0.3;
104
+ await mouse.down();
105
+ for (let i = 0; i < holdSteps; i++) {
106
+ // Sine-wave breathing movement (natural hand tremor)
107
+ const breathX = Math.sin(i * breathFreq) * breathAmpX;
108
+ const breathY = Math.cos(i * breathFreq * 0.7) * breathAmpY;
109
+ // Occasional micro-adjustment
110
+ const adjX = Math.random() > 0.95 ? (Math.random() - 0.5) * 2 : 0;
111
+ const adjY = Math.random() > 0.95 ? (Math.random() - 0.5) * 2 : 0;
112
+ await mouse.move(x + breathX + adjX, y + breathY + adjY);
113
+ await page.waitForTimeout(60 + Math.random() * 40);
114
+ }
115
+ // Release with slight upward drift
116
+ await mouse.move(x + (Math.random() - 0.5) * 3, y - 1 - Math.random() * 2);
117
+ await page.waitForTimeout(30 + Math.random() * 50);
118
+ await mouse.up();
119
+ }
120
+ async function humanClick(locator, page) {
121
+ const box = await locator.first().boundingBox();
122
+ if (box) {
123
+ const clickX = box.x + box.width * (0.3 + Math.random() * 0.4);
124
+ const clickY = box.y + box.height * (0.3 + Math.random() * 0.4);
125
+ await humanMove(clickX, clickY, page);
126
+ await page.waitForTimeout(60 + Math.random() * 100);
127
+ await page.mouse.down();
128
+ await page.waitForTimeout(50 + Math.random() * 80);
129
+ await page.mouse.up();
130
+ }
131
+ else {
132
+ await locator.first().click({ force: true });
133
+ }
134
+ }
25
135
  const sessions = new Map();
26
136
  let currentSessionKey = 'default';
27
137
  let consecutiveEvalFailures = 0;
@@ -1143,7 +1253,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1143
1253
  const checkbox = checkboxFrame.locator('#recaptcha-anchor, .recaptcha-checkbox, .rc-anchor-checkbox');
1144
1254
  if (await checkbox.count() > 0) {
1145
1255
  await p.waitForTimeout(1000 + Math.random() * 1500);
1146
- await checkbox.first().click({ force: true });
1256
+ await humanClick(checkbox, p);
1147
1257
  await p.waitForTimeout(3000);
1148
1258
  const updatedFrames = p.frames();
1149
1259
  const challengeFrame = updatedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
@@ -1164,7 +1274,8 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1164
1274
  }
1165
1275
  else {
1166
1276
  results.push(warn('reCAPTCHA anchor frame found but checkbox element missing', { action: 'trying click on anchor body' }));
1167
- await checkboxFrame.locator('#recaptcha-anchor').click({ force: true }).catch(() => { });
1277
+ const anchor = checkboxFrame.locator('#recaptcha-anchor');
1278
+ await humanClick(anchor, p).catch(() => { });
1168
1279
  await p.waitForTimeout(3000);
1169
1280
  results.push('Use "captcha-grid" to check for image challenge.');
1170
1281
  }
@@ -1173,7 +1284,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1173
1284
  results.push(warn('No reCAPTCHA anchor frame found', { action: 'trying main page widget' }));
1174
1285
  const mainCheckbox = p.locator('.g-recaptcha, [data-sitekey]');
1175
1286
  if (await mainCheckbox.count() > 0) {
1176
- await mainCheckbox.first().click({ force: true });
1287
+ await humanClick(mainCheckbox, p);
1177
1288
  await p.waitForTimeout(3000);
1178
1289
  results.push('Clicked reCAPTCHA widget. Use "captcha-grid" if image challenge appeared.');
1179
1290
  }
@@ -1194,7 +1305,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1194
1305
  const checkbox = checkboxFrame.locator('#checkbox, .check');
1195
1306
  if (await checkbox.count() > 0) {
1196
1307
  await p.waitForTimeout(800 + Math.random() * 1200);
1197
- await checkbox.first().click({ force: true });
1308
+ await humanClick(checkbox, p);
1198
1309
  await p.waitForTimeout(3000);
1199
1310
  const updatedFrames = p.frames();
1200
1311
  const challengeFrame = updatedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
@@ -1233,7 +1344,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1233
1344
  await p.waitForTimeout(1500 + Math.random() * 1000);
1234
1345
  const cb = turnstileFrame.locator('input[type="checkbox"], .cb-lb');
1235
1346
  if (await cb.count() > 0) {
1236
- await cb.first().click({ force: true });
1347
+ await humanClick(cb, p);
1237
1348
  await p.waitForTimeout(3000);
1238
1349
  results.push(ok('Turnstile checkbox clicked'));
1239
1350
  }
@@ -1502,7 +1613,19 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1502
1613
  return err(`Tile index ${tileIndex} out of range (0-${tiles.length - 1})`);
1503
1614
  try {
1504
1615
  const tile = tiles[tileIndex];
1505
- await tile.click({ force: true });
1616
+ const tileBox = await tile.boundingBox();
1617
+ if (tileBox) {
1618
+ const clickX = tileBox.x + tileBox.width * (0.3 + Math.random() * 0.4);
1619
+ const clickY = tileBox.y + tileBox.height * (0.3 + Math.random() * 0.4);
1620
+ await humanMove(clickX, clickY, p);
1621
+ await p.waitForTimeout(80 + Math.random() * 120);
1622
+ await p.mouse.down();
1623
+ await p.waitForTimeout(60 + Math.random() * 100);
1624
+ await p.mouse.up();
1625
+ }
1626
+ else {
1627
+ await tile.click({ force: true });
1628
+ }
1506
1629
  await p.waitForTimeout(500 + Math.random() * 400);
1507
1630
  const isRecaptcha = provider === 'recaptcha';
1508
1631
  const selectedClass = isRecaptcha ? '.rc-imageselect-dynamic-selected' : '.task-image.selected, .task .selected';
@@ -1561,7 +1684,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1561
1684
  if (await verifyBtn.count() === 0) {
1562
1685
  return err('No verify button found', 'Use "captcha-grid" to analyze the challenge first');
1563
1686
  }
1564
- await verifyBtn.first().click({ force: true });
1687
+ await humanClick(verifyBtn, p);
1565
1688
  await p.waitForTimeout(3000);
1566
1689
  const screenshotPath = join(homedir(), '.aurix-captcha-verify-result.png');
1567
1690
  const errorText = await challengeFrame.locator('.rc-imageselect-incorrect-response, .error-message, .incorrect').count();
@@ -2151,8 +2274,8 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2151
2274
  const sourceBox = await sourceEl.boundingBox();
2152
2275
  if (!sourceBox)
2153
2276
  return err(`Source element "${target}" not found or not visible`);
2154
- const startX = sourceBox.x + sourceBox.width / 2;
2155
- const startY = sourceBox.y + sourceBox.height / 2;
2277
+ const startX = sourceBox.x + sourceBox.width * (0.3 + Math.random() * 0.4);
2278
+ const startY = sourceBox.y + sourceBox.height * (0.3 + Math.random() * 0.4);
2156
2279
  let endX, endY;
2157
2280
  const coords = value.split(',').map(s => parseInt(s.trim()));
2158
2281
  if (coords.length === 2 && !isNaN(coords[0]) && !isNaN(coords[1])) {
@@ -2164,28 +2287,52 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2164
2287
  const targetBox = await targetEl.boundingBox();
2165
2288
  if (!targetBox)
2166
2289
  return err(`Target element "${value}" not found or not visible`);
2167
- endX = targetBox.x + targetBox.width / 2;
2168
- endY = targetBox.y + targetBox.height / 2;
2290
+ endX = targetBox.x + targetBox.width * (0.3 + Math.random() * 0.4);
2291
+ endY = targetBox.y + targetBox.height * (0.3 + Math.random() * 0.4);
2169
2292
  }
2170
- await p.mouse.move(startX, startY);
2171
- await p.waitForTimeout(100 + Math.random() * 150);
2293
+ await warmupBehavior(p);
2294
+ await humanMove(startX, startY, p);
2295
+ await p.waitForTimeout(150 + Math.random() * 250);
2172
2296
  await p.mouse.down();
2173
- await p.waitForTimeout(200 + Math.random() * 200);
2174
- const steps = 15 + Math.floor(Math.random() * 10);
2175
- for (let i = 1; i <= steps; i++) {
2176
- const progress = i / steps;
2177
- const eased = progress < 0.5
2178
- ? 2 * progress * progress
2179
- : 1 - Math.pow(-2 * progress + 2, 2) / 2;
2180
- const x = startX + (endX - startX) * eased + (Math.random() - 0.5) * 2;
2181
- const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 2;
2182
- await p.mouse.move(x, y);
2183
- await p.waitForTimeout(10 + Math.random() * 25);
2184
- }
2185
- await p.mouse.move(endX, endY);
2186
- await p.waitForTimeout(100 + Math.random() * 200);
2297
+ await p.waitForTimeout(200 + Math.random() * 300);
2298
+ const distance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);
2299
+ const numControls = distance > 200 ? 3 : 2;
2300
+ const dragPoints = [[startX, startY]];
2301
+ for (let i = 0; i < numControls; i++) {
2302
+ const frac = (i + 1) / (numControls + 1);
2303
+ const perpX = -(endY - startY) / distance;
2304
+ const perpY = (endX - startX) / distance;
2305
+ const offset = (Math.random() - 0.5) * Math.min(distance * 0.15, 40);
2306
+ const cx = startX + (endX - startX) * frac + perpX * offset;
2307
+ const cy = startY + (endY - startY) * frac + perpY * offset;
2308
+ dragPoints.push([cx, cy]);
2309
+ }
2310
+ dragPoints.push([endX, endY]);
2311
+ const dragSteps = 25 + Math.floor(Math.random() * 20);
2312
+ for (let step = 0; step <= dragSteps; step++) {
2313
+ const rawT = step / dragSteps;
2314
+ const t = easeInOut(rawT);
2315
+ const [px, py] = bezierPoint(t, dragPoints);
2316
+ const tremor = Math.sin(step * 0.35 + Math.random() * 0.5) * 0.5;
2317
+ const tremorY = Math.cos(step * 0.3 + Math.random() * 0.5) * 0.4;
2318
+ await p.mouse.move(px + tremor, py + tremorY);
2319
+ const speedFactor = 1 - Math.abs(rawT - 0.5) * 2;
2320
+ const delay = 10 + Math.random() * 15 + speedFactor * 8;
2321
+ await p.waitForTimeout(delay);
2322
+ }
2323
+ if (Math.random() > 0.5) {
2324
+ const overX = endX + (Math.random() - 0.5) * 6;
2325
+ const overY = endY + (Math.random() - 0.5) * 6;
2326
+ await p.mouse.move(overX, overY);
2327
+ await p.waitForTimeout(40 + Math.random() * 60);
2328
+ await p.mouse.move(endX, endY);
2329
+ await p.waitForTimeout(30 + Math.random() * 50);
2330
+ }
2331
+ await p.waitForTimeout(80 + Math.random() * 150);
2332
+ await p.mouse.move(endX + (Math.random() - 0.5) * 2, endY - 1 - Math.random());
2333
+ await p.waitForTimeout(30 + Math.random() * 40);
2187
2334
  await p.mouse.up();
2188
- await p.waitForTimeout(500);
2335
+ await p.waitForTimeout(400 + Math.random() * 300);
2189
2336
  const screenshotPath = join(homedir(), '.aurix-drag-result.png');
2190
2337
  await p.screenshot({ path: screenshotPath });
2191
2338
  return ok(`Dragged "${target}" to (${Math.round(endX)}, ${Math.round(endY)})`, {
@@ -2203,29 +2350,26 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2203
2350
  const p = await ensureBrowser();
2204
2351
  if (!target)
2205
2352
  return err('hold-click requires a target element');
2206
- const duration = parseInt(value) || 3000;
2353
+ const baseDuration = parseInt(value) || 5000;
2354
+ const duration = baseDuration + Math.floor(Math.random() * 3000) - 1000;
2207
2355
  try {
2208
2356
  const el = p.locator(target).first();
2209
2357
  const box = await el.boundingBox();
2210
2358
  if (!box)
2211
2359
  return err(`Element "${target}" not found or not visible`);
2212
- const x = box.x + box.width / 2;
2213
- const y = box.y + box.height / 2;
2214
- await p.mouse.move(x, y);
2215
- await p.waitForTimeout(100 + Math.random() * 100);
2216
- await p.mouse.down();
2217
- const holdSteps = Math.floor(duration / 100);
2218
- for (let i = 0; i < holdSteps; i++) {
2219
- const jitterX = x + (Math.random() - 0.5) * 3;
2220
- const jitterY = y + (Math.random() - 0.5) * 3;
2221
- await p.mouse.move(jitterX, jitterY);
2222
- await p.waitForTimeout(80 + Math.random() * 40);
2223
- }
2224
- await p.mouse.up();
2225
- await p.waitForTimeout(500);
2360
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
2361
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
2362
+ // Pre-interaction warmup: move mouse around naturally
2363
+ await warmupBehavior(p);
2364
+ // Bezier curve approach to target
2365
+ await humanMove(x, y, p);
2366
+ await p.waitForTimeout(100 + Math.random() * 200);
2367
+ // Human-like hold with breathing movements
2368
+ await humanHold(x, y, Math.max(2000, duration), p);
2369
+ await p.waitForTimeout(300 + Math.random() * 400);
2226
2370
  const screenshotPath = join(homedir(), '.aurix-hold-result.png');
2227
2371
  await p.screenshot({ path: screenshotPath });
2228
- return ok(`Held click on "${target}" for ${duration}ms`, {
2372
+ return ok(`Held click on "${target}" for ${Math.round(duration)}ms (human-like)`, {
2229
2373
  position: `(${Math.round(x)}, ${Math.round(y)})`,
2230
2374
  screenshot: screenshotPath,
2231
2375
  });