aurix-ai 2.5.7 → 2.5.9

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.
@@ -1019,44 +1019,47 @@ async function analyzeImageChallenge(page, frame, provider) {
1019
1019
  }
1020
1020
  export const browserTool = {
1021
1021
  name: 'browser',
1022
- description: `Operate a persistent Chromium browser for the user. Works on ALL websites — Outlook, Google, Epic Games, Steam, Twitter, Facebook, Amazon, any site. Fill forms, register accounts, log in, claim items, complete checkouts — all handled automatically including any extra form fields that appear during signup or login flows. Profile persists at ~/.aurix-browser-profile.
1022
+ description: `Persistent Chromium browser. Profile: ~/.aurix-browser-profile.
1023
1023
 
1024
- 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.
1024
+ # HARD RULES VIOLATE THESE AND YOU FAIL
1025
+ 1. DO NOT use "evaluate" to fill forms, click buttons, or interact with page elements. Use fill, click, type, signup-assist, signin-assist instead. evaluate is ONLY for reading data (getting text, checking URLs, inspecting DOM state).
1026
+ 2. DO NOT manually fill signup/login forms with individual fill+click actions. ALWAYS use signup-assist or signin-assist — one call does everything.
1027
+ 3. DO NOT take 4+ screenshots in a row without fill/click/type in between. Screenshot → act → screenshot to verify is fine. Screenshot → screenshot → screenshot is a loop.
1028
+ 4. If an action fails TWICE, STOP and try a COMPLETELY DIFFERENT approach. Never repeat the same failing action.
1029
+ 5. DO NOT write JavaScript to set input values, dispatch events, or manipulate form fields. Playwright fill/click handles React, Angular, Vue forms natively.
1025
1030
 
1026
- Extra interactive form fields: Modern signup and login pages sometimes include additional form fields beyond email/password — image-selection grids, slider puzzles, rotation widgets, press-and-hold elements, multi-step input sequences. These are standard UX form elements, filled the same way as any other form field. Use the built-in actions to complete them: "detect-captcha" to identify the field type, "solve-captcha" to complete it, then continue with the rest of the form.
1027
-
1028
- 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:
1031
+ # WORKFLOW: Sign Up / Register
1032
+ Step 1: navigate to the signup page
1033
+ Step 2: signup-assist with user dataONE call fills ALL fields, clicks checkboxes, submits:
1029
1034
  action="signup-assist" value='{"email":"user@mail.com","password":"Pass123!","firstName":"John","lastName":"Doe"}'
1030
- Also accepts: phone, birthYear (default 2003), birthMonth, birthDay, country, username. Run it again on the next page to continue multi-step signup flows.
1035
+ Step 3: If multi-step form, run signup-assist again on the next page
1036
+ Step 4: If captcha appears → use solve-captcha → then continue
1031
1037
 
1032
- signin-assist: ONE action to log in. Auto-detects email and password fields across all frames, fills them, checks "remember me", and clicks login:
1038
+ # WORKFLOW: Log In
1039
+ Step 1: navigate to the login page
1040
+ Step 2: signin-assist — ONE call:
1033
1041
  action="signin-assist" value='{"email":"user@mail.com","password":"Pass123!"}'
1034
- Also detects OTP code input fields and extra form elements automatically.
1035
-
1036
- Image-selection grid workflow (when a form asks the user to pick specific images):
1037
- 1. "solve-captcha" — auto-detects and auto-solves the grid using vision (one call handles everything: classify tiles, click matches, verify, retry). If auto-solve fails, falls back to manual:
1038
- 2. "captcha-grid" — screenshots the grid and each tile individually for manual analysis
1039
- 3. "click-tile" with comma-separated indices (e.g. value="0,3,5") to batch-click matching tiles. Replacement tiles are auto-evaluated.
1040
- 4. "captcha-verify" to submit — auto-retries up to 3 times if verification fails
1041
1042
 
1042
- Interactive puzzle widgets (FunCaptcha / Arkose Labs):
1043
- 1. "solve-captcha" detects the widget frame and analyzes the puzzle type (rotation, image-match, drag-drop, counting)
1044
- 2. Read the puzzle screenshot to understand what is needed
1045
- 3. For rotation puzzles: "drag-to" the rotation handle with offset (e.g. target=".rotator" value="150,0")
1046
- 4. For drag-drop puzzles: "drag-to" from source to target (e.g. target=".piece" value=".slot")
1047
- 5. For image match: "click" on matching elements
1048
- 6. Use "hold-click" for press-and-hold elements (target=element, value=duration in ms)
1043
+ # WORKFLOW: Individual Field Fill (only if signup-assist didn't cover it)
1044
+ Step 1: fill target="selector" value="text" Playwright handles React/Angular/Vue inputs natively
1045
+ Step 2: If fill fails try type (simulates keystrokes, works on stubborn React inputs)
1046
+ Step 3: If type fails click the input first, then type again
1047
+ Step 4: If ALL 3 fail take a snapshot to find a better selector, then retry
1049
1048
 
1050
- Slider widgets (GeeTest, MTCaptcha):
1051
- 1. "solve-captcha" auto-detects slider type, screenshots the puzzle, and calculates the exact gap offset from the DOM
1052
- 2. The response includes RECOMMENDED OFFSET use that exact value in drag-to
1053
- 3. If gap was not detected, use "slider-analyze" to re-scan and get the offset
1054
- 4. NEVER guess the offset — always use the value from solve-captcha or slider-analyze
1055
- 5. Then: drag-to target=".geetest_slider_button" value="<offset>,0"
1049
+ # Captcha Auto-Solve (all types)
1050
+ - solve-captcha: ONE call auto-solves image grids, sliders, FunCaptcha. Use this FIRST.
1051
+ - If solve-captcha fails after 2 attempts tell the user, do NOT keep retrying.
1056
1052
 
1057
- Target resolution: CSS selectors (#id, .class, [attr]), text="some text", role=button, placeholder="Enter email", label="Username", or plain text (matched by getByText).
1053
+ # Action Reference
1054
+ Forms: signup-assist, signin-assist, fill, type, click, select, press-key, upload
1055
+ Navigation: navigate, back, forward, scroll, new-tab, switch-tab, close-tab, open-tabs
1056
+ Read: screenshot, snapshot, text, html, url, title, cookies
1057
+ Advanced: evaluate (READ ONLY), drag-to, hold-click, wait
1058
+ Captcha: detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze
1059
+ Config: set-proxy, set-ui, status, close
1058
1060
 
1059
- The browser profile persists at ~/.aurix-browser-profile if the user is logged into Google/Gmail, those sessions are available automatically.`,
1061
+ Target: CSS (#id, .class, [attr]), text="...", role=button, placeholder="...", label="...", or plain text.
1062
+ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass" per session.`,
1060
1063
  parameters: {
1061
1064
  type: 'object',
1062
1065
  properties: {
@@ -1232,9 +1235,19 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1232
1235
  const msg = e.message || String(e);
1233
1236
  if (msg.includes('Timeout'))
1234
1237
  return err(`Input "${target}" not found within timeout`, 'Use "snapshot" to see available form fields');
1235
- if (msg.includes('not an input'))
1236
- return err(`"${target}" is not a fillable input element`, 'Use "type" for non-input elements, or find the correct input selector');
1237
- return err(`Fill failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
1238
+ try {
1239
+ const locator = await resolveLocator(p, target);
1240
+ await locator.first().click({ timeout: 3000 });
1241
+ await locator.first().pressSequentially(value, { delay: 30, timeout: 10000 });
1242
+ const ss = await autoScreenshot(p, 'fill-fallback-type');
1243
+ return ok(`Filled "${target}" (via keystroke fallback)`, {
1244
+ value: value.length > 50 ? value.slice(0, 50) + '...' : value,
1245
+ screenshot: ss,
1246
+ });
1247
+ }
1248
+ catch (e2) {
1249
+ return err(`Fill failed on "${target}": ${msg.slice(0, 150)}`, 'Use "type" action directly, or "snapshot" to find a better selector');
1250
+ }
1238
1251
  }
1239
1252
  }
1240
1253
  case 'type': {
@@ -2598,6 +2611,27 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2598
2611
  results.push('=== SIGNUP ASSIST ===');
2599
2612
  results.push(`Provided: ${Object.keys(data).join(', ')}`);
2600
2613
  results.push('');
2614
+ const cookieSelectors = [
2615
+ 'button:has-text("Accept All")', 'button:has-text("Accept all")', 'button:has-text("accept all")',
2616
+ 'button:has-text("Accept")', 'button:has-text("Accept Cookies")',
2617
+ 'button:has-text("I agree")', 'button:has-text("Got it")', 'button:has-text("OK")',
2618
+ 'button:has-text("Allow All")', 'button:has-text("Allow all")',
2619
+ '[id*="cookie"] button', '[class*="cookie"] button',
2620
+ '[id*="consent"] button', '[class*="consent"] button',
2621
+ '.cc-accept', '.cookie-accept', '#accept-cookies',
2622
+ ];
2623
+ for (const sel of cookieSelectors) {
2624
+ try {
2625
+ const btn = p.locator(sel).first();
2626
+ if (await btn.count() > 0 && await btn.isVisible()) {
2627
+ await btn.click({ timeout: 2000 });
2628
+ results.push(` ✓ Dismissed cookie banner: ${sel}`);
2629
+ await p.waitForTimeout(500);
2630
+ break;
2631
+ }
2632
+ }
2633
+ catch { }
2634
+ }
2601
2635
  const allFrames = [p, ...p.frames().filter(f => f !== p.mainFrame())];
2602
2636
  let activeFrame = p;
2603
2637
  for (const frame of allFrames) {
@@ -2607,6 +2641,42 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2607
2641
  break;
2608
2642
  }
2609
2643
  }
2644
+ const hasEmailField = await activeFrame.locator('input[type="email"]:visible, input[name*="email" i]:visible, input[autocomplete="email"]:visible').count() > 0;
2645
+ const hasPasswordField = await activeFrame.locator('input[type="password"]:visible').count() > 0;
2646
+ if (!hasEmailField && !hasPasswordField) {
2647
+ const ctaSelectors = [
2648
+ 'button:has-text("Sign Up With Email")', 'button:has-text("sign up with email")',
2649
+ 'a:has-text("Sign Up With Email")', 'a:has-text("sign up with email")',
2650
+ 'button:has-text("Sign up with email")',
2651
+ 'button:has-text("Create Account")', 'button:has-text("create account")',
2652
+ 'button:has-text("Sign Up")', 'button:has-text("sign up")',
2653
+ 'button:has-text("Register")', 'button:has-text("register")',
2654
+ 'button:has-text("Get Started")', 'button:has-text("get started")',
2655
+ 'button:has-text("Continue with Email")', 'button:has-text("continue with email")',
2656
+ 'button:has-text("Use Email")', 'button:has-text("use email")',
2657
+ 'a:has-text("Sign Up")', 'a:has-text("Register")',
2658
+ '[data-testid*="signup"]', '[data-testid*="email"]',
2659
+ ];
2660
+ for (const sel of ctaSelectors) {
2661
+ try {
2662
+ const btn = activeFrame.locator(sel).first();
2663
+ if (await btn.count() > 0 && await btn.isVisible()) {
2664
+ await btn.click({ timeout: 3000 });
2665
+ results.push(` ✓ Clicked CTA: ${sel}`);
2666
+ await p.waitForTimeout(1500);
2667
+ break;
2668
+ }
2669
+ }
2670
+ catch { }
2671
+ }
2672
+ for (const frame of [p, ...p.frames().filter(f => f !== p.mainFrame())]) {
2673
+ const inputs = await frame.locator('input:visible, select:visible, textarea:visible').count();
2674
+ if (inputs > 0) {
2675
+ activeFrame = frame;
2676
+ break;
2677
+ }
2678
+ }
2679
+ }
2610
2680
  results.push(`Active frame: ${activeFrame === p ? 'main page' : 'iframe'} (${await activeFrame.locator('input:visible, select:visible, textarea:visible').count()} fields)`);
2611
2681
  const fillField = async (selectors, val, label) => {
2612
2682
  for (const sel of selectors) {
@@ -2618,7 +2688,13 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2618
2688
  results.push(` ✓ ${label}: already filled`);
2619
2689
  return true;
2620
2690
  }
2621
- await loc.fill(val, { timeout: 3000 });
2691
+ try {
2692
+ await loc.fill(val, { timeout: 1500 });
2693
+ }
2694
+ catch {
2695
+ await loc.click({ timeout: 1500 });
2696
+ await loc.pressSequentially(val, { delay: 30, timeout: 5000 });
2697
+ }
2622
2698
  results.push(` ✓ ${label}: filled`);
2623
2699
  return true;
2624
2700
  }
@@ -2637,7 +2713,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2637
2713
  results.push(` ✓ ${label}: already checked`);
2638
2714
  return true;
2639
2715
  }
2640
- await loc.click({ timeout: 3000 });
2716
+ await loc.click({ timeout: 1500 });
2641
2717
  results.push(` ✓ ${label}: clicked`);
2642
2718
  return true;
2643
2719
  }
@@ -2701,102 +2777,73 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2701
2777
  results.push('--- Filling fields ---');
2702
2778
  if (data.email) {
2703
2779
  await fillField([
2704
- 'input[type="email"]',
2705
- 'input[name*="email" i]', 'input[name*="Email"]',
2706
- 'input[name*="username" i]', 'input[name*="MemberName"]',
2707
- 'input[id*="email" i]', 'input[id*="username" i]',
2708
- 'input[placeholder*="email" i]', 'input[placeholder*="Email"]',
2709
- 'input[autocomplete="email"]', 'input[autocomplete="username"]',
2710
- 'input[name="loginfmt"]',
2780
+ 'input[type="email"]', 'input[name*="email" i]', 'input[id*="email" i]',
2781
+ 'input[autocomplete="email"]', 'input[placeholder*="email" i]',
2711
2782
  ], data.email, 'Email');
2712
2783
  }
2713
2784
  if (data.password) {
2714
2785
  await fillField([
2715
- 'input[type="password"]',
2716
- 'input[name*="password" i]', 'input[name*="Password"]', 'input[name*="Passwd"]',
2717
- 'input[id*="password" i]', 'input[name*="pass" i]',
2718
- 'input[autocomplete="new-password"]', 'input[autocomplete="current-password"]',
2786
+ 'input[type="password"]', 'input[name*="password" i]', 'input[id*="password" i]',
2787
+ 'input[autocomplete="new-password"]',
2719
2788
  ], data.password, 'Password');
2720
2789
  }
2721
2790
  if (data.firstName) {
2722
2791
  await fillField([
2723
- 'input[name*="firstName" i]', 'input[name*="FirstName"]', 'input[name*="fname" i]',
2724
- 'input[id*="firstName" i]', 'input[id*="fname" i]',
2725
- 'input[autocomplete="given-name"]',
2726
- 'input[placeholder*="first name" i]', 'input[placeholder*="First"]',
2727
- 'input[name="NameInput"]',
2792
+ 'input[name*="first" i]', 'input[id*="first" i]',
2793
+ 'input[autocomplete="given-name"]', 'input[placeholder*="first" i]',
2728
2794
  ], data.firstName, 'First name');
2729
2795
  }
2730
2796
  if (data.lastName) {
2731
2797
  await fillField([
2732
- 'input[name*="lastName" i]', 'input[name*="LastName"]', 'input[name*="lname" i]',
2733
- 'input[id*="lastName" i]', 'input[id*="lname" i]',
2734
- 'input[autocomplete="family-name"]',
2735
- 'input[placeholder*="last name" i]', 'input[placeholder*="Last"]',
2736
- 'input[name="LastName"]',
2798
+ 'input[name*="last" i]', 'input[id*="last" i]',
2799
+ 'input[autocomplete="family-name"]', 'input[placeholder*="last" i]',
2737
2800
  ], data.lastName, 'Last name');
2738
2801
  }
2739
2802
  if (data.firstName && !data.lastName) {
2740
2803
  await fillField([
2741
- 'input[name*="name" i]', 'input[id*="name" i]',
2742
- 'input[autocomplete="name"]',
2804
+ 'input[name*="name" i]', 'input[id*="name" i]', 'input[autocomplete="name"]',
2743
2805
  ], data.firstName + ' User', 'Full name');
2744
2806
  }
2745
2807
  if (data.phone) {
2746
2808
  await fillField([
2747
- 'input[type="tel"]', 'input[name*="phone" i]', 'input[name*="Phone"]',
2748
- 'input[id*="phone" i]', 'input[autocomplete="tel"]',
2749
- 'input[placeholder*="phone" i]',
2809
+ 'input[type="tel"]', 'input[name*="phone" i]', 'input[autocomplete="tel"]',
2750
2810
  ], data.phone, 'Phone');
2751
2811
  }
2752
- const birthYear = data.birthYear || '2003';
2753
- const birthMonth = data.birthMonth || 'January';
2754
- const birthDay = data.birthDay || '15';
2755
- await selectDropdown([
2756
- 'select[id*="BirthYear"]', 'select[name*="birthYear" i]', 'select[id*="year" i]',
2757
- 'select[name*="year" i]', 'select[aria-label*="year" i]', 'select[aria-label*="Birth"]',
2758
- ], birthYear, 'Birth year');
2759
- await selectDropdown([
2760
- 'select[id*="BirthMonth"]', 'select[name*="birthMonth" i]', 'select[id*="month" i]',
2761
- 'select[name*="month" i]', 'select[aria-label*="month" i]',
2762
- ], birthMonth, 'Birth month');
2763
- await selectDropdown([
2764
- 'select[id*="BirthDay"]', 'select[name*="birthDay" i]', 'select[id*="day" i]',
2765
- 'select[name*="day" i]', 'select[aria-label*="day" i]',
2766
- ], birthDay, 'Birth day');
2767
- await selectDropdown([
2768
- 'select[id*="Country"]', 'select[name*="country" i]',
2769
- 'select[aria-label*="country" i]', 'select[name*="Country"]',
2770
- ], data.country || 'United States', 'Country');
2771
- await fillField([
2772
- 'input[name*="username" i]', 'input[id*="username" i]',
2773
- 'input[placeholder*="username" i]', 'input[name*="Username"]',
2774
- ], data.username || (data.email ? data.email.split('@')[0] + Math.floor(Math.random() * 999) : 'user' + Math.floor(Math.random() * 9999)), 'Username');
2812
+ if (data.birthYear || data.birthMonth || data.birthDay) {
2813
+ const birthYear = data.birthYear || '2003';
2814
+ const birthMonth = data.birthMonth || 'January';
2815
+ const birthDay = data.birthDay || '15';
2816
+ await selectDropdown([
2817
+ 'select[id*="year" i]', 'select[name*="year" i]',
2818
+ ], birthYear, 'Birth year');
2819
+ await selectDropdown([
2820
+ 'select[id*="month" i]', 'select[name*="month" i]',
2821
+ ], birthMonth, 'Birth month');
2822
+ await selectDropdown([
2823
+ 'select[id*="day" i]', 'select[name*="day" i]',
2824
+ ], birthDay, 'Birth day');
2825
+ }
2826
+ if (data.country) {
2827
+ await selectDropdown([
2828
+ 'select[name*="country" i]', 'select[id*="country" i]',
2829
+ ], data.country, 'Country');
2830
+ }
2831
+ if (data.username) {
2832
+ await fillField([
2833
+ 'input[name*="username" i]', 'input[id*="username" i]',
2834
+ ], data.username, 'Username');
2835
+ }
2775
2836
  await clickField([
2776
2837
  'input[type="checkbox"][name*="agree" i]',
2777
- 'input[type="checkbox"][name*="tos" i]',
2778
2838
  'input[type="checkbox"][name*="terms" i]',
2779
2839
  'input[type="checkbox"][name*="consent" i]',
2780
- 'input[type="checkbox"][name*="privacy" i]',
2781
- 'input[type="checkbox"][name*="policy" i]',
2782
2840
  'input[type="checkbox"][id*="agree" i]',
2783
2841
  'input[type="checkbox"][id*="terms" i]',
2784
- 'input[type="checkbox"][id*="consent" i]',
2785
- 'input[type="checkbox"][id*="privacy" i]',
2786
- 'input[type="checkbox"][aria-label*="agree" i]',
2787
- 'input[type="checkbox"][aria-label*="terms" i]',
2788
- 'input[type="checkbox"][aria-label*="consent" i]',
2789
- 'input[type="checkbox"][aria-label*="accept" i]',
2790
2842
  'label:has-text("agree") input[type="checkbox"]',
2791
2843
  'label:has-text("terms") input[type="checkbox"]',
2792
2844
  'label:has-text("accept") input[type="checkbox"]',
2793
- 'label:has-text("consent") input[type="checkbox"]',
2794
- 'label:has-text("privacy") input[type="checkbox"]',
2795
2845
  'label:has-text("I agree") input[type="checkbox"]',
2796
- 'label:has-text("I accept") input[type="checkbox"]',
2797
2846
  '[role="checkbox"][aria-checked="false"]',
2798
- 'div:has-text("I agree"):not(:has(div:has-text("I agree")))',
2799
- 'span:has-text("I agree"):not(:has(span:has-text("I agree")))',
2800
2847
  ], 'Terms/Agreement checkbox');
2801
2848
  await p.waitForTimeout(500);
2802
2849
  const hasCaptcha = p.frames().some(f => {
@@ -2809,7 +2856,16 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2809
2856
  results.push('');
2810
2857
  results.push('--- Verification step detected ---');
2811
2858
  results.push('Attempting to complete automatically...');
2812
- const solveResults = await autoSolveCaptcha(p);
2859
+ let solveResults;
2860
+ try {
2861
+ solveResults = await Promise.race([
2862
+ autoSolveCaptcha(p),
2863
+ new Promise((_, rej) => setTimeout(() => rej(new Error('auto-solve timed out (30s)')), 30000)),
2864
+ ]);
2865
+ }
2866
+ catch (e) {
2867
+ solveResults = [`Auto-solve: ${e.message}`];
2868
+ }
2813
2869
  solveResults.forEach(r => results.push(` ${r}`));
2814
2870
  const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
2815
2871
  const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
@@ -2826,17 +2882,11 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2826
2882
  }
2827
2883
  }
2828
2884
  const clicked = await clickField([
2829
- 'button[type="submit"]',
2830
- 'input[type="submit"]',
2831
- 'button:has-text("Next")', 'button:has-text("next")',
2832
- 'button:has-text("Continue")', 'button:has-text("continue")',
2833
- 'button:has-text("Submit")', 'button:has-text("submit")',
2834
- 'button:has-text("Create")', 'button:has-text("create")',
2835
- 'button:has-text("Sign up")', 'button:has-text("sign up")',
2836
- 'button:has-text("Register")', 'button:has-text("register")',
2837
- 'button:has-text("Accept")', 'button:has-text("accept")',
2838
- '#iSignupAction', '#signup-button', '#submit-btn',
2839
- 'button.fui-Button[type="button"]:visible',
2885
+ 'button[type="submit"]', 'input[type="submit"]',
2886
+ 'button:has-text("Sign up")', 'button:has-text("Register")',
2887
+ 'button:has-text("Next")', 'button:has-text("Continue")',
2888
+ 'button:has-text("Submit")', 'button:has-text("Create")',
2889
+ '#signup-button', '#submit-btn',
2840
2890
  ], 'Submit/Next button');
2841
2891
  await p.waitForTimeout(2000);
2842
2892
  results.push('');
@@ -2881,6 +2931,27 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2881
2931
  }
2882
2932
  const results = [];
2883
2933
  results.push('=== SIGNIN ASSIST ===');
2934
+ const cookieSelectors = [
2935
+ 'button:has-text("Accept All")', 'button:has-text("Accept all")', 'button:has-text("accept all")',
2936
+ 'button:has-text("Accept")', 'button:has-text("Accept Cookies")',
2937
+ 'button:has-text("I agree")', 'button:has-text("Got it")', 'button:has-text("OK")',
2938
+ 'button:has-text("Allow All")', 'button:has-text("Allow all")',
2939
+ '[id*="cookie"] button', '[class*="cookie"] button',
2940
+ '[id*="consent"] button', '[class*="consent"] button',
2941
+ '.cc-accept', '.cookie-accept', '#accept-cookies',
2942
+ ];
2943
+ for (const sel of cookieSelectors) {
2944
+ try {
2945
+ const btn = p.locator(sel).first();
2946
+ if (await btn.count() > 0 && await btn.isVisible()) {
2947
+ await btn.click({ timeout: 2000 });
2948
+ results.push(` ✓ Dismissed cookie banner: ${sel}`);
2949
+ await p.waitForTimeout(500);
2950
+ break;
2951
+ }
2952
+ }
2953
+ catch { }
2954
+ }
2884
2955
  const allFrames = [p, ...p.frames().filter(f => f !== p.mainFrame())];
2885
2956
  let activeFrame = p;
2886
2957
  for (const frame of allFrames) {
@@ -2901,7 +2972,13 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2901
2972
  results.push(` ✓ ${label}: already filled`);
2902
2973
  return true;
2903
2974
  }
2904
- await loc.fill(val, { timeout: 3000 });
2975
+ try {
2976
+ await loc.fill(val, { timeout: 1500 });
2977
+ }
2978
+ catch {
2979
+ await loc.click({ timeout: 1500 });
2980
+ await loc.pressSequentially(val, { delay: 30, timeout: 5000 });
2981
+ }
2905
2982
  results.push(` ✓ ${label}: filled`);
2906
2983
  return true;
2907
2984
  }
@@ -2920,7 +2997,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2920
2997
  results.push(` ✓ ${label}: already checked`);
2921
2998
  return true;
2922
2999
  }
2923
- await loc.click({ timeout: 3000 });
3000
+ await loc.click({ timeout: 1500 });
2924
3001
  results.push(` ✓ ${label}: clicked`);
2925
3002
  return true;
2926
3003
  }
@@ -2969,7 +3046,16 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2969
3046
  results.push('');
2970
3047
  results.push('--- Verification step detected ---');
2971
3048
  results.push('Attempting to complete automatically...');
2972
- const solveResults = await autoSolveCaptcha(p);
3049
+ let solveResults;
3050
+ try {
3051
+ solveResults = await Promise.race([
3052
+ autoSolveCaptcha(p),
3053
+ new Promise((_, rej) => setTimeout(() => rej(new Error('auto-solve timed out (30s)')), 30000)),
3054
+ ]);
3055
+ }
3056
+ catch (e) {
3057
+ solveResults = [`Auto-solve: ${e.message}`];
3058
+ }
2973
3059
  solveResults.forEach(r => results.push(` ${r}`));
2974
3060
  const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
2975
3061
  const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));