surfagent 1.0.6 → 1.0.7

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/dist/api/act.d.ts CHANGED
@@ -23,6 +23,7 @@ export interface ClickRequest {
23
23
  tab: string;
24
24
  selector?: string;
25
25
  text?: string;
26
+ waitAfter?: number;
26
27
  }
27
28
  export declare function clickElement(request: ClickRequest, options: {
28
29
  port?: number;
@@ -70,6 +71,10 @@ export declare function readPage(tabPattern: string, options: {
70
71
  host?: string;
71
72
  selector?: string;
72
73
  }): Promise<any>;
74
+ export declare function dismissOverlays(tabPattern: string, options: {
75
+ port?: number;
76
+ host?: string;
77
+ }): Promise<any>;
73
78
  export interface CaptchaRequest {
74
79
  tab: string;
75
80
  action: 'detect' | 'read' | 'next' | 'prev' | 'submit' | 'audio' | 'restart';
package/dist/api/act.js CHANGED
@@ -135,9 +135,9 @@ export async function clickElement(request, options) {
135
135
  }
136
136
  if (!el && text) {
137
137
  const lower = text.toLowerCase();
138
- const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [onclick]');
138
+ const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [role="option"], [role="menuitem"], [role="listitem"], [role="tab"], [role="link"], li[aria-label], [onclick]');
139
139
  for (const candidate of all) {
140
- const t = (candidate.innerText || candidate.textContent || candidate.value || '').trim();
140
+ const t = (candidate.innerText || candidate.textContent || candidate.value || candidate.getAttribute('aria-label') || '').trim();
141
141
  if (t.toLowerCase().includes(lower)) { el = candidate; break; }
142
142
  }
143
143
  }
@@ -159,6 +159,10 @@ export async function clickElement(request, options) {
159
159
  `,
160
160
  returnByValue: true
161
161
  });
162
+ // Wait after click if requested (for page to settle after navigation/SPA route change)
163
+ if (request.waitAfter && request.waitAfter > 0) {
164
+ await new Promise(resolve => setTimeout(resolve, Math.min(request.waitAfter, 10000)));
165
+ }
162
166
  await client.close();
163
167
  return result.result.value;
164
168
  }
@@ -357,6 +361,67 @@ export async function readPage(tabPattern, options) {
357
361
  throw error;
358
362
  }
359
363
  }
364
+ const DISMISS_OVERLAYS_SCRIPT = `
365
+ (function() {
366
+ const dismissed = [];
367
+
368
+ // Common cookie consent button patterns (multi-language)
369
+ const consentPatterns = [
370
+ 'reject all', 'reject', 'decline', 'deny',
371
+ 'accept all', 'accept', 'godta alle', 'godta',
372
+ 'alle ablehnen', 'ablehnen', 'tout refuser', 'refuser',
373
+ 'rechazar todo', 'rechazar', 'rifiuta tutto', 'rifiuta',
374
+ 'bare nødvendige', 'only necessary', 'nur notwendige',
375
+ 'manage preferences', 'cookie settings',
376
+ ];
377
+
378
+ // Try cookie consent buttons
379
+ for (const btn of document.querySelectorAll('button, a[role="button"]')) {
380
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
381
+ if (text.length > 50 || text.length < 2) continue;
382
+ for (const pattern of consentPatterns) {
383
+ if (text === pattern || text.startsWith(pattern)) {
384
+ btn.click();
385
+ dismissed.push({ type: 'cookie', text: text.substring(0, 40) });
386
+ break;
387
+ }
388
+ }
389
+ if (dismissed.length) break;
390
+ }
391
+
392
+ // Try closing modal dialogs (X button, close button, dismiss)
393
+ if (!dismissed.length) {
394
+ for (const btn of document.querySelectorAll('[aria-label*="Close" i], [aria-label*="Dismiss" i], [aria-label*="Lukk" i], [aria-label*="Schließen" i], [aria-label*="Fermer" i]')) {
395
+ const dialog = btn.closest('[role="dialog"], [role="alertdialog"], .modal, [data-overlay]');
396
+ if (dialog) {
397
+ btn.click();
398
+ dismissed.push({ type: 'dialog', text: btn.getAttribute('aria-label') || 'close' });
399
+ break;
400
+ }
401
+ }
402
+ }
403
+
404
+ return { dismissed, count: dismissed.length };
405
+ })()
406
+ `;
407
+ export async function dismissOverlays(tabPattern, options) {
408
+ const port = options.port || 9222;
409
+ const host = options.host || 'localhost';
410
+ const tab = await resolveTab(tabPattern, port, host);
411
+ const client = await connectToTab(tab.id, port, host);
412
+ try {
413
+ const r = await client.Runtime.evaluate({
414
+ expression: DISMISS_OVERLAYS_SCRIPT,
415
+ returnByValue: true
416
+ });
417
+ await client.close();
418
+ return r.result.value;
419
+ }
420
+ catch (error) {
421
+ await client.close();
422
+ throw error;
423
+ }
424
+ }
360
425
  const CAPTCHA_DETECT_SCRIPT = `
361
426
  (function() {
362
427
  // Find captcha iframes on the page
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import http from 'node:http';
3
3
  import { reconUrl, reconTab } from './recon.js';
4
- import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract } from './act.js';
4
+ import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract, dismissOverlays } from './act.js';
5
5
  import { getAllTabs } from '../chrome/tabs.js';
6
6
  const PORT = parseInt(process.env.API_PORT || '3456', 10);
7
7
  const CDP_PORT = parseInt(process.env.CDP_PORT || '9222', 10);
@@ -90,6 +90,15 @@ const server = http.createServer(async (req, res) => {
90
90
  const result = await scrollPage(body, { port: CDP_PORT, host: CDP_HOST });
91
91
  return json(res, 200, result);
92
92
  }
93
+ // POST /dismiss — dismiss cookie banners, modals, overlays
94
+ if (path === '/dismiss' && req.method === 'POST') {
95
+ const body = parseBody(await readBody(req));
96
+ if (!body.tab) {
97
+ return json(res, 400, { error: 'Provide "tab"' });
98
+ }
99
+ const result = await dismissOverlays(body.tab, { port: CDP_PORT, host: CDP_HOST });
100
+ return json(res, 200, result);
101
+ }
93
102
  // POST /captcha — detect and interact with captchas
94
103
  if (path === '/captcha' && req.method === 'POST') {
95
104
  const body = parseBody(await readBody(req));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surfagent",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Browser automation API for AI agents — structured page recon, form filling, clicking, and navigation via Chrome CDP",
5
5
  "keywords": [
6
6
  "ai-agent",
package/src/api/act.ts CHANGED
@@ -148,6 +148,7 @@ export interface ClickRequest {
148
148
  tab: string;
149
149
  selector?: string;
150
150
  text?: string;
151
+ waitAfter?: number; // ms to wait after click for page to settle
151
152
  }
152
153
 
153
154
  export async function clickElement(
@@ -173,9 +174,9 @@ export async function clickElement(
173
174
  }
174
175
  if (!el && text) {
175
176
  const lower = text.toLowerCase();
176
- const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [onclick]');
177
+ const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [role="option"], [role="menuitem"], [role="listitem"], [role="tab"], [role="link"], li[aria-label], [onclick]');
177
178
  for (const candidate of all) {
178
- const t = (candidate.innerText || candidate.textContent || candidate.value || '').trim();
179
+ const t = (candidate.innerText || candidate.textContent || candidate.value || candidate.getAttribute('aria-label') || '').trim();
179
180
  if (t.toLowerCase().includes(lower)) { el = candidate; break; }
180
181
  }
181
182
  }
@@ -198,6 +199,11 @@ export async function clickElement(
198
199
  returnByValue: true
199
200
  });
200
201
 
202
+ // Wait after click if requested (for page to settle after navigation/SPA route change)
203
+ if (request.waitAfter && request.waitAfter > 0) {
204
+ await new Promise<void>(resolve => setTimeout(resolve, Math.min(request.waitAfter!, 10000)));
205
+ }
206
+
201
207
  await client.close();
202
208
  return result.result.value as any;
203
209
  } catch (error) {
@@ -434,6 +440,73 @@ export async function readPage(
434
440
  }
435
441
  }
436
442
 
443
+ const DISMISS_OVERLAYS_SCRIPT = `
444
+ (function() {
445
+ const dismissed = [];
446
+
447
+ // Common cookie consent button patterns (multi-language)
448
+ const consentPatterns = [
449
+ 'reject all', 'reject', 'decline', 'deny',
450
+ 'accept all', 'accept', 'godta alle', 'godta',
451
+ 'alle ablehnen', 'ablehnen', 'tout refuser', 'refuser',
452
+ 'rechazar todo', 'rechazar', 'rifiuta tutto', 'rifiuta',
453
+ 'bare nødvendige', 'only necessary', 'nur notwendige',
454
+ 'manage preferences', 'cookie settings',
455
+ ];
456
+
457
+ // Try cookie consent buttons
458
+ for (const btn of document.querySelectorAll('button, a[role="button"]')) {
459
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
460
+ if (text.length > 50 || text.length < 2) continue;
461
+ for (const pattern of consentPatterns) {
462
+ if (text === pattern || text.startsWith(pattern)) {
463
+ btn.click();
464
+ dismissed.push({ type: 'cookie', text: text.substring(0, 40) });
465
+ break;
466
+ }
467
+ }
468
+ if (dismissed.length) break;
469
+ }
470
+
471
+ // Try closing modal dialogs (X button, close button, dismiss)
472
+ if (!dismissed.length) {
473
+ for (const btn of document.querySelectorAll('[aria-label*="Close" i], [aria-label*="Dismiss" i], [aria-label*="Lukk" i], [aria-label*="Schließen" i], [aria-label*="Fermer" i]')) {
474
+ const dialog = btn.closest('[role="dialog"], [role="alertdialog"], .modal, [data-overlay]');
475
+ if (dialog) {
476
+ btn.click();
477
+ dismissed.push({ type: 'dialog', text: btn.getAttribute('aria-label') || 'close' });
478
+ break;
479
+ }
480
+ }
481
+ }
482
+
483
+ return { dismissed, count: dismissed.length };
484
+ })()
485
+ `;
486
+
487
+ export async function dismissOverlays(
488
+ tabPattern: string,
489
+ options: { port?: number; host?: string }
490
+ ): Promise<any> {
491
+ const port = options.port || 9222;
492
+ const host = options.host || 'localhost';
493
+
494
+ const tab = await resolveTab(tabPattern, port, host);
495
+ const client = await connectToTab(tab.id, port, host);
496
+
497
+ try {
498
+ const r = await client.Runtime.evaluate({
499
+ expression: DISMISS_OVERLAYS_SCRIPT,
500
+ returnByValue: true
501
+ });
502
+ await client.close();
503
+ return r.result.value;
504
+ } catch (error) {
505
+ await client.close();
506
+ throw error;
507
+ }
508
+ }
509
+
437
510
  const CAPTCHA_DETECT_SCRIPT = `
438
511
  (function() {
439
512
  // Find captcha iframes on the page
package/src/api/server.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import http from 'node:http';
4
4
  import { reconUrl, reconTab } from './recon.js';
5
- import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract } from './act.js';
5
+ import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract, dismissOverlays } from './act.js';
6
6
  import { getAllTabs } from '../chrome/tabs.js';
7
7
 
8
8
  const PORT = parseInt(process.env.API_PORT || '3456', 10);
@@ -110,6 +110,16 @@ const server = http.createServer(async (req, res) => {
110
110
  return json(res, 200, result);
111
111
  }
112
112
 
113
+ // POST /dismiss — dismiss cookie banners, modals, overlays
114
+ if (path === '/dismiss' && req.method === 'POST') {
115
+ const body = parseBody(await readBody(req));
116
+ if (!body.tab) {
117
+ return json(res, 400, { error: 'Provide "tab"' });
118
+ }
119
+ const result = await dismissOverlays(body.tab, { port: CDP_PORT, host: CDP_HOST });
120
+ return json(res, 200, result);
121
+ }
122
+
113
123
  // POST /captcha — detect and interact with captchas
114
124
  if (path === '/captcha' && req.method === 'POST') {
115
125
  const body = parseBody(await readBody(req));