@sparkleideas/browser 3.0.0-alpha.18

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.
@@ -0,0 +1,654 @@
1
+ /**
2
+ * @sparkleideas/browser - Agent Browser Adapter
3
+ * Wraps agent-browser CLI for programmatic access
4
+ */
5
+
6
+ import { spawn, execSync } from 'child_process';
7
+ import type {
8
+ ActionResult,
9
+ Snapshot,
10
+ SnapshotOptions,
11
+ OpenInput,
12
+ ClickInput,
13
+ FillInput,
14
+ TypeInput,
15
+ ScreenshotInput,
16
+ WaitInput,
17
+ EvalInput,
18
+ GetInput,
19
+ BrowserSession,
20
+ NetworkRouteInput,
21
+ } from '../domain/types.js';
22
+
23
+ export interface AgentBrowserAdapterOptions {
24
+ session?: string;
25
+ timeout?: number;
26
+ headless?: boolean;
27
+ executablePath?: string;
28
+ proxy?: string;
29
+ viewport?: { width: number; height: number };
30
+ debug?: boolean;
31
+ }
32
+
33
+ export class AgentBrowserAdapter {
34
+ private session: string;
35
+ private timeout: number;
36
+ private headless: boolean;
37
+ private executablePath?: string;
38
+ private proxy?: string;
39
+ private viewport?: { width: number; height: number };
40
+ private debug: boolean;
41
+
42
+ constructor(options: AgentBrowserAdapterOptions = {}) {
43
+ this.session = options.session || 'default';
44
+ this.timeout = options.timeout || 30000;
45
+ this.headless = options.headless !== false;
46
+ this.executablePath = options.executablePath;
47
+ this.proxy = options.proxy;
48
+ this.viewport = options.viewport;
49
+ this.debug = options.debug || false;
50
+ }
51
+
52
+ // ===========================================================================
53
+ // Core Command Execution
54
+ // ===========================================================================
55
+
56
+ private async exec<T = unknown>(args: string[], jsonOutput = true): Promise<ActionResult<T>> {
57
+ const startTime = Date.now();
58
+ const fullArgs = [
59
+ '--session', this.session,
60
+ ...(this.timeout ? ['--timeout', String(this.timeout)] : []),
61
+ ...(!this.headless ? ['--headed'] : []),
62
+ ...(this.executablePath ? ['--executable-path', this.executablePath] : []),
63
+ ...(this.proxy ? ['--proxy-server', this.proxy] : []),
64
+ ...(jsonOutput ? ['--json'] : []),
65
+ ...args,
66
+ ];
67
+
68
+ if (this.debug) {
69
+ console.log(`[agent-browser] ${fullArgs.join(' ')}`);
70
+ }
71
+
72
+ return new Promise((resolve) => {
73
+ try {
74
+ const result = execSync(`agent-browser ${fullArgs.join(' ')}`, {
75
+ encoding: 'utf-8',
76
+ timeout: this.timeout + 5000,
77
+ stdio: ['pipe', 'pipe', 'pipe'],
78
+ });
79
+
80
+ const duration = Date.now() - startTime;
81
+
82
+ if (jsonOutput) {
83
+ try {
84
+ const parsed = JSON.parse(result);
85
+ resolve({
86
+ success: parsed.success !== false,
87
+ data: (parsed.data || parsed) as T,
88
+ duration,
89
+ });
90
+ } catch {
91
+ resolve({ success: true, data: result.trim() as T, duration });
92
+ }
93
+ } else {
94
+ resolve({ success: true, data: result.trim() as T, duration });
95
+ }
96
+ } catch (error) {
97
+ const duration = Date.now() - startTime;
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ resolve({ success: false, error: message, duration });
100
+ }
101
+ });
102
+ }
103
+
104
+ // ===========================================================================
105
+ // Navigation Commands
106
+ // ===========================================================================
107
+
108
+ async open(input: OpenInput): Promise<ActionResult> {
109
+ const args = ['open', input.url];
110
+ if (input.waitUntil) args.push('--wait', input.waitUntil);
111
+ if (input.headers) args.push('--headers', JSON.stringify(input.headers));
112
+ return this.exec(args);
113
+ }
114
+
115
+ async back(): Promise<ActionResult> {
116
+ return this.exec(['back']);
117
+ }
118
+
119
+ async forward(): Promise<ActionResult> {
120
+ return this.exec(['forward']);
121
+ }
122
+
123
+ async reload(): Promise<ActionResult> {
124
+ return this.exec(['reload']);
125
+ }
126
+
127
+ async close(): Promise<ActionResult> {
128
+ return this.exec(['close']);
129
+ }
130
+
131
+ // ===========================================================================
132
+ // Interaction Commands
133
+ // ===========================================================================
134
+
135
+ async click(input: ClickInput): Promise<ActionResult> {
136
+ const args = ['click', input.target];
137
+ if (input.button) args.push('--button', input.button);
138
+ if (input.clickCount) args.push('--click-count', String(input.clickCount));
139
+ if (input.force) args.push('--force');
140
+ return this.exec(args);
141
+ }
142
+
143
+ async dblclick(target: string): Promise<ActionResult> {
144
+ return this.exec(['dblclick', target]);
145
+ }
146
+
147
+ async fill(input: FillInput): Promise<ActionResult> {
148
+ return this.exec(['fill', input.target, input.value]);
149
+ }
150
+
151
+ async type(input: TypeInput): Promise<ActionResult> {
152
+ const args = ['type', input.target, input.text];
153
+ if (input.delay) args.push('--delay', String(input.delay));
154
+ return this.exec(args);
155
+ }
156
+
157
+ async press(key: string, delay?: number): Promise<ActionResult> {
158
+ const args = ['press', key];
159
+ if (delay) args.push('--delay', String(delay));
160
+ return this.exec(args);
161
+ }
162
+
163
+ async hover(target: string): Promise<ActionResult> {
164
+ return this.exec(['hover', target]);
165
+ }
166
+
167
+ async focus(target: string): Promise<ActionResult> {
168
+ return this.exec(['focus', target]);
169
+ }
170
+
171
+ async select(target: string, value: string): Promise<ActionResult> {
172
+ return this.exec(['select', target, value]);
173
+ }
174
+
175
+ async check(target: string): Promise<ActionResult> {
176
+ return this.exec(['check', target]);
177
+ }
178
+
179
+ async uncheck(target: string): Promise<ActionResult> {
180
+ return this.exec(['uncheck', target]);
181
+ }
182
+
183
+ async scroll(direction: 'up' | 'down' | 'left' | 'right', pixels?: number): Promise<ActionResult> {
184
+ const args = ['scroll', direction];
185
+ if (pixels) args.push(String(pixels));
186
+ return this.exec(args);
187
+ }
188
+
189
+ async scrollIntoView(target: string): Promise<ActionResult> {
190
+ return this.exec(['scrollintoview', target]);
191
+ }
192
+
193
+ async drag(source: string, target: string): Promise<ActionResult> {
194
+ return this.exec(['drag', source, target]);
195
+ }
196
+
197
+ async upload(target: string, files: string[]): Promise<ActionResult> {
198
+ return this.exec(['upload', target, ...files]);
199
+ }
200
+
201
+ // ===========================================================================
202
+ // Information Retrieval
203
+ // ===========================================================================
204
+
205
+ async get(input: GetInput): Promise<ActionResult> {
206
+ const args = ['get', input.type];
207
+ if (input.target) args.push(input.target);
208
+ if (input.attribute && input.type === 'attr') args.push(input.attribute);
209
+ return this.exec(args);
210
+ }
211
+
212
+ async getText(target: string): Promise<ActionResult<string>> {
213
+ return this.exec<string>(['get', 'text', target]);
214
+ }
215
+
216
+ async getHtml(target: string): Promise<ActionResult<string>> {
217
+ return this.exec<string>(['get', 'html', target]);
218
+ }
219
+
220
+ async getValue(target: string): Promise<ActionResult<string>> {
221
+ return this.exec<string>(['get', 'value', target]);
222
+ }
223
+
224
+ async getAttr(target: string, attribute: string): Promise<ActionResult<string>> {
225
+ return this.exec<string>(['get', 'attr', target, attribute]);
226
+ }
227
+
228
+ async getTitle(): Promise<ActionResult<string>> {
229
+ return this.exec<string>(['get', 'title']);
230
+ }
231
+
232
+ async getUrl(): Promise<ActionResult<string>> {
233
+ return this.exec<string>(['get', 'url']);
234
+ }
235
+
236
+ async getCount(selector: string): Promise<ActionResult<number>> {
237
+ return this.exec<number>(['get', 'count', selector]);
238
+ }
239
+
240
+ async getBox(target: string): Promise<ActionResult<{ x: number; y: number; width: number; height: number }>> {
241
+ return this.exec<{ x: number; y: number; width: number; height: number }>(['get', 'box', target]);
242
+ }
243
+
244
+ // ===========================================================================
245
+ // State Checks
246
+ // ===========================================================================
247
+
248
+ async isVisible(target: string): Promise<ActionResult<boolean>> {
249
+ return this.exec<boolean>(['is', 'visible', target]);
250
+ }
251
+
252
+ async isEnabled(target: string): Promise<ActionResult<boolean>> {
253
+ return this.exec<boolean>(['is', 'enabled', target]);
254
+ }
255
+
256
+ async isChecked(target: string): Promise<ActionResult<boolean>> {
257
+ return this.exec<boolean>(['is', 'checked', target]);
258
+ }
259
+
260
+ // ===========================================================================
261
+ // Snapshot & Screenshot
262
+ // ===========================================================================
263
+
264
+ async snapshot(options: SnapshotOptions = {}): Promise<ActionResult<Snapshot>> {
265
+ const args = ['snapshot'];
266
+ if (options.interactive) args.push('-i');
267
+ if (options.compact) args.push('-c');
268
+ if (options.depth) args.push('-d', String(options.depth));
269
+ if (options.selector) args.push('-s', options.selector);
270
+ return this.exec<Snapshot>(args);
271
+ }
272
+
273
+ async screenshot(input: ScreenshotInput = {}): Promise<ActionResult<string>> {
274
+ const args = ['screenshot'];
275
+ if (input.path) args.push(input.path);
276
+ if (input.fullPage) args.push('--full');
277
+ return this.exec<string>(args);
278
+ }
279
+
280
+ async pdf(path: string): Promise<ActionResult> {
281
+ return this.exec(['pdf', path]);
282
+ }
283
+
284
+ // ===========================================================================
285
+ // Wait Commands
286
+ // ===========================================================================
287
+
288
+ async wait(input: WaitInput): Promise<ActionResult> {
289
+ if (input.selector) {
290
+ return this.exec(['wait', input.selector]);
291
+ }
292
+ if (typeof input.timeout === 'number' && !input.text && !input.url && !input.load && !input.fn) {
293
+ return this.exec(['wait', String(input.timeout)]);
294
+ }
295
+ const args = ['wait'];
296
+ if (input.text) args.push('--text', input.text);
297
+ if (input.url) args.push('--url', input.url);
298
+ if (input.load) args.push('--load', input.load);
299
+ if (input.fn) args.push('--fn', input.fn);
300
+ return this.exec(args);
301
+ }
302
+
303
+ async waitForSelector(selector: string, timeout?: number): Promise<ActionResult> {
304
+ const args = ['wait', selector];
305
+ if (timeout) args.push('--timeout', String(timeout));
306
+ return this.exec(args);
307
+ }
308
+
309
+ async waitForText(text: string): Promise<ActionResult> {
310
+ return this.exec(['wait', '--text', text]);
311
+ }
312
+
313
+ async waitForUrl(pattern: string): Promise<ActionResult> {
314
+ return this.exec(['wait', '--url', pattern]);
315
+ }
316
+
317
+ async waitForLoad(state: 'load' | 'domcontentloaded' | 'networkidle'): Promise<ActionResult> {
318
+ return this.exec(['wait', '--load', state]);
319
+ }
320
+
321
+ async waitForFunction(fn: string): Promise<ActionResult> {
322
+ return this.exec(['wait', '--fn', fn]);
323
+ }
324
+
325
+ // ===========================================================================
326
+ // JavaScript Execution
327
+ // ===========================================================================
328
+
329
+ async eval<T = unknown>(input: EvalInput): Promise<ActionResult<T>> {
330
+ return this.exec<T>(['eval', input.script]);
331
+ }
332
+
333
+ // ===========================================================================
334
+ // Mouse Control
335
+ // ===========================================================================
336
+
337
+ async mouseMove(x: number, y: number): Promise<ActionResult> {
338
+ return this.exec(['mouse', 'move', String(x), String(y)]);
339
+ }
340
+
341
+ async mouseDown(button: 'left' | 'right' | 'middle' = 'left'): Promise<ActionResult> {
342
+ return this.exec(['mouse', 'down', button]);
343
+ }
344
+
345
+ async mouseUp(button: 'left' | 'right' | 'middle' = 'left'): Promise<ActionResult> {
346
+ return this.exec(['mouse', 'up', button]);
347
+ }
348
+
349
+ async mouseWheel(deltaY: number, deltaX = 0): Promise<ActionResult> {
350
+ return this.exec(['mouse', 'wheel', String(deltaY), String(deltaX)]);
351
+ }
352
+
353
+ // ===========================================================================
354
+ // Browser Settings
355
+ // ===========================================================================
356
+
357
+ async setViewport(width: number, height: number): Promise<ActionResult> {
358
+ return this.exec(['set', 'viewport', String(width), String(height)]);
359
+ }
360
+
361
+ async setDevice(device: string): Promise<ActionResult> {
362
+ return this.exec(['set', 'device', device]);
363
+ }
364
+
365
+ async setGeolocation(lat: number, lng: number): Promise<ActionResult> {
366
+ return this.exec(['set', 'geo', String(lat), String(lng)]);
367
+ }
368
+
369
+ async setOffline(enabled: boolean): Promise<ActionResult> {
370
+ return this.exec(['set', 'offline', enabled ? 'on' : 'off']);
371
+ }
372
+
373
+ async setHeaders(headers: Record<string, string>): Promise<ActionResult> {
374
+ return this.exec(['set', 'headers', JSON.stringify(headers)]);
375
+ }
376
+
377
+ async setCredentials(username: string, password: string): Promise<ActionResult> {
378
+ return this.exec(['set', 'credentials', username, password]);
379
+ }
380
+
381
+ async setMedia(scheme: 'dark' | 'light'): Promise<ActionResult> {
382
+ return this.exec(['set', 'media', scheme]);
383
+ }
384
+
385
+ // ===========================================================================
386
+ // Cookies & Storage
387
+ // ===========================================================================
388
+
389
+ async getCookies(): Promise<ActionResult> {
390
+ return this.exec(['cookies']);
391
+ }
392
+
393
+ async setCookie(name: string, value: string): Promise<ActionResult> {
394
+ return this.exec(['cookies', 'set', name, value]);
395
+ }
396
+
397
+ async clearCookies(): Promise<ActionResult> {
398
+ return this.exec(['cookies', 'clear']);
399
+ }
400
+
401
+ async getLocalStorage(key?: string): Promise<ActionResult> {
402
+ const args = ['storage', 'local'];
403
+ if (key) args.push(key);
404
+ return this.exec(args);
405
+ }
406
+
407
+ async setLocalStorage(key: string, value: string): Promise<ActionResult> {
408
+ return this.exec(['storage', 'local', 'set', key, value]);
409
+ }
410
+
411
+ async clearLocalStorage(): Promise<ActionResult> {
412
+ return this.exec(['storage', 'local', 'clear']);
413
+ }
414
+
415
+ async getSessionStorage(key?: string): Promise<ActionResult> {
416
+ const args = ['storage', 'session'];
417
+ if (key) args.push(key);
418
+ return this.exec(args);
419
+ }
420
+
421
+ async setSessionStorage(key: string, value: string): Promise<ActionResult> {
422
+ return this.exec(['storage', 'session', 'set', key, value]);
423
+ }
424
+
425
+ async clearSessionStorage(): Promise<ActionResult> {
426
+ return this.exec(['storage', 'session', 'clear']);
427
+ }
428
+
429
+ // ===========================================================================
430
+ // Network
431
+ // ===========================================================================
432
+
433
+ async networkRoute(input: NetworkRouteInput): Promise<ActionResult> {
434
+ const args = ['network', 'route', input.urlPattern];
435
+ if (input.abort) args.push('--abort');
436
+ if (input.body) args.push('--body', typeof input.body === 'string' ? input.body : JSON.stringify(input.body));
437
+ if (input.status) args.push('--status', String(input.status));
438
+ if (input.headers) args.push('--headers', JSON.stringify(input.headers));
439
+ return this.exec(args);
440
+ }
441
+
442
+ async networkUnroute(urlPattern?: string): Promise<ActionResult> {
443
+ const args = ['network', 'unroute'];
444
+ if (urlPattern) args.push(urlPattern);
445
+ return this.exec(args);
446
+ }
447
+
448
+ async networkRequests(filter?: string): Promise<ActionResult> {
449
+ const args = ['network', 'requests'];
450
+ if (filter) args.push('--filter', filter);
451
+ return this.exec(args);
452
+ }
453
+
454
+ // ===========================================================================
455
+ // Tabs & Windows
456
+ // ===========================================================================
457
+
458
+ async listTabs(): Promise<ActionResult> {
459
+ return this.exec(['tab']);
460
+ }
461
+
462
+ async newTab(url?: string): Promise<ActionResult> {
463
+ const args = ['tab', 'new'];
464
+ if (url) args.push(url);
465
+ return this.exec(args);
466
+ }
467
+
468
+ async switchTab(index: number): Promise<ActionResult> {
469
+ return this.exec(['tab', String(index)]);
470
+ }
471
+
472
+ async closeTab(index?: number): Promise<ActionResult> {
473
+ const args = ['tab', 'close'];
474
+ if (index !== undefined) args.push(String(index));
475
+ return this.exec(args);
476
+ }
477
+
478
+ async newWindow(): Promise<ActionResult> {
479
+ return this.exec(['window', 'new']);
480
+ }
481
+
482
+ // ===========================================================================
483
+ // Frames
484
+ // ===========================================================================
485
+
486
+ async switchFrame(selector: string): Promise<ActionResult> {
487
+ return this.exec(['frame', selector]);
488
+ }
489
+
490
+ async switchToMainFrame(): Promise<ActionResult> {
491
+ return this.exec(['frame', 'main']);
492
+ }
493
+
494
+ // ===========================================================================
495
+ // Dialogs
496
+ // ===========================================================================
497
+
498
+ async dialogAccept(text?: string): Promise<ActionResult> {
499
+ const args = ['dialog', 'accept'];
500
+ if (text) args.push(text);
501
+ return this.exec(args);
502
+ }
503
+
504
+ async dialogDismiss(): Promise<ActionResult> {
505
+ return this.exec(['dialog', 'dismiss']);
506
+ }
507
+
508
+ // ===========================================================================
509
+ // Debug & Trace
510
+ // ===========================================================================
511
+
512
+ async traceStart(path?: string): Promise<ActionResult> {
513
+ const args = ['trace', 'start'];
514
+ if (path) args.push(path);
515
+ return this.exec(args);
516
+ }
517
+
518
+ async traceStop(path?: string): Promise<ActionResult> {
519
+ const args = ['trace', 'stop'];
520
+ if (path) args.push(path);
521
+ return this.exec(args);
522
+ }
523
+
524
+ async getConsole(): Promise<ActionResult> {
525
+ return this.exec(['console']);
526
+ }
527
+
528
+ async clearConsole(): Promise<ActionResult> {
529
+ return this.exec(['console', '--clear']);
530
+ }
531
+
532
+ async getErrors(): Promise<ActionResult> {
533
+ return this.exec(['errors']);
534
+ }
535
+
536
+ async clearErrors(): Promise<ActionResult> {
537
+ return this.exec(['errors', '--clear']);
538
+ }
539
+
540
+ async highlight(selector: string): Promise<ActionResult> {
541
+ return this.exec(['highlight', selector]);
542
+ }
543
+
544
+ async saveState(path: string): Promise<ActionResult> {
545
+ return this.exec(['state', 'save', path]);
546
+ }
547
+
548
+ async loadState(path: string): Promise<ActionResult> {
549
+ return this.exec(['state', 'load', path]);
550
+ }
551
+
552
+ // ===========================================================================
553
+ // Find Commands (Semantic Locators)
554
+ // ===========================================================================
555
+
556
+ async findByRole(role: string, action: string, options?: { name?: string; exact?: boolean }): Promise<ActionResult> {
557
+ const args = ['find', 'role', role, action];
558
+ if (options?.name) args.push('--name', options.name);
559
+ if (options?.exact) args.push('--exact');
560
+ return this.exec(args);
561
+ }
562
+
563
+ async findByText(text: string, action: string): Promise<ActionResult> {
564
+ return this.exec(['find', 'text', text, action]);
565
+ }
566
+
567
+ async findByLabel(label: string, action: string, value?: string): Promise<ActionResult> {
568
+ const args = ['find', 'label', label, action];
569
+ if (value) args.push(value);
570
+ return this.exec(args);
571
+ }
572
+
573
+ async findByPlaceholder(placeholder: string, action: string, value?: string): Promise<ActionResult> {
574
+ const args = ['find', 'placeholder', placeholder, action];
575
+ if (value) args.push(value);
576
+ return this.exec(args);
577
+ }
578
+
579
+ async findByTestId(testId: string, action: string, value?: string): Promise<ActionResult> {
580
+ const args = ['find', 'testid', testId, action];
581
+ if (value) args.push(value);
582
+ return this.exec(args);
583
+ }
584
+
585
+ async findFirst(selector: string, action: string, value?: string): Promise<ActionResult> {
586
+ const args = ['find', 'first', selector, action];
587
+ if (value) args.push(value);
588
+ return this.exec(args);
589
+ }
590
+
591
+ async findLast(selector: string, action: string, value?: string): Promise<ActionResult> {
592
+ const args = ['find', 'last', selector, action];
593
+ if (value) args.push(value);
594
+ return this.exec(args);
595
+ }
596
+
597
+ async findNth(n: number, selector: string, action: string, value?: string): Promise<ActionResult> {
598
+ const args = ['find', 'nth', String(n), selector, action];
599
+ if (value) args.push(value);
600
+ return this.exec(args);
601
+ }
602
+
603
+ // ===========================================================================
604
+ // Session Management
605
+ // ===========================================================================
606
+
607
+ async listSessions(): Promise<ActionResult> {
608
+ return this.exec(['session', 'list']);
609
+ }
610
+
611
+ async getCurrentSession(): Promise<ActionResult> {
612
+ return this.exec(['session']);
613
+ }
614
+
615
+ setSession(sessionId: string): void {
616
+ this.session = sessionId;
617
+ }
618
+
619
+ getSession(): string {
620
+ return this.session;
621
+ }
622
+
623
+ // ===========================================================================
624
+ // CDP Connection
625
+ // ===========================================================================
626
+
627
+ async connect(port: number): Promise<ActionResult> {
628
+ return this.exec(['connect', String(port)]);
629
+ }
630
+
631
+ // ===========================================================================
632
+ // Setup
633
+ // ===========================================================================
634
+
635
+ static async install(withDeps = false): Promise<ActionResult> {
636
+ const args = ['install'];
637
+ if (withDeps) args.push('--with-deps');
638
+
639
+ return new Promise((resolve) => {
640
+ try {
641
+ const result = execSync(`agent-browser ${args.join(' ')}`, {
642
+ encoding: 'utf-8',
643
+ timeout: 300000, // 5 minutes for download
644
+ });
645
+ resolve({ success: true, data: result });
646
+ } catch (error) {
647
+ const message = error instanceof Error ? error.message : String(error);
648
+ resolve({ success: false, error: message });
649
+ }
650
+ });
651
+ }
652
+ }
653
+
654
+ export default AgentBrowserAdapter;