agent-browser 0.3.1 → 0.3.2

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.
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "agent-browser",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Headless browser automation CLI for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
+ "files": [
8
+ "dist",
9
+ "bin",
10
+ "scripts"
11
+ ],
7
12
  "bin": {
8
13
  "agent-browser": "./bin/agent-browser"
9
14
  },
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "semi": true,
3
- "singleQuote": true,
4
- "trailingComma": "es5",
5
- "printWidth": 100,
6
- "tabWidth": 2
7
- }
package/AGENTS.md DELETED
@@ -1,26 +0,0 @@
1
- # AGENTS.md
2
-
3
- Instructions for AI coding agents working with this codebase.
4
-
5
- <!-- opensrc:start -->
6
-
7
- ## Source Code Reference
8
-
9
- Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details.
10
-
11
- See `opensrc/sources.json` for the list of available packages and their versions.
12
-
13
- Use this source code when you need to understand how a package works internally, not just its types/interface.
14
-
15
- ### Fetching Additional Source Code
16
-
17
- To fetch source code for a package or repository you need to understand, run:
18
-
19
- ```bash
20
- npx opensrc <package> # npm package (e.g., npx opensrc zod)
21
- npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
22
- npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
23
- npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)
24
- ```
25
-
26
- <!-- opensrc:end -->
@@ -1,521 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * Benchmark: agent-browser vs playwright-mcp
4
- *
5
- * Measures:
6
- * - Speed: cold start, navigation, click, snapshot operations
7
- * - Context usage: output/response size in bytes and estimated tokens
8
- */
9
-
10
- import { spawn, execSync, ChildProcess } from 'child_process';
11
- import * as readline from 'readline';
12
-
13
- // ============================================================================
14
- // Configuration
15
- // ============================================================================
16
-
17
- const TEST_URL = 'https://example.com';
18
- const ITERATIONS = 3;
19
-
20
- interface BenchmarkResult {
21
- operation: string;
22
- tool: string;
23
- timeMs: number;
24
- outputBytes: number;
25
- estimatedTokens: number;
26
- }
27
-
28
- const results: BenchmarkResult[] = [];
29
-
30
- // Estimate tokens (~4 chars per token for English text)
31
- function estimateTokens(text: string): number {
32
- return Math.ceil(text.length / 4);
33
- }
34
-
35
- function formatBytes(bytes: number): string {
36
- if (bytes < 1024) return `${bytes} B`;
37
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
38
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
39
- }
40
-
41
- // ============================================================================
42
- // Agent-Browser Benchmarks
43
- // ============================================================================
44
-
45
- async function runAgentBrowser(args: string[]): Promise<{ timeMs: number; output: string }> {
46
- const start = performance.now();
47
-
48
- return new Promise((resolve, reject) => {
49
- const proc = spawn('node', ['./dist/index.js', '--session', 'benchmark', '--json', ...args], {
50
- cwd: process.cwd(),
51
- env: { ...process.env },
52
- });
53
-
54
- let output = '';
55
- let stderr = '';
56
-
57
- proc.stdout.on('data', (data) => { output += data.toString(); });
58
- proc.stderr.on('data', (data) => { stderr += data.toString(); });
59
-
60
- proc.on('close', (code) => {
61
- const timeMs = performance.now() - start;
62
- if (code !== 0 && !output.includes('"success"')) {
63
- reject(new Error(`agent-browser failed: ${stderr || output}`));
64
- } else {
65
- resolve({ timeMs, output: output.trim() });
66
- }
67
- });
68
-
69
- proc.on('error', reject);
70
- });
71
- }
72
-
73
- async function benchmarkAgentBrowser(): Promise<void> {
74
- console.log('\n📦 Benchmarking agent-browser...\n');
75
-
76
- // Clean up any existing session
77
- try {
78
- await runAgentBrowser(['close']);
79
- } catch {
80
- // Ignore - session might not exist
81
- }
82
-
83
- // Wait a bit for cleanup
84
- await new Promise(r => setTimeout(r, 500));
85
-
86
- // Cold start (includes daemon startup + browser launch + navigation)
87
- console.log(' ⏱️ Cold start (navigate)...');
88
- const coldStart = await runAgentBrowser(['open', TEST_URL]);
89
- results.push({
90
- operation: 'cold_start_navigate',
91
- tool: 'agent-browser',
92
- timeMs: coldStart.timeMs,
93
- outputBytes: coldStart.output.length,
94
- estimatedTokens: estimateTokens(coldStart.output),
95
- });
96
- console.log(` ${coldStart.timeMs.toFixed(0)}ms, ${formatBytes(coldStart.output.length)}`);
97
-
98
- // Warm operations
99
- for (let i = 0; i < ITERATIONS; i++) {
100
- // Navigate (warm)
101
- console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
102
- const nav = await runAgentBrowser(['open', TEST_URL]);
103
- results.push({
104
- operation: 'navigate_warm',
105
- tool: 'agent-browser',
106
- timeMs: nav.timeMs,
107
- outputBytes: nav.output.length,
108
- estimatedTokens: estimateTokens(nav.output),
109
- });
110
- console.log(` ${nav.timeMs.toFixed(0)}ms, ${formatBytes(nav.output.length)}`);
111
-
112
- // Snapshot
113
- console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
114
- const snapshot = await runAgentBrowser(['snapshot']);
115
- results.push({
116
- operation: 'snapshot',
117
- tool: 'agent-browser',
118
- timeMs: snapshot.timeMs,
119
- outputBytes: snapshot.output.length,
120
- estimatedTokens: estimateTokens(snapshot.output),
121
- });
122
- console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
123
-
124
- // Get title
125
- console.log(` ⏱️ Get title (iter ${i + 1})...`);
126
- const title = await runAgentBrowser(['get', 'title']);
127
- results.push({
128
- operation: 'get_title',
129
- tool: 'agent-browser',
130
- timeMs: title.timeMs,
131
- outputBytes: title.output.length,
132
- estimatedTokens: estimateTokens(title.output),
133
- });
134
- console.log(` ${title.timeMs.toFixed(0)}ms, ${formatBytes(title.output.length)}`);
135
-
136
- // Get URL
137
- console.log(` ⏱️ Get URL (iter ${i + 1})...`);
138
- const url = await runAgentBrowser(['get', 'url']);
139
- results.push({
140
- operation: 'get_url',
141
- tool: 'agent-browser',
142
- timeMs: url.timeMs,
143
- outputBytes: url.output.length,
144
- estimatedTokens: estimateTokens(url.output),
145
- });
146
- console.log(` ${url.timeMs.toFixed(0)}ms, ${formatBytes(url.output.length)}`);
147
-
148
- // Click (on a link that exists on example.com)
149
- console.log(` ⏱️ Click link (iter ${i + 1})...`);
150
- const click = await runAgentBrowser(['click', 'a']);
151
- results.push({
152
- operation: 'click',
153
- tool: 'agent-browser',
154
- timeMs: click.timeMs,
155
- outputBytes: click.output.length,
156
- estimatedTokens: estimateTokens(click.output),
157
- });
158
- console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
159
-
160
- // Navigate back for next iteration
161
- await runAgentBrowser(['open', TEST_URL]);
162
- }
163
-
164
- // Screenshot
165
- console.log(' ⏱️ Screenshot...');
166
- const screenshot = await runAgentBrowser(['screenshot']);
167
- results.push({
168
- operation: 'screenshot',
169
- tool: 'agent-browser',
170
- timeMs: screenshot.timeMs,
171
- outputBytes: screenshot.output.length,
172
- estimatedTokens: estimateTokens(screenshot.output),
173
- });
174
- console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
175
-
176
- // Close
177
- console.log(' ⏱️ Close...');
178
- const close = await runAgentBrowser(['close']);
179
- results.push({
180
- operation: 'close',
181
- tool: 'agent-browser',
182
- timeMs: close.timeMs,
183
- outputBytes: close.output.length,
184
- estimatedTokens: estimateTokens(close.output),
185
- });
186
- console.log(` ${close.timeMs.toFixed(0)}ms`);
187
- }
188
-
189
- // ============================================================================
190
- // Playwright-MCP Benchmarks
191
- // ============================================================================
192
-
193
- class MCPClient {
194
- private proc: ChildProcess;
195
- private rl: readline.Interface;
196
- private responseBuffer: Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }> = new Map();
197
- private requestId = 0;
198
- private ready = false;
199
-
200
- constructor() {
201
- this.proc = spawn('node', ['./opensrc/repos/github.com/microsoft/playwright-mcp/cli.js', '--headless'], {
202
- cwd: process.cwd(),
203
- stdio: ['pipe', 'pipe', 'pipe'],
204
- });
205
-
206
- this.rl = readline.createInterface({ input: this.proc.stdout! });
207
-
208
- this.rl.on('line', (line) => {
209
- try {
210
- const msg = JSON.parse(line);
211
- if (msg.id !== undefined && this.responseBuffer.has(msg.id)) {
212
- const handler = this.responseBuffer.get(msg.id)!;
213
- this.responseBuffer.delete(msg.id);
214
- handler.resolve(msg);
215
- }
216
- } catch {
217
- // Non-JSON output, ignore
218
- }
219
- });
220
-
221
- this.proc.stderr?.on('data', (data) => {
222
- // Debug output, ignore in benchmarks
223
- });
224
- }
225
-
226
- async initialize(): Promise<{ timeMs: number; output: string }> {
227
- const start = performance.now();
228
-
229
- // Send initialize request
230
- const initResult = await this.sendRequest('initialize', {
231
- protocolVersion: '2024-11-05',
232
- capabilities: {},
233
- clientInfo: { name: 'benchmark', version: '1.0.0' },
234
- });
235
-
236
- // Send initialized notification
237
- this.sendNotification('notifications/initialized', {});
238
-
239
- const timeMs = performance.now() - start;
240
- const output = JSON.stringify(initResult);
241
-
242
- this.ready = true;
243
- return { timeMs, output };
244
- }
245
-
246
- async callTool(name: string, args: Record<string, unknown>): Promise<{ timeMs: number; output: string }> {
247
- const start = performance.now();
248
- const result = await this.sendRequest('tools/call', { name, arguments: args });
249
- const timeMs = performance.now() - start;
250
- const output = JSON.stringify(result);
251
- return { timeMs, output };
252
- }
253
-
254
- private sendRequest(method: string, params: Record<string, unknown>): Promise<any> {
255
- const id = ++this.requestId;
256
- const request = { jsonrpc: '2.0', id, method, params };
257
-
258
- return new Promise((resolve, reject) => {
259
- this.responseBuffer.set(id, { resolve, reject });
260
- this.proc.stdin!.write(JSON.stringify(request) + '\n');
261
-
262
- // Timeout after 30s
263
- setTimeout(() => {
264
- if (this.responseBuffer.has(id)) {
265
- this.responseBuffer.delete(id);
266
- reject(new Error(`Request timeout: ${method}`));
267
- }
268
- }, 30000);
269
- });
270
- }
271
-
272
- private sendNotification(method: string, params: Record<string, unknown>): void {
273
- const notification = { jsonrpc: '2.0', method, params };
274
- this.proc.stdin!.write(JSON.stringify(notification) + '\n');
275
- }
276
-
277
- async close(): Promise<void> {
278
- this.proc.kill();
279
- this.rl.close();
280
- }
281
- }
282
-
283
- async function benchmarkPlaywrightMCP(): Promise<void> {
284
- console.log('\n📦 Benchmarking playwright-mcp...\n');
285
-
286
- let client: MCPClient | null = null;
287
-
288
- try {
289
- // Cold start (includes server startup + initialization)
290
- console.log(' ⏱️ Cold start (initialize + navigate)...');
291
- const coldStartBegin = performance.now();
292
-
293
- client = new MCPClient();
294
- const init = await client.initialize();
295
-
296
- // Navigate
297
- const nav = await client.callTool('browser_navigate', { url: TEST_URL });
298
-
299
- const coldStartTime = performance.now() - coldStartBegin;
300
- const coldStartOutput = init.output + nav.output;
301
-
302
- results.push({
303
- operation: 'cold_start_navigate',
304
- tool: 'playwright-mcp',
305
- timeMs: coldStartTime,
306
- outputBytes: coldStartOutput.length,
307
- estimatedTokens: estimateTokens(coldStartOutput),
308
- });
309
- console.log(` ${coldStartTime.toFixed(0)}ms, ${formatBytes(coldStartOutput.length)}`);
310
-
311
- // Warm operations
312
- for (let i = 0; i < ITERATIONS; i++) {
313
- // Navigate (warm)
314
- console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
315
- const navWarm = await client.callTool('browser_navigate', { url: TEST_URL });
316
- results.push({
317
- operation: 'navigate_warm',
318
- tool: 'playwright-mcp',
319
- timeMs: navWarm.timeMs,
320
- outputBytes: navWarm.output.length,
321
- estimatedTokens: estimateTokens(navWarm.output),
322
- });
323
- console.log(` ${navWarm.timeMs.toFixed(0)}ms, ${formatBytes(navWarm.output.length)}`);
324
-
325
- // Snapshot
326
- console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
327
- const snapshot = await client.callTool('browser_snapshot', {});
328
- results.push({
329
- operation: 'snapshot',
330
- tool: 'playwright-mcp',
331
- timeMs: snapshot.timeMs,
332
- outputBytes: snapshot.output.length,
333
- estimatedTokens: estimateTokens(snapshot.output),
334
- });
335
- console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
336
-
337
- // Note: playwright-mcp doesn't have separate get_title/get_url tools
338
- // Title and URL are included in snapshot, so we'll skip those
339
-
340
- // Click
341
- console.log(` ⏱️ Click link (iter ${i + 1})...`);
342
- // playwright-mcp uses ref from snapshot - we'll use a generic approach
343
- const click = await client.callTool('browser_click', {
344
- element: 'More information link',
345
- ref: 'a' // This might not work exactly the same way
346
- });
347
- results.push({
348
- operation: 'click',
349
- tool: 'playwright-mcp',
350
- timeMs: click.timeMs,
351
- outputBytes: click.output.length,
352
- estimatedTokens: estimateTokens(click.output),
353
- });
354
- console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
355
-
356
- // Navigate back
357
- await client.callTool('browser_navigate', { url: TEST_URL });
358
- }
359
-
360
- // Screenshot
361
- console.log(' ⏱️ Screenshot...');
362
- const screenshot = await client.callTool('browser_take_screenshot', {});
363
- results.push({
364
- operation: 'screenshot',
365
- tool: 'playwright-mcp',
366
- timeMs: screenshot.timeMs,
367
- outputBytes: screenshot.output.length,
368
- estimatedTokens: estimateTokens(screenshot.output),
369
- });
370
- console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
371
-
372
- // Close
373
- console.log(' ⏱️ Close...');
374
- const closeStart = performance.now();
375
- const closeResult = await client.callTool('browser_close', {});
376
- results.push({
377
- operation: 'close',
378
- tool: 'playwright-mcp',
379
- timeMs: closeResult.timeMs,
380
- outputBytes: closeResult.output.length,
381
- estimatedTokens: estimateTokens(closeResult.output),
382
- });
383
- console.log(` ${closeResult.timeMs.toFixed(0)}ms`);
384
-
385
- } catch (error) {
386
- console.error(' ❌ playwright-mcp benchmark failed:', error);
387
- } finally {
388
- if (client) {
389
- await client.close();
390
- }
391
- }
392
- }
393
-
394
- // ============================================================================
395
- // Results Summary
396
- // ============================================================================
397
-
398
- function printResults(): void {
399
- console.log('\n' + '='.repeat(80));
400
- console.log('📊 BENCHMARK RESULTS');
401
- console.log('='.repeat(80));
402
-
403
- // Group by operation
404
- const operations = [...new Set(results.map(r => r.operation))];
405
-
406
- console.log('\n📈 Speed Comparison (average across iterations):\n');
407
- console.log('| Operation | agent-browser | playwright-mcp | Difference |');
408
- console.log('|---------------------|---------------|----------------|------------|');
409
-
410
- for (const op of operations) {
411
- const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
412
- const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
413
-
414
- const agentAvg = agentResults.length > 0
415
- ? agentResults.reduce((sum, r) => sum + r.timeMs, 0) / agentResults.length
416
- : null;
417
- const mcpAvg = mcpResults.length > 0
418
- ? mcpResults.reduce((sum, r) => sum + r.timeMs, 0) / mcpResults.length
419
- : null;
420
-
421
- const agentStr = agentAvg !== null ? `${agentAvg.toFixed(0)}ms`.padEnd(13) : 'N/A'.padEnd(13);
422
- const mcpStr = mcpAvg !== null ? `${mcpAvg.toFixed(0)}ms`.padEnd(14) : 'N/A'.padEnd(14);
423
-
424
- let diff = '';
425
- if (agentAvg !== null && mcpAvg !== null) {
426
- const ratio = agentAvg / mcpAvg;
427
- if (ratio < 1) {
428
- diff = `${((1 - ratio) * 100).toFixed(0)}% faster`;
429
- } else if (ratio > 1) {
430
- diff = `${((ratio - 1) * 100).toFixed(0)}% slower`;
431
- } else {
432
- diff = 'same';
433
- }
434
- }
435
-
436
- console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} | ${diff.padEnd(10)} |`);
437
- }
438
-
439
- console.log('\n📦 Context Usage (output size for AI consumption):\n');
440
- console.log('| Operation | agent-browser | playwright-mcp |');
441
- console.log('|---------------------|--------------------|--------------------|');
442
-
443
- for (const op of operations) {
444
- const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
445
- const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
446
-
447
- const agentAvgBytes = agentResults.length > 0
448
- ? agentResults.reduce((sum, r) => sum + r.outputBytes, 0) / agentResults.length
449
- : null;
450
- const agentAvgTokens = agentResults.length > 0
451
- ? agentResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / agentResults.length
452
- : null;
453
-
454
- const mcpAvgBytes = mcpResults.length > 0
455
- ? mcpResults.reduce((sum, r) => sum + r.outputBytes, 0) / mcpResults.length
456
- : null;
457
- const mcpAvgTokens = mcpResults.length > 0
458
- ? mcpResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / mcpResults.length
459
- : null;
460
-
461
- const agentStr = agentAvgBytes !== null
462
- ? `${formatBytes(agentAvgBytes)} (~${Math.round(agentAvgTokens!)} tok)`.padEnd(18)
463
- : 'N/A'.padEnd(18);
464
- const mcpStr = mcpAvgBytes !== null
465
- ? `${formatBytes(mcpAvgBytes)} (~${Math.round(mcpAvgTokens!)} tok)`.padEnd(18)
466
- : 'N/A'.padEnd(18);
467
-
468
- console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} |`);
469
- }
470
-
471
- // Total context usage
472
- const agentTotal = results.filter(r => r.tool === 'agent-browser');
473
- const mcpTotal = results.filter(r => r.tool === 'playwright-mcp');
474
-
475
- const agentTotalBytes = agentTotal.reduce((sum, r) => sum + r.outputBytes, 0);
476
- const agentTotalTokens = agentTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
477
- const mcpTotalBytes = mcpTotal.reduce((sum, r) => sum + r.outputBytes, 0);
478
- const mcpTotalTokens = mcpTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
479
-
480
- console.log('\n📊 Total Context Usage (all operations combined):');
481
- console.log(` agent-browser: ${formatBytes(agentTotalBytes)} (~${agentTotalTokens} tokens)`);
482
- console.log(` playwright-mcp: ${formatBytes(mcpTotalBytes)} (~${mcpTotalTokens} tokens)`);
483
-
484
- if (agentTotalBytes > 0 && mcpTotalBytes > 0) {
485
- const ratio = agentTotalBytes / mcpTotalBytes;
486
- if (ratio < 1) {
487
- console.log(` → agent-browser uses ${((1 - ratio) * 100).toFixed(0)}% less context`);
488
- } else {
489
- console.log(` → playwright-mcp uses ${((1 - 1/ratio) * 100).toFixed(0)}% less context`);
490
- }
491
- }
492
-
493
- console.log('\n' + '='.repeat(80));
494
- }
495
-
496
- // ============================================================================
497
- // Main
498
- // ============================================================================
499
-
500
- async function main(): Promise<void> {
501
- console.log('🚀 Browser Automation Benchmark');
502
- console.log(` Testing against: ${TEST_URL}`);
503
- console.log(` Iterations: ${ITERATIONS}`);
504
- console.log('='.repeat(80));
505
-
506
- try {
507
- // Build first
508
- console.log('\n🔨 Building agent-browser...');
509
- execSync('pnpm build', { cwd: process.cwd(), stdio: 'inherit' });
510
-
511
- await benchmarkAgentBrowser();
512
- await benchmarkPlaywrightMCP();
513
-
514
- printResults();
515
- } catch (error) {
516
- console.error('\n❌ Benchmark failed:', error);
517
- process.exit(1);
518
- }
519
- }
520
-
521
- main();