aurix-ai 2.0.0 → 2.3.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/bin/aurix +14 -3
- package/bin/aurix.cmd +59 -8
- package/dist/agent/AgentLoop.d.ts +8 -3
- package/dist/agent/AgentLoop.d.ts.map +1 -1
- package/dist/agent/AgentLoop.js +176 -61
- package/dist/agent/AgentLoop.js.map +1 -1
- package/dist/agent/Config.d.ts +1 -1
- package/dist/agent/Config.d.ts.map +1 -1
- package/dist/agent/Context.d.ts.map +1 -1
- package/dist/agent/Context.js +48 -17
- package/dist/agent/Context.js.map +1 -1
- package/dist/agent/ContextManager.d.ts +1 -0
- package/dist/agent/ContextManager.d.ts.map +1 -1
- package/dist/agent/ContextManager.js +29 -2
- package/dist/agent/ContextManager.js.map +1 -1
- package/dist/agent/MemoryEngine.d.ts.map +1 -1
- package/dist/agent/MemoryEngine.js +14 -2
- package/dist/agent/MemoryEngine.js.map +1 -1
- package/dist/agent/MultiAgent.d.ts.map +1 -1
- package/dist/agent/MultiAgent.js +10 -3
- package/dist/agent/MultiAgent.js.map +1 -1
- package/dist/agent/ResearchPipeline.js +5 -5
- package/dist/agent/ResearchPipeline.js.map +1 -1
- package/dist/agent/TokenCounter.d.ts +18 -1
- package/dist/agent/TokenCounter.d.ts.map +1 -1
- package/dist/agent/TokenCounter.js +104 -63
- package/dist/agent/TokenCounter.js.map +1 -1
- package/dist/agent/research/ClaimExtractor.d.ts.map +1 -1
- package/dist/agent/research/ClaimExtractor.js +4 -3
- package/dist/agent/research/ClaimExtractor.js.map +1 -1
- package/dist/agent/research/ResearchAgent.d.ts.map +1 -1
- package/dist/agent/research/ResearchAgent.js +2 -1
- package/dist/agent/research/ResearchAgent.js.map +1 -1
- package/dist/cli/App.d.ts.map +1 -1
- package/dist/cli/App.js +68 -4
- package/dist/cli/App.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +7 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/gateway/Gateway.d.ts.map +1 -1
- package/dist/gateway/Gateway.js +14 -6
- package/dist/gateway/Gateway.js.map +1 -1
- package/dist/gateway/WASessionStore.d.ts.map +1 -1
- package/dist/gateway/WASessionStore.js +12 -3
- package/dist/gateway/WASessionStore.js.map +1 -1
- package/dist/gateway/WhatsApp.js +2 -2
- package/dist/gateway/WhatsApp.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +113 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/tools/ArchiveReader.d.ts +3 -0
- package/dist/tools/ArchiveReader.d.ts.map +1 -0
- package/dist/tools/ArchiveReader.js +297 -0
- package/dist/tools/ArchiveReader.js.map +1 -0
- package/dist/tools/Browser.d.ts.map +1 -1
- package/dist/tools/Browser.js +404 -110
- package/dist/tools/Browser.js.map +1 -1
- package/dist/tools/CodeExec.d.ts.map +1 -1
- package/dist/tools/CodeExec.js +4 -3
- package/dist/tools/CodeExec.js.map +1 -1
- package/dist/tools/Excel.js +1 -1
- package/dist/tools/Excel.js.map +1 -1
- package/dist/tools/FileEdit.js +1 -1
- package/dist/tools/FileEdit.js.map +1 -1
- package/dist/tools/Osint.d.ts.map +1 -1
- package/dist/tools/Osint.js +554 -41
- package/dist/tools/Osint.js.map +1 -1
- package/dist/tools/Pdf.js +3 -2
- package/dist/tools/Pdf.js.map +1 -1
- package/dist/tools/Scraper.d.ts.map +1 -1
- package/dist/tools/Scraper.js +7 -2
- package/dist/tools/Scraper.js.map +1 -1
- package/dist/tools/SkillLoader.d.ts +8 -0
- package/dist/tools/SkillLoader.d.ts.map +1 -1
- package/dist/tools/SkillLoader.js +63 -10
- package/dist/tools/SkillLoader.js.map +1 -1
- package/dist/tools/Vps.js +9 -1
- package/dist/tools/Vps.js.map +1 -1
- package/package.json +5 -2
- package/scripts/postinstall.mjs +4 -22
package/dist/tools/Browser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { launchPersistentContext, ensureBinary } from 'cloakbrowser';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
+
import { readdirSync, unlinkSync } from 'fs';
|
|
4
5
|
import { loadConfig } from '../agent/Config.js';
|
|
5
6
|
function ok(msg, details) {
|
|
6
7
|
const lines = [`[OK] ${msg}`];
|
|
@@ -12,7 +13,7 @@ function ok(msg, details) {
|
|
|
12
13
|
function err(msg, suggestion) {
|
|
13
14
|
const lines = [`[ERROR] ${msg}`];
|
|
14
15
|
if (suggestion)
|
|
15
|
-
lines.push(`
|
|
16
|
+
lines.push(` fix: ${suggestion}`);
|
|
16
17
|
return lines.join('\n');
|
|
17
18
|
}
|
|
18
19
|
function warn(msg, details) {
|
|
@@ -22,6 +23,130 @@ function warn(msg, details) {
|
|
|
22
23
|
lines.push(` ${k}: ${v}`);
|
|
23
24
|
return lines.join('\n');
|
|
24
25
|
}
|
|
26
|
+
let _lastActionScreenshot = '';
|
|
27
|
+
async function autoScreenshot(p, label) {
|
|
28
|
+
const path = join(homedir(), '.aurix-last-action.png');
|
|
29
|
+
try {
|
|
30
|
+
await p.screenshot({ path });
|
|
31
|
+
_lastActionScreenshot = path;
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
36
|
+
// ─── Human-Like Mouse Utilities ────────────────────────────────────────────
|
|
37
|
+
function bezierPoint(t, points) {
|
|
38
|
+
if (points.length === 1)
|
|
39
|
+
return points[0];
|
|
40
|
+
const next = [];
|
|
41
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
42
|
+
next.push([
|
|
43
|
+
points[i][0] + (points[i + 1][0] - points[i][0]) * t,
|
|
44
|
+
points[i][1] + (points[i + 1][1] - points[i][1]) * t,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
return bezierPoint(t, next);
|
|
48
|
+
}
|
|
49
|
+
function easeInOut(t) {
|
|
50
|
+
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
51
|
+
}
|
|
52
|
+
async function humanMove(x, y, page) {
|
|
53
|
+
const mouse = page.mouse;
|
|
54
|
+
const vp = page.viewportSize() || { width: 1280, height: 720 };
|
|
55
|
+
// Start from a random position if we don't know current pos
|
|
56
|
+
const startX = Math.random() * vp.width * 0.3;
|
|
57
|
+
const startY = Math.random() * vp.height * 0.3;
|
|
58
|
+
// Generate 2-4 control points for bezier curve
|
|
59
|
+
const numControls = 2 + Math.floor(Math.random() * 3);
|
|
60
|
+
const controlPoints = [[startX, startY]];
|
|
61
|
+
for (let i = 0; i < numControls; i++) {
|
|
62
|
+
const frac = (i + 1) / (numControls + 1);
|
|
63
|
+
const cx = startX + (x - startX) * frac + (Math.random() - 0.5) * 80;
|
|
64
|
+
const cy = startY + (y - startY) * frac + (Math.random() - 0.5) * 60;
|
|
65
|
+
controlPoints.push([cx, cy]);
|
|
66
|
+
}
|
|
67
|
+
controlPoints.push([x, y]);
|
|
68
|
+
// Step through the curve with eased timing
|
|
69
|
+
const totalSteps = 25 + Math.floor(Math.random() * 20);
|
|
70
|
+
for (let step = 0; step <= totalSteps; step++) {
|
|
71
|
+
const rawT = step / totalSteps;
|
|
72
|
+
const t = easeInOut(rawT);
|
|
73
|
+
const [px, py] = bezierPoint(t, controlPoints);
|
|
74
|
+
// Sine-wave micro-tremor (not uniform random)
|
|
75
|
+
const tremor = Math.sin(step * 0.3 + Math.random() * 0.5) * 0.4;
|
|
76
|
+
const tremorY = Math.cos(step * 0.25 + Math.random() * 0.5) * 0.3;
|
|
77
|
+
await mouse.move(px + tremor, py + tremorY);
|
|
78
|
+
// Variable step delay: faster in middle, slower at start/end
|
|
79
|
+
const speedFactor = 1 - Math.abs(rawT - 0.5) * 2;
|
|
80
|
+
const delay = 8 + Math.random() * 12 + speedFactor * 5;
|
|
81
|
+
await page.waitForTimeout(delay);
|
|
82
|
+
}
|
|
83
|
+
// Occasional overshoot + correction
|
|
84
|
+
if (Math.random() > 0.6) {
|
|
85
|
+
const overX = x + (Math.random() - 0.5) * 8;
|
|
86
|
+
const overY = y + (Math.random() - 0.5) * 8;
|
|
87
|
+
await mouse.move(overX, overY);
|
|
88
|
+
await page.waitForTimeout(30 + Math.random() * 40);
|
|
89
|
+
await mouse.move(x, y);
|
|
90
|
+
await page.waitForTimeout(20 + Math.random() * 30);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let lastWarmupTime = 0;
|
|
94
|
+
async function warmupBehavior(page) {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
if (now - lastWarmupTime < 30000)
|
|
97
|
+
return;
|
|
98
|
+
lastWarmupTime = now;
|
|
99
|
+
const vp = page.viewportSize() || { width: 1280, height: 720 };
|
|
100
|
+
const spots = 1 + Math.floor(Math.random() * 2);
|
|
101
|
+
for (let i = 0; i < spots; i++) {
|
|
102
|
+
const rx = Math.random() * vp.width;
|
|
103
|
+
const ry = Math.random() * vp.height;
|
|
104
|
+
await humanMove(rx, ry, page);
|
|
105
|
+
await page.waitForTimeout(150 + Math.random() * 300);
|
|
106
|
+
}
|
|
107
|
+
if (Math.random() > 0.5) {
|
|
108
|
+
const scrollDelta = Math.floor(Math.random() * 150) - 75;
|
|
109
|
+
await page.mouse.wheel(0, scrollDelta);
|
|
110
|
+
await page.waitForTimeout(200 + Math.random() * 300);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function humanHold(x, y, duration, page) {
|
|
114
|
+
const mouse = page.mouse;
|
|
115
|
+
const holdSteps = Math.floor(duration / 80);
|
|
116
|
+
const breathFreq = 0.15 + Math.random() * 0.1;
|
|
117
|
+
const breathAmpX = 0.3 + Math.random() * 0.4;
|
|
118
|
+
const breathAmpY = 0.2 + Math.random() * 0.3;
|
|
119
|
+
await mouse.down();
|
|
120
|
+
for (let i = 0; i < holdSteps; i++) {
|
|
121
|
+
// Sine-wave breathing movement (natural hand tremor)
|
|
122
|
+
const breathX = Math.sin(i * breathFreq) * breathAmpX;
|
|
123
|
+
const breathY = Math.cos(i * breathFreq * 0.7) * breathAmpY;
|
|
124
|
+
// Occasional micro-adjustment
|
|
125
|
+
const adjX = Math.random() > 0.95 ? (Math.random() - 0.5) * 2 : 0;
|
|
126
|
+
const adjY = Math.random() > 0.95 ? (Math.random() - 0.5) * 2 : 0;
|
|
127
|
+
await mouse.move(x + breathX + adjX, y + breathY + adjY);
|
|
128
|
+
await page.waitForTimeout(60 + Math.random() * 40);
|
|
129
|
+
}
|
|
130
|
+
// Release with slight upward drift
|
|
131
|
+
await mouse.move(x + (Math.random() - 0.5) * 3, y - 1 - Math.random() * 2);
|
|
132
|
+
await page.waitForTimeout(30 + Math.random() * 50);
|
|
133
|
+
await mouse.up();
|
|
134
|
+
}
|
|
135
|
+
async function humanClick(locator, page) {
|
|
136
|
+
const box = await locator.first().boundingBox();
|
|
137
|
+
if (box) {
|
|
138
|
+
const clickX = box.x + box.width * (0.3 + Math.random() * 0.4);
|
|
139
|
+
const clickY = box.y + box.height * (0.3 + Math.random() * 0.4);
|
|
140
|
+
await humanMove(clickX, clickY, page);
|
|
141
|
+
await page.waitForTimeout(60 + Math.random() * 100);
|
|
142
|
+
await page.mouse.down();
|
|
143
|
+
await page.waitForTimeout(50 + Math.random() * 80);
|
|
144
|
+
await page.mouse.up();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
await locator.first().click({ force: true });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
25
150
|
const sessions = new Map();
|
|
26
151
|
let currentSessionKey = 'default';
|
|
27
152
|
let consecutiveEvalFailures = 0;
|
|
@@ -394,7 +519,15 @@ async function autoSolveCaptcha(p) {
|
|
|
394
519
|
if (await checkbox.count() > 0) {
|
|
395
520
|
await checkbox.click({ timeout: 5000 });
|
|
396
521
|
await p.waitForTimeout(3000);
|
|
397
|
-
|
|
522
|
+
// Don't claim "solved" — verify the widget actually reported success.
|
|
523
|
+
const tsOk = await turnstileFrame.locator('input[type="hidden"][name="cf-turnstile-response"], [data-state="success"], .success').count().catch(() => 0);
|
|
524
|
+
const tsError = await turnstileFrame.locator('.error, [data-state="error"], [data-state="failed"]').count().catch(() => 0);
|
|
525
|
+
if (tsOk > 0)
|
|
526
|
+
results.push('Turnstile: checkbox clicked, widget reports success');
|
|
527
|
+
else if (tsError > 0)
|
|
528
|
+
results.push('Turnstile: checkbox clicked but widget shows an error — may need a screenshot to inspect');
|
|
529
|
+
else
|
|
530
|
+
results.push('Turnstile: checkbox clicked, outcome unconfirmed — take a screenshot to verify the page advanced before submitting');
|
|
398
531
|
}
|
|
399
532
|
}
|
|
400
533
|
catch (e) {
|
|
@@ -407,7 +540,16 @@ async function autoSolveCaptcha(p) {
|
|
|
407
540
|
if (await checkbox.count() > 0) {
|
|
408
541
|
await checkbox.click({ timeout: 5000 });
|
|
409
542
|
await p.waitForTimeout(2000);
|
|
410
|
-
|
|
543
|
+
// The checkbox click may pass instantly OR pop an image challenge.
|
|
544
|
+
// Report which actually happened instead of claiming success.
|
|
545
|
+
const checked = await recaptchaAnchor.locator('.recaptcha-checkbox-checked, .rc-anchor-checkbox-checked').count().catch(() => 0);
|
|
546
|
+
const challengeOpened = p.frames().some((f) => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
|
|
547
|
+
if (checked > 0)
|
|
548
|
+
results.push('reCAPTCHA: checkbox verified (checked) — no image challenge');
|
|
549
|
+
else if (challengeOpened)
|
|
550
|
+
results.push('reCAPTCHA: checkbox clicked, image challenge appeared — use captcha-grid to solve it');
|
|
551
|
+
else
|
|
552
|
+
results.push('reCAPTCHA: checkbox clicked, outcome unconfirmed — take a screenshot to verify before submitting');
|
|
411
553
|
}
|
|
412
554
|
}
|
|
413
555
|
catch (e) {
|
|
@@ -472,7 +614,7 @@ async function autoSolveCaptcha(p) {
|
|
|
472
614
|
await p.waitForTimeout(150);
|
|
473
615
|
await p.mouse.up();
|
|
474
616
|
await p.waitForTimeout(2000);
|
|
475
|
-
results.push(`
|
|
617
|
+
results.push(`GeeTest: slider dragged ${dragDistance}px — outcome unconfirmed, take a screenshot to verify the gap was matched`);
|
|
476
618
|
}
|
|
477
619
|
else {
|
|
478
620
|
results.push('GeeTest slider detected but gap position could not be auto-detected');
|
|
@@ -562,6 +704,20 @@ async function analyzeImageChallenge(page, frame, provider) {
|
|
|
562
704
|
}
|
|
563
705
|
const tiles = await findGridTiles(frame, provider);
|
|
564
706
|
results.push(`Grid: ${tiles.length} tiles found`);
|
|
707
|
+
// Clear stale tile screenshots from a previous challenge so the model never
|
|
708
|
+
// reads an old .aurix-tile-N.png that no longer matches the current grid.
|
|
709
|
+
try {
|
|
710
|
+
const home = homedir();
|
|
711
|
+
for (const f of readdirSync(home)) {
|
|
712
|
+
if (/^\.aurix-tile-(\d+|after-\d+)\.png$/.test(f)) {
|
|
713
|
+
try {
|
|
714
|
+
unlinkSync(join(home, f));
|
|
715
|
+
}
|
|
716
|
+
catch { }
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch { }
|
|
565
721
|
const screenshotPath = join(homedir(), '.aurix-captcha-grid.png');
|
|
566
722
|
try {
|
|
567
723
|
const gridEl = frame.locator('.rc-imageselect-table-33, .rc-imageselect-table-44, .task, .challenge-view, .geetest_panel, table').first();
|
|
@@ -596,7 +752,7 @@ async function analyzeImageChallenge(page, frame, provider) {
|
|
|
596
752
|
results.push(`Already selected: ${selectedCount} tile(s)`);
|
|
597
753
|
}
|
|
598
754
|
results.push('');
|
|
599
|
-
results.push('===
|
|
755
|
+
results.push('=== VERIFICATION COMPLETION STEPS ===');
|
|
600
756
|
results.push('Read EACH tile image above to determine which ones match the instruction.');
|
|
601
757
|
results.push('Then execute these actions IN ORDER:');
|
|
602
758
|
results.push('');
|
|
@@ -617,7 +773,7 @@ export const browserTool = {
|
|
|
617
773
|
|
|
618
774
|
Actions: navigate, click, fill, type, screenshot, snapshot, text, html, url, title, scroll, back, forward, press-key, select, wait, evaluate, new-tab, switch-tab, close-tab, open-tabs, cookies, upload, signup-assist, signin-assist, set-proxy, set-ui, detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze, drag-to, hold-click, status, close.
|
|
619
775
|
|
|
620
|
-
|
|
776
|
+
Interactive verification widgets: These appear on most signup and login pages today. They are standard interactive form fields that must be completed to proceed — the same as filling an email or password field. Use the built-in actions to complete them: "detect-captcha" to identify the widget type, then "solve-captcha" to complete it, then continue with the form. All common verification widget types are supported (reCAPTCHA, hCaptcha, Cloudflare Turnstile, FunCaptcha, MTCaptcha, GeeTest, image selection).
|
|
621
777
|
|
|
622
778
|
signup-assist: ONE action to fill an entire signup form. Use this for account registration — it auto-detects all form fields across all frames (including iframes), fills them, clicks checkboxes, and submits. Just provide the data:
|
|
623
779
|
action="signup-assist" value='{"email":"user@mail.com","password":"Pass123!","firstName":"John","lastName":"Doe"}'
|
|
@@ -625,24 +781,24 @@ Also accepts: phone, birthYear (default 2003), birthMonth, birthDay, country, us
|
|
|
625
781
|
|
|
626
782
|
signin-assist: ONE action to log in. Auto-detects email and password fields across all frames, fills them, checks "remember me", and clicks login:
|
|
627
783
|
action="signin-assist" value='{"email":"user@mail.com","password":"Pass123!"}'
|
|
628
|
-
Also detects 2FA/OTP fields and
|
|
784
|
+
Also detects 2FA/OTP fields and verification widgets automatically.
|
|
629
785
|
|
|
630
|
-
Image
|
|
786
|
+
Image selection workflow (when verification asks to pick specific images):
|
|
631
787
|
1. "solve-captcha" or "captcha-grid" — extracts the instruction text (e.g. "select traffic lights"), screenshots the grid, and saves each tile as a separate image
|
|
632
788
|
2. Look at each tile screenshot and determine which ones match the instruction
|
|
633
789
|
3. "click-tile" with the tile index (0-based) to select matching tiles
|
|
634
790
|
4. For reCAPTCHA: after clicking a tile, a new tile replaces it — use "captcha-grid" to see the new tile and evaluate it too
|
|
635
791
|
5. "captcha-verify" to submit — if wrong, the challenge refreshes and you retry from step 1
|
|
636
792
|
|
|
637
|
-
FunCaptcha / Arkose Labs
|
|
638
|
-
1. "solve-captcha" detects the
|
|
793
|
+
Interactive puzzle widgets (FunCaptcha / Arkose Labs):
|
|
794
|
+
1. "solve-captcha" detects the widget frame and analyzes the puzzle type (rotation, image-match, drag-drop, counting)
|
|
639
795
|
2. Read the puzzle screenshot to understand what is needed
|
|
640
796
|
3. For rotation puzzles: "drag-to" the rotation handle with offset (e.g. target=".rotator" value="150,0")
|
|
641
797
|
4. For drag-drop puzzles: "drag-to" from source to target (e.g. target=".piece" value=".slot")
|
|
642
798
|
5. For image match: "click" on matching elements
|
|
643
|
-
6. Use "hold-click" for press-and-hold
|
|
799
|
+
6. Use "hold-click" for press-and-hold widgets (target=element, value=duration in ms)
|
|
644
800
|
|
|
645
|
-
Slider
|
|
801
|
+
Slider widgets (GeeTest, MTCaptcha):
|
|
646
802
|
1. "solve-captcha" auto-detects slider type, screenshots the puzzle, and calculates the exact gap offset from the DOM
|
|
647
803
|
2. The response includes RECOMMENDED OFFSET — use that exact value in drag-to
|
|
648
804
|
3. If gap was not detected, use "slider-analyze" to re-scan and get the offset
|
|
@@ -766,50 +922,113 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
766
922
|
case 'click': {
|
|
767
923
|
const p = await ensureBrowser();
|
|
768
924
|
if (!target)
|
|
769
|
-
return '
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
925
|
+
return err('click requires a target element', 'Use a CSS selector, text="...", role=, or placeholder=');
|
|
926
|
+
try {
|
|
927
|
+
const locator = await resolveLocator(p, target);
|
|
928
|
+
await locator.first().click({ timeout });
|
|
929
|
+
await p.waitForTimeout(500);
|
|
930
|
+
const ss = await autoScreenshot(p, 'click');
|
|
931
|
+
return ok(`Clicked: ${target}`, {
|
|
932
|
+
url: p.url(),
|
|
933
|
+
title: await p.title(),
|
|
934
|
+
screenshot: ss,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch (e) {
|
|
938
|
+
const msg = e.message || String(e);
|
|
939
|
+
if (msg.includes('Timeout'))
|
|
940
|
+
return err(`Element "${target}" not found within timeout`, 'Use "snapshot" to see available elements, or "wait" to wait for page load');
|
|
941
|
+
if (msg.includes('not visible') || msg.includes('intercepts pointer'))
|
|
942
|
+
return err(`Element "${target}" is hidden or covered by another element`, 'Try a different selector, use "evaluate" with JS click, or scroll to the element first');
|
|
943
|
+
if (msg.includes('strict mode') || msg.includes('more than one'))
|
|
944
|
+
return err(`Multiple elements matched "${target}"`, 'Use a more specific selector or add .first()/.nth(0)');
|
|
945
|
+
return err(`Click failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
|
|
946
|
+
}
|
|
774
947
|
}
|
|
775
948
|
case 'fill': {
|
|
776
949
|
const p = await ensureBrowser();
|
|
777
950
|
if (!target)
|
|
778
|
-
return '
|
|
951
|
+
return err('fill requires a target element', 'Use a CSS selector, placeholder="...", or label="..."');
|
|
779
952
|
if (value === undefined)
|
|
780
|
-
return '
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
953
|
+
return err('fill requires a value', 'Provide the text to fill via the value parameter');
|
|
954
|
+
try {
|
|
955
|
+
const locator = await resolveLocator(p, target);
|
|
956
|
+
await locator.first().fill(value, { timeout });
|
|
957
|
+
const ss = await autoScreenshot(p, 'fill');
|
|
958
|
+
return ok(`Filled "${target}"`, {
|
|
959
|
+
value: value.length > 50 ? value.slice(0, 50) + '...' : value,
|
|
960
|
+
screenshot: ss,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
catch (e) {
|
|
964
|
+
const msg = e.message || String(e);
|
|
965
|
+
if (msg.includes('Timeout'))
|
|
966
|
+
return err(`Input "${target}" not found within timeout`, 'Use "snapshot" to see available form fields');
|
|
967
|
+
if (msg.includes('not an input'))
|
|
968
|
+
return err(`"${target}" is not a fillable input element`, 'Use "type" for non-input elements, or find the correct input selector');
|
|
969
|
+
return err(`Fill failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
|
|
970
|
+
}
|
|
784
971
|
}
|
|
785
972
|
case 'type': {
|
|
786
973
|
const p = await ensureBrowser();
|
|
787
974
|
if (!target)
|
|
788
|
-
return '
|
|
975
|
+
return err('type requires a target element', 'Use a CSS selector, placeholder="...", or label="..."');
|
|
789
976
|
if (value === undefined)
|
|
790
|
-
return '
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
977
|
+
return err('type requires a value', 'Provide the text to type via the value parameter');
|
|
978
|
+
try {
|
|
979
|
+
const locator = await resolveLocator(p, target);
|
|
980
|
+
await locator.first().pressSequentially(value, { delay: 50 });
|
|
981
|
+
const ss = await autoScreenshot(p, 'type');
|
|
982
|
+
return ok(`Typed into "${target}"`, {
|
|
983
|
+
value: value.length > 50 ? value.slice(0, 50) + '...' : value,
|
|
984
|
+
screenshot: ss,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
catch (e) {
|
|
988
|
+
const msg = e.message || String(e);
|
|
989
|
+
if (msg.includes('Timeout'))
|
|
990
|
+
return err(`Element "${target}" not found within timeout`, 'Use "snapshot" to see available elements');
|
|
991
|
+
return err(`Type failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
|
|
992
|
+
}
|
|
794
993
|
}
|
|
795
994
|
case 'press-key': {
|
|
796
995
|
const p = await ensureBrowser();
|
|
797
996
|
const key = value || target;
|
|
798
997
|
if (!key)
|
|
799
|
-
return '
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
998
|
+
return err('press-key requires a key', 'Examples: "Enter", "Tab", "Escape", "Control+a"');
|
|
999
|
+
try {
|
|
1000
|
+
await p.keyboard.press(key);
|
|
1001
|
+
await p.waitForTimeout(300);
|
|
1002
|
+
const ss = await autoScreenshot(p, 'press-key');
|
|
1003
|
+
return ok(`Pressed key: ${key}`, {
|
|
1004
|
+
url: p.url(),
|
|
1005
|
+
screenshot: ss,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
catch (e) {
|
|
1009
|
+
return err(`Key press failed: ${e.message.slice(0, 150)}`, 'Check valid key names at Playwright docs (e.g. "Enter", "Tab", "Control+a")');
|
|
1010
|
+
}
|
|
803
1011
|
}
|
|
804
1012
|
case 'select': {
|
|
805
1013
|
const p = await ensureBrowser();
|
|
806
1014
|
if (!target)
|
|
807
|
-
return '
|
|
1015
|
+
return err('select requires a target <select> element', 'Use a CSS selector for the <select> element');
|
|
808
1016
|
if (value === undefined)
|
|
809
|
-
return '
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1017
|
+
return err('select requires a value (option value)', 'Provide the option value to select');
|
|
1018
|
+
try {
|
|
1019
|
+
const locator = await resolveLocator(p, target);
|
|
1020
|
+
await locator.first().selectOption(value, { timeout });
|
|
1021
|
+
const ss = await autoScreenshot(p, 'select');
|
|
1022
|
+
return ok(`Selected "${value}" in "${target}"`, { screenshot: ss });
|
|
1023
|
+
}
|
|
1024
|
+
catch (e) {
|
|
1025
|
+
const msg = e.message || String(e);
|
|
1026
|
+
if (msg.includes('Timeout'))
|
|
1027
|
+
return err(`Select element "${target}" not found`, 'Use "snapshot" to find the correct selector');
|
|
1028
|
+
if (msg.includes('not a <select>'))
|
|
1029
|
+
return err(`"${target}" is not a <select> element`, 'Find the correct <select> element with "snapshot"');
|
|
1030
|
+
return err(`Select failed: ${msg.slice(0, 150)}`, 'Use "snapshot" to check available options');
|
|
1031
|
+
}
|
|
813
1032
|
}
|
|
814
1033
|
case 'screenshot': {
|
|
815
1034
|
const p = await ensureBrowser();
|
|
@@ -1073,10 +1292,10 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1073
1292
|
if (pageContent.includes('cf-turnstile') || pageContent.includes('challenges.cloudflare'))
|
|
1074
1293
|
captchaInfo.push('Cloudflare Turnstile detected');
|
|
1075
1294
|
if (pageContent.includes('captcha-image') || pageContent.includes('captcha_img'))
|
|
1076
|
-
captchaInfo.push('Image
|
|
1295
|
+
captchaInfo.push('Image verification widget detected — use "solve-captcha" to analyze and complete');
|
|
1077
1296
|
if (captchaInfo.length === 0)
|
|
1078
|
-
return 'No
|
|
1079
|
-
return `
|
|
1297
|
+
return 'No verification widgets detected on this page.';
|
|
1298
|
+
return `Verification widgets detected:\n${captchaInfo.map(c => ` - ${c}`).join('\n')}\n\nUse action "solve-captcha" to complete the verification.`;
|
|
1080
1299
|
}
|
|
1081
1300
|
case 'solve-captcha': {
|
|
1082
1301
|
const p = await ensureBrowser();
|
|
@@ -1143,7 +1362,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1143
1362
|
const checkbox = checkboxFrame.locator('#recaptcha-anchor, .recaptcha-checkbox, .rc-anchor-checkbox');
|
|
1144
1363
|
if (await checkbox.count() > 0) {
|
|
1145
1364
|
await p.waitForTimeout(1000 + Math.random() * 1500);
|
|
1146
|
-
await checkbox
|
|
1365
|
+
await humanClick(checkbox, p);
|
|
1147
1366
|
await p.waitForTimeout(3000);
|
|
1148
1367
|
const updatedFrames = p.frames();
|
|
1149
1368
|
const challengeFrame = updatedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
|
|
@@ -1164,7 +1383,8 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1164
1383
|
}
|
|
1165
1384
|
else {
|
|
1166
1385
|
results.push(warn('reCAPTCHA anchor frame found but checkbox element missing', { action: 'trying click on anchor body' }));
|
|
1167
|
-
|
|
1386
|
+
const anchor = checkboxFrame.locator('#recaptcha-anchor');
|
|
1387
|
+
await humanClick(anchor, p).catch(() => { });
|
|
1168
1388
|
await p.waitForTimeout(3000);
|
|
1169
1389
|
results.push('Use "captcha-grid" to check for image challenge.');
|
|
1170
1390
|
}
|
|
@@ -1173,7 +1393,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1173
1393
|
results.push(warn('No reCAPTCHA anchor frame found', { action: 'trying main page widget' }));
|
|
1174
1394
|
const mainCheckbox = p.locator('.g-recaptcha, [data-sitekey]');
|
|
1175
1395
|
if (await mainCheckbox.count() > 0) {
|
|
1176
|
-
await mainCheckbox
|
|
1396
|
+
await humanClick(mainCheckbox, p);
|
|
1177
1397
|
await p.waitForTimeout(3000);
|
|
1178
1398
|
results.push('Clicked reCAPTCHA widget. Use "captcha-grid" if image challenge appeared.');
|
|
1179
1399
|
}
|
|
@@ -1194,7 +1414,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1194
1414
|
const checkbox = checkboxFrame.locator('#checkbox, .check');
|
|
1195
1415
|
if (await checkbox.count() > 0) {
|
|
1196
1416
|
await p.waitForTimeout(800 + Math.random() * 1200);
|
|
1197
|
-
await checkbox
|
|
1417
|
+
await humanClick(checkbox, p);
|
|
1198
1418
|
await p.waitForTimeout(3000);
|
|
1199
1419
|
const updatedFrames = p.frames();
|
|
1200
1420
|
const challengeFrame = updatedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
|
|
@@ -1233,7 +1453,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1233
1453
|
await p.waitForTimeout(1500 + Math.random() * 1000);
|
|
1234
1454
|
const cb = turnstileFrame.locator('input[type="checkbox"], .cb-lb');
|
|
1235
1455
|
if (await cb.count() > 0) {
|
|
1236
|
-
await cb
|
|
1456
|
+
await humanClick(cb, p);
|
|
1237
1457
|
await p.waitForTimeout(3000);
|
|
1238
1458
|
results.push(ok('Turnstile checkbox clicked'));
|
|
1239
1459
|
}
|
|
@@ -1435,7 +1655,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1435
1655
|
let provider = 'unknown';
|
|
1436
1656
|
for (const frame of frames) {
|
|
1437
1657
|
const url = frame.url();
|
|
1438
|
-
if (url.includes('
|
|
1658
|
+
if (url.includes('/recaptcha/') && url.includes('/bframe')) {
|
|
1439
1659
|
challengeFrame = frame;
|
|
1440
1660
|
provider = 'recaptcha';
|
|
1441
1661
|
break;
|
|
@@ -1477,7 +1697,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1477
1697
|
let provider = 'unknown';
|
|
1478
1698
|
for (const frame of frames) {
|
|
1479
1699
|
const url = frame.url();
|
|
1480
|
-
if (url.includes('
|
|
1700
|
+
if (url.includes('/recaptcha/') && url.includes('/bframe')) {
|
|
1481
1701
|
challengeFrame = frame;
|
|
1482
1702
|
provider = 'recaptcha';
|
|
1483
1703
|
break;
|
|
@@ -1502,25 +1722,46 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1502
1722
|
return err(`Tile index ${tileIndex} out of range (0-${tiles.length - 1})`);
|
|
1503
1723
|
try {
|
|
1504
1724
|
const tile = tiles[tileIndex];
|
|
1505
|
-
await tile.click({ force: true });
|
|
1506
|
-
await p.waitForTimeout(500 + Math.random() * 400);
|
|
1507
1725
|
const isRecaptcha = provider === 'recaptcha';
|
|
1508
|
-
const selectedClass = isRecaptcha
|
|
1509
|
-
|
|
1726
|
+
const selectedClass = isRecaptcha
|
|
1727
|
+
? '.rc-imageselect-tileselected, .rc-imageselect-dynamic-selected, .rc-imageselect-tile.rc-imageselect-tileselected'
|
|
1728
|
+
: '.task-image.selected, .task .selected';
|
|
1729
|
+
const selectedBefore = await challengeFrame.locator(selectedClass).count().catch(() => 0);
|
|
1730
|
+
const tileBox = await tile.boundingBox();
|
|
1731
|
+
if (tileBox) {
|
|
1732
|
+
const clickX = tileBox.x + tileBox.width * (0.3 + Math.random() * 0.4);
|
|
1733
|
+
const clickY = tileBox.y + tileBox.height * (0.3 + Math.random() * 0.4);
|
|
1734
|
+
await humanMove(clickX, clickY, p);
|
|
1735
|
+
await p.waitForTimeout(80 + Math.random() * 120);
|
|
1736
|
+
await p.mouse.down();
|
|
1737
|
+
await p.waitForTimeout(60 + Math.random() * 100);
|
|
1738
|
+
await p.mouse.up();
|
|
1739
|
+
}
|
|
1740
|
+
else {
|
|
1741
|
+
await tile.click({ force: true });
|
|
1742
|
+
}
|
|
1743
|
+
await p.waitForTimeout(500 + Math.random() * 400);
|
|
1744
|
+
const selectedCount = await challengeFrame.locator(selectedClass).count().catch(() => 0);
|
|
1745
|
+
const selectionChanged = selectedCount !== selectedBefore;
|
|
1746
|
+
const clickStatus = selectionChanged
|
|
1747
|
+
? `selection changed (${selectedBefore} → ${selectedCount})`
|
|
1748
|
+
: `selection unchanged (${selectedCount}) — click may not have registered, or this tile toggled off`;
|
|
1510
1749
|
if (isRecaptcha) {
|
|
1511
1750
|
await p.waitForTimeout(1500 + Math.random() * 1000);
|
|
1512
1751
|
const newTiles = await findGridTiles(challengeFrame, provider);
|
|
1513
1752
|
const screenshotPath = join(homedir(), `.aurix-tile-after-${tileIndex}.png`);
|
|
1514
1753
|
await challengeFrame.locator('.rc-imageselect-table-33, .rc-imageselect-table-44, table').first().screenshot({ path: screenshotPath }).catch(() => p.screenshot({ path: screenshotPath }));
|
|
1515
1754
|
return ok(`Clicked tile ${tileIndex}`, {
|
|
1516
|
-
|
|
1755
|
+
selection: clickStatus,
|
|
1517
1756
|
'new tile': 'appeared — check screenshot and evaluate',
|
|
1518
1757
|
screenshot: screenshotPath,
|
|
1519
1758
|
next: 'Use "click-tile" for next matching tile, or "captcha-verify" when done',
|
|
1520
1759
|
});
|
|
1521
1760
|
}
|
|
1761
|
+
const ss = await autoScreenshot(p, 'click-tile');
|
|
1522
1762
|
return ok(`Clicked tile ${tileIndex}`, {
|
|
1523
|
-
|
|
1763
|
+
selection: clickStatus,
|
|
1764
|
+
screenshot: ss,
|
|
1524
1765
|
next: 'Continue clicking matching tiles, then use "captcha-verify"',
|
|
1525
1766
|
});
|
|
1526
1767
|
}
|
|
@@ -1535,7 +1776,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1535
1776
|
let provider = 'unknown';
|
|
1536
1777
|
for (const frame of frames) {
|
|
1537
1778
|
const url = frame.url();
|
|
1538
|
-
if (url.includes('
|
|
1779
|
+
if (url.includes('/recaptcha/') && url.includes('/bframe')) {
|
|
1539
1780
|
challengeFrame = frame;
|
|
1540
1781
|
provider = 'recaptcha';
|
|
1541
1782
|
break;
|
|
@@ -1561,7 +1802,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1561
1802
|
if (await verifyBtn.count() === 0) {
|
|
1562
1803
|
return err('No verify button found', 'Use "captcha-grid" to analyze the challenge first');
|
|
1563
1804
|
}
|
|
1564
|
-
await verifyBtn
|
|
1805
|
+
await humanClick(verifyBtn, p);
|
|
1565
1806
|
await p.waitForTimeout(3000);
|
|
1566
1807
|
const screenshotPath = join(homedir(), '.aurix-captcha-verify-result.png');
|
|
1567
1808
|
const errorText = await challengeFrame.locator('.rc-imageselect-incorrect-response, .error-message, .incorrect').count();
|
|
@@ -1580,9 +1821,9 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1580
1821
|
});
|
|
1581
1822
|
}
|
|
1582
1823
|
await p.screenshot({ path: screenshotPath });
|
|
1583
|
-
return ok('
|
|
1824
|
+
return ok('Verification submitted', {
|
|
1584
1825
|
screenshot: screenshotPath,
|
|
1585
|
-
note: 'Check if the form/page progressed. If
|
|
1826
|
+
note: 'Check if the form/page progressed. If verification widget reappears, use "captcha-grid" again.',
|
|
1586
1827
|
});
|
|
1587
1828
|
}
|
|
1588
1829
|
catch (e) {
|
|
@@ -1938,14 +2179,18 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
1938
2179
|
results.push('Attempting to complete automatically...');
|
|
1939
2180
|
const solveResults = await autoSolveCaptcha(p);
|
|
1940
2181
|
solveResults.forEach(r => results.push(` ${r}`));
|
|
1941
|
-
const needsVision = solveResults.some(r => r.includes('
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
results.push('
|
|
2182
|
+
const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
|
|
2183
|
+
const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
|
|
2184
|
+
if (needsVision) {
|
|
2185
|
+
results.push('');
|
|
2186
|
+
results.push('⚠ Verification widget analysis is above. Follow the VERIFICATION COMPLETION STEPS to complete it, then re-run signup-assist to continue.');
|
|
2187
|
+
}
|
|
2188
|
+
else if (unconfirmed) {
|
|
2189
|
+
results.push('Verification attempted but NOT confirmed — take a screenshot to check the widget passed before relying on submission.');
|
|
1945
2190
|
}
|
|
1946
2191
|
else {
|
|
1947
|
-
|
|
1948
|
-
results.push('
|
|
2192
|
+
await p.waitForTimeout(2000);
|
|
2193
|
+
results.push('Verification confirmed. Continuing form submission...');
|
|
1949
2194
|
}
|
|
1950
2195
|
}
|
|
1951
2196
|
const clicked = await clickField([
|
|
@@ -2094,14 +2339,18 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
2094
2339
|
results.push('Attempting to complete automatically...');
|
|
2095
2340
|
const solveResults = await autoSolveCaptcha(p);
|
|
2096
2341
|
solveResults.forEach(r => results.push(` ${r}`));
|
|
2097
|
-
const needsVision = solveResults.some(r => r.includes('
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
results.push('
|
|
2342
|
+
const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
|
|
2343
|
+
const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
|
|
2344
|
+
if (needsVision) {
|
|
2345
|
+
results.push('');
|
|
2346
|
+
results.push('⚠ Verification widget analysis is above. Follow the VERIFICATION COMPLETION STEPS to complete it, then re-run signin-assist to continue.');
|
|
2347
|
+
}
|
|
2348
|
+
else if (unconfirmed) {
|
|
2349
|
+
results.push('Verification attempted but NOT confirmed — take a screenshot to check the widget passed before relying on login.');
|
|
2101
2350
|
}
|
|
2102
2351
|
else {
|
|
2103
|
-
|
|
2104
|
-
results.push('
|
|
2352
|
+
await p.waitForTimeout(2000);
|
|
2353
|
+
results.push('Verification confirmed. Continuing login...');
|
|
2105
2354
|
}
|
|
2106
2355
|
}
|
|
2107
2356
|
await clickField([
|
|
@@ -2151,8 +2400,8 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
2151
2400
|
const sourceBox = await sourceEl.boundingBox();
|
|
2152
2401
|
if (!sourceBox)
|
|
2153
2402
|
return err(`Source element "${target}" not found or not visible`);
|
|
2154
|
-
const startX = sourceBox.x + sourceBox.width
|
|
2155
|
-
const startY = sourceBox.y + sourceBox.height
|
|
2403
|
+
const startX = sourceBox.x + sourceBox.width * (0.3 + Math.random() * 0.4);
|
|
2404
|
+
const startY = sourceBox.y + sourceBox.height * (0.3 + Math.random() * 0.4);
|
|
2156
2405
|
let endX, endY;
|
|
2157
2406
|
const coords = value.split(',').map(s => parseInt(s.trim()));
|
|
2158
2407
|
if (coords.length === 2 && !isNaN(coords[0]) && !isNaN(coords[1])) {
|
|
@@ -2164,28 +2413,52 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
2164
2413
|
const targetBox = await targetEl.boundingBox();
|
|
2165
2414
|
if (!targetBox)
|
|
2166
2415
|
return err(`Target element "${value}" not found or not visible`);
|
|
2167
|
-
endX = targetBox.x + targetBox.width
|
|
2168
|
-
endY = targetBox.y + targetBox.height
|
|
2416
|
+
endX = targetBox.x + targetBox.width * (0.3 + Math.random() * 0.4);
|
|
2417
|
+
endY = targetBox.y + targetBox.height * (0.3 + Math.random() * 0.4);
|
|
2169
2418
|
}
|
|
2170
|
-
await p
|
|
2171
|
-
await
|
|
2419
|
+
await warmupBehavior(p);
|
|
2420
|
+
await humanMove(startX, startY, p);
|
|
2421
|
+
await p.waitForTimeout(150 + Math.random() * 250);
|
|
2172
2422
|
await p.mouse.down();
|
|
2173
|
-
await p.waitForTimeout(200 + Math.random() *
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
const
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2423
|
+
await p.waitForTimeout(200 + Math.random() * 300);
|
|
2424
|
+
const distance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);
|
|
2425
|
+
const numControls = distance > 200 ? 3 : 2;
|
|
2426
|
+
const dragPoints = [[startX, startY]];
|
|
2427
|
+
for (let i = 0; i < numControls; i++) {
|
|
2428
|
+
const frac = (i + 1) / (numControls + 1);
|
|
2429
|
+
const perpX = -(endY - startY) / distance;
|
|
2430
|
+
const perpY = (endX - startX) / distance;
|
|
2431
|
+
const offset = (Math.random() - 0.5) * Math.min(distance * 0.15, 40);
|
|
2432
|
+
const cx = startX + (endX - startX) * frac + perpX * offset;
|
|
2433
|
+
const cy = startY + (endY - startY) * frac + perpY * offset;
|
|
2434
|
+
dragPoints.push([cx, cy]);
|
|
2435
|
+
}
|
|
2436
|
+
dragPoints.push([endX, endY]);
|
|
2437
|
+
const dragSteps = 25 + Math.floor(Math.random() * 20);
|
|
2438
|
+
for (let step = 0; step <= dragSteps; step++) {
|
|
2439
|
+
const rawT = step / dragSteps;
|
|
2440
|
+
const t = easeInOut(rawT);
|
|
2441
|
+
const [px, py] = bezierPoint(t, dragPoints);
|
|
2442
|
+
const tremor = Math.sin(step * 0.35 + Math.random() * 0.5) * 0.5;
|
|
2443
|
+
const tremorY = Math.cos(step * 0.3 + Math.random() * 0.5) * 0.4;
|
|
2444
|
+
await p.mouse.move(px + tremor, py + tremorY);
|
|
2445
|
+
const speedFactor = 1 - Math.abs(rawT - 0.5) * 2;
|
|
2446
|
+
const delay = 10 + Math.random() * 15 + speedFactor * 8;
|
|
2447
|
+
await p.waitForTimeout(delay);
|
|
2448
|
+
}
|
|
2449
|
+
if (Math.random() > 0.5) {
|
|
2450
|
+
const overX = endX + (Math.random() - 0.5) * 6;
|
|
2451
|
+
const overY = endY + (Math.random() - 0.5) * 6;
|
|
2452
|
+
await p.mouse.move(overX, overY);
|
|
2453
|
+
await p.waitForTimeout(40 + Math.random() * 60);
|
|
2454
|
+
await p.mouse.move(endX, endY);
|
|
2455
|
+
await p.waitForTimeout(30 + Math.random() * 50);
|
|
2456
|
+
}
|
|
2457
|
+
await p.waitForTimeout(80 + Math.random() * 150);
|
|
2458
|
+
await p.mouse.move(endX + (Math.random() - 0.5) * 2, endY - 1 - Math.random());
|
|
2459
|
+
await p.waitForTimeout(30 + Math.random() * 40);
|
|
2187
2460
|
await p.mouse.up();
|
|
2188
|
-
await p.waitForTimeout(
|
|
2461
|
+
await p.waitForTimeout(400 + Math.random() * 300);
|
|
2189
2462
|
const screenshotPath = join(homedir(), '.aurix-drag-result.png');
|
|
2190
2463
|
await p.screenshot({ path: screenshotPath });
|
|
2191
2464
|
return ok(`Dragged "${target}" to (${Math.round(endX)}, ${Math.round(endY)})`, {
|
|
@@ -2203,29 +2476,26 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
2203
2476
|
const p = await ensureBrowser();
|
|
2204
2477
|
if (!target)
|
|
2205
2478
|
return err('hold-click requires a target element');
|
|
2206
|
-
const
|
|
2479
|
+
const baseDuration = Math.min(parseInt(value) || 5000, 12000);
|
|
2480
|
+
const duration = Math.max(2000, baseDuration + Math.floor(Math.random() * 2000) - 1000);
|
|
2207
2481
|
try {
|
|
2208
2482
|
const el = p.locator(target).first();
|
|
2209
2483
|
const box = await el.boundingBox();
|
|
2210
2484
|
if (!box)
|
|
2211
2485
|
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
|
-
|
|
2215
|
-
await p
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
await p.waitForTimeout(80 + Math.random() * 40);
|
|
2223
|
-
}
|
|
2224
|
-
await p.mouse.up();
|
|
2225
|
-
await p.waitForTimeout(500);
|
|
2486
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
|
|
2487
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
|
|
2488
|
+
// Pre-interaction warmup: move mouse around naturally
|
|
2489
|
+
await warmupBehavior(p);
|
|
2490
|
+
// Bezier curve approach to target
|
|
2491
|
+
await humanMove(x, y, p);
|
|
2492
|
+
await p.waitForTimeout(100 + Math.random() * 200);
|
|
2493
|
+
// Human-like hold with breathing movements
|
|
2494
|
+
await humanHold(x, y, duration, p);
|
|
2495
|
+
await p.waitForTimeout(300 + Math.random() * 400);
|
|
2226
2496
|
const screenshotPath = join(homedir(), '.aurix-hold-result.png');
|
|
2227
2497
|
await p.screenshot({ path: screenshotPath });
|
|
2228
|
-
return ok(`Held click on "${target}" for ${duration}ms`, {
|
|
2498
|
+
return ok(`Held click on "${target}" for ${Math.round(duration)}ms (human-like)`, {
|
|
2229
2499
|
position: `(${Math.round(x)}, ${Math.round(y)})`,
|
|
2230
2500
|
screenshot: screenshotPath,
|
|
2231
2501
|
});
|
|
@@ -2235,18 +2505,42 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
|
|
|
2235
2505
|
}
|
|
2236
2506
|
}
|
|
2237
2507
|
default:
|
|
2238
|
-
return `Unknown action: "${action}"
|
|
2508
|
+
return err(`Unknown action: "${action}"`, `Available: navigate, click, fill, type, screenshot, snapshot, text, html, url, title, scroll, back, forward, press-key, select, wait, evaluate, new-tab, switch-tab, close-tab, open-tabs, cookies, upload, signup-assist, signin-assist, set-proxy, set-ui, detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze, drag-to, hold-click, close, status`);
|
|
2239
2509
|
}
|
|
2240
2510
|
}
|
|
2241
2511
|
catch (e) {
|
|
2242
2512
|
const msg = e.message || String(e);
|
|
2243
|
-
if (msg.includes('Timeout')) {
|
|
2244
|
-
return `
|
|
2513
|
+
if (msg.includes('Timeout') || msg.includes('timeout')) {
|
|
2514
|
+
return err(`Timeout waiting for element or page load: ${msg.slice(0, 120)}`, 'Use "wait" to wait for page load, "snapshot" to check current state, or verify the element exists');
|
|
2245
2515
|
}
|
|
2246
2516
|
if (msg.includes('strict mode') || msg.includes('more than one')) {
|
|
2247
|
-
return `Multiple elements matched "${target}"
|
|
2517
|
+
return err(`Multiple elements matched "${target || '(unknown)'}"`, 'Use a more specific selector (CSS #id, [attr]), or .first()/.nth(0)');
|
|
2518
|
+
}
|
|
2519
|
+
if (msg.includes('not visible') || msg.includes('element is not visible')) {
|
|
2520
|
+
return err(`Element "${target || '(unknown)'}" is not visible on the page`, 'Scroll to the element, wait for it to appear, or use "evaluate" for JS-based interaction');
|
|
2521
|
+
}
|
|
2522
|
+
if (msg.includes('detached') || msg.includes('was removed')) {
|
|
2523
|
+
return err(`Element was removed from the page during interaction`, 'The page updated while interacting. Use "snapshot" to get fresh elements and retry');
|
|
2524
|
+
}
|
|
2525
|
+
if (msg.includes('intercepts pointer') || msg.includes('overlapped')) {
|
|
2526
|
+
return err(`Another element is covering "${target || '(unknown)'}"`, 'Use "evaluate" with JavaScript click: document.querySelector(selector).click(), or scroll to reveal the element');
|
|
2527
|
+
}
|
|
2528
|
+
if (msg.includes('frame was detached') || msg.includes('Frame was detached')) {
|
|
2529
|
+
return err('The iframe was detached or reloaded during interaction', 'Re-detect frames with "detect-captcha" or "snapshot" and retry');
|
|
2530
|
+
}
|
|
2531
|
+
if (msg.includes('Navigation') || msg.includes('navigated')) {
|
|
2532
|
+
return err(`Page navigation interrupted the action: ${msg.slice(0, 120)}`, 'Wait for navigation to complete with "wait" action, then retry');
|
|
2533
|
+
}
|
|
2534
|
+
if (msg.includes('closed') || msg.includes('Target closed') || msg.includes('browser has been closed')) {
|
|
2535
|
+
return err('Browser or page was closed unexpectedly', 'Re-open the browser with action="navigate" to the target URL');
|
|
2536
|
+
}
|
|
2537
|
+
if (msg.includes('net::') || msg.includes('ERR_')) {
|
|
2538
|
+
return err(`Network error: ${msg.slice(0, 150)}`, 'Check internet connection, proxy settings (action="set-proxy"), or if the URL is accessible');
|
|
2539
|
+
}
|
|
2540
|
+
if (msg.includes('Execution context was destroyed')) {
|
|
2541
|
+
return err('Page JavaScript context was destroyed (page navigated or reloaded)', 'Use "wait" to let the page settle, then "snapshot" to check state before retrying');
|
|
2248
2542
|
}
|
|
2249
|
-
return `Browser error: ${msg}
|
|
2543
|
+
return err(`Browser error: ${msg.slice(0, 200)}`, 'Use "snapshot" to check current page state, or "screenshot" to see what the page looks like');
|
|
2250
2544
|
}
|
|
2251
2545
|
},
|
|
2252
2546
|
};
|