agent-browser 0.0.0 → 0.1.0

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.
Files changed (52) hide show
  1. package/.prettierrc +7 -0
  2. package/README.md +271 -1
  3. package/bin/agent-browser +2 -0
  4. package/dist/actions.d.ts +7 -0
  5. package/dist/actions.d.ts.map +1 -0
  6. package/dist/actions.js +1138 -0
  7. package/dist/actions.js.map +1 -0
  8. package/dist/browser.d.ts +232 -0
  9. package/dist/browser.d.ts.map +1 -0
  10. package/dist/browser.js +477 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/browser.test.d.ts +2 -0
  13. package/dist/browser.test.d.ts.map +1 -0
  14. package/dist/browser.test.js +136 -0
  15. package/dist/browser.test.js.map +1 -0
  16. package/dist/client.d.ts +17 -0
  17. package/dist/client.d.ts.map +1 -0
  18. package/dist/client.js +133 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/daemon.d.ts +29 -0
  21. package/dist/daemon.d.ts.map +1 -0
  22. package/dist/daemon.js +165 -0
  23. package/dist/daemon.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +972 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/protocol.d.ts +26 -0
  29. package/dist/protocol.d.ts.map +1 -0
  30. package/dist/protocol.js +717 -0
  31. package/dist/protocol.js.map +1 -0
  32. package/dist/protocol.test.d.ts +2 -0
  33. package/dist/protocol.test.d.ts.map +1 -0
  34. package/dist/protocol.test.js +176 -0
  35. package/dist/protocol.test.js.map +1 -0
  36. package/dist/types.d.ts +604 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +2 -0
  39. package/dist/types.js.map +1 -0
  40. package/package.json +36 -7
  41. package/src/actions.ts +1658 -0
  42. package/src/browser.test.ts +157 -0
  43. package/src/browser.ts +586 -0
  44. package/src/client.ts +150 -0
  45. package/src/daemon.ts +187 -0
  46. package/src/index.ts +984 -0
  47. package/src/protocol.test.ts +216 -0
  48. package/src/protocol.ts +848 -0
  49. package/src/types.ts +913 -0
  50. package/tsconfig.json +19 -0
  51. package/vitest.config.ts +9 -0
  52. package/index.js +0 -2
package/dist/index.js ADDED
@@ -0,0 +1,972 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import { send, setDebug, setSession, getSession } from './client.js';
6
+ // ============================================================================
7
+ // Utilities
8
+ // ============================================================================
9
+ function listSessions() {
10
+ const tmpDir = os.tmpdir();
11
+ try {
12
+ const files = fs.readdirSync(tmpDir);
13
+ const sessions = [];
14
+ for (const file of files) {
15
+ const match = file.match(/^agent-browser-(.+)\.pid$/);
16
+ if (match) {
17
+ const pidFile = path.join(tmpDir, file);
18
+ try {
19
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
20
+ process.kill(pid, 0);
21
+ sessions.push(match[1]);
22
+ }
23
+ catch {
24
+ /* Process not running */
25
+ }
26
+ }
27
+ }
28
+ return sessions;
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ const colors = {
35
+ reset: '\x1b[0m',
36
+ bold: '\x1b[1m',
37
+ dim: '\x1b[2m',
38
+ red: '\x1b[31m',
39
+ green: '\x1b[32m',
40
+ yellow: '\x1b[33m',
41
+ cyan: '\x1b[36m',
42
+ };
43
+ const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
44
+ function genId() {
45
+ return Math.random().toString(36).slice(2, 10);
46
+ }
47
+ function err(msg) {
48
+ console.error(c('red', 'Error:'), msg);
49
+ process.exit(1);
50
+ }
51
+ // ============================================================================
52
+ // Help
53
+ // ============================================================================
54
+ function printHelp() {
55
+ console.log(`
56
+ ${c('bold', 'agent-browser')} - headless browser automation for AI agents
57
+
58
+ ${c('yellow', 'Usage:')} agent-browser <command> [options]
59
+
60
+ ${c('yellow', 'Core Commands:')}
61
+ ${c('cyan', 'open')} <url> Navigate to URL
62
+ ${c('cyan', 'click')} <sel> Click element
63
+ ${c('cyan', 'type')} <sel> <text> Type into element
64
+ ${c('cyan', 'fill')} <sel> <text> Clear and fill
65
+ ${c('cyan', 'press')} <key> Press key (Enter, Tab, Control+a)
66
+ ${c('cyan', 'hover')} <sel> Hover element
67
+ ${c('cyan', 'select')} <sel> <val> Select dropdown option
68
+ ${c('cyan', 'scroll')} <dir> [px] Scroll (up/down/left/right)
69
+ ${c('cyan', 'wait')} <sel|ms> Wait for element or time
70
+ ${c('cyan', 'screenshot')} [path] Take screenshot
71
+ ${c('cyan', 'snapshot')} Accessibility tree (for AI)
72
+ ${c('cyan', 'eval')} <js> Run JavaScript
73
+ ${c('cyan', 'close')} Close browser
74
+
75
+ ${c('yellow', 'Get Info:')} agent-browser get <what> [selector]
76
+ text, html, value, attr, title, url, count, box
77
+
78
+ ${c('yellow', 'Check State:')} agent-browser is <what> <selector>
79
+ visible, enabled, checked
80
+
81
+ ${c('yellow', 'Find Elements:')} agent-browser find <locator> <action> [value]
82
+ role, text, label, placeholder, alt, title, testid, first, last, nth
83
+
84
+ ${c('yellow', 'Mouse:')} agent-browser mouse <action> [args]
85
+ move <x> <y>, down, up, wheel <dy>
86
+
87
+ ${c('yellow', 'Storage:')}
88
+ ${c('cyan', 'cookies')} [get|set|clear] Manage cookies
89
+ ${c('cyan', 'storage')} <local|session> Manage web storage
90
+
91
+ ${c('yellow', 'Browser:')} agent-browser set <setting> [value]
92
+ viewport, device, geo, offline, headers, credentials
93
+
94
+ ${c('yellow', 'Network:')} agent-browser network <action>
95
+ route, unroute, requests
96
+
97
+ ${c('yellow', 'Tabs:')}
98
+ ${c('cyan', 'tab')} [new|list|close|<n>] Manage tabs
99
+
100
+ ${c('yellow', 'Debug:')}
101
+ ${c('cyan', 'trace')} start|stop <path> Record trace
102
+ ${c('cyan', 'console')} View console logs
103
+ ${c('cyan', 'errors')} View page errors
104
+
105
+ ${c('yellow', 'Options:')}
106
+ --session <name> Isolated session (or AGENT_BROWSER_SESSION env)
107
+ --json JSON output
108
+ --full, -f Full page screenshot
109
+ --headed Show browser window (not headless)
110
+ --debug Debug output
111
+
112
+ ${c('yellow', 'Examples:')}
113
+ agent-browser open example.com
114
+ agent-browser click "#submit"
115
+ agent-browser fill "#email" "test@example.com"
116
+ agent-browser get text "h1"
117
+ agent-browser is visible ".modal"
118
+ agent-browser find role button click --name Submit
119
+ agent-browser wait 2000
120
+ agent-browser wait --load networkidle
121
+ `);
122
+ }
123
+ // ============================================================================
124
+ // Response Printing
125
+ // ============================================================================
126
+ function printResponse(response, jsonMode) {
127
+ if (jsonMode) {
128
+ console.log(JSON.stringify(response));
129
+ return;
130
+ }
131
+ if (!response.success) {
132
+ console.error(c('red', '✗ Error:'), response.error);
133
+ process.exit(1);
134
+ }
135
+ const data = response.data;
136
+ if (data.url && data.title) {
137
+ console.log(c('green', '✓'), c('bold', data.title));
138
+ console.log(c('dim', ` ${data.url}`));
139
+ }
140
+ else if (data.text !== undefined) {
141
+ console.log(data.text ?? c('dim', 'null'));
142
+ }
143
+ else if (data.html !== undefined) {
144
+ console.log(data.html);
145
+ }
146
+ else if (data.value !== undefined) {
147
+ console.log(data.value ?? c('dim', 'null'));
148
+ }
149
+ else if (data.result !== undefined) {
150
+ const result = data.result;
151
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
152
+ }
153
+ else if (data.snapshot) {
154
+ console.log(data.snapshot);
155
+ }
156
+ else if (data.visible !== undefined) {
157
+ console.log(data.visible ? c('green', 'true') : c('red', 'false'));
158
+ }
159
+ else if (data.enabled !== undefined) {
160
+ console.log(data.enabled ? c('green', 'true') : c('red', 'false'));
161
+ }
162
+ else if (data.checked !== undefined) {
163
+ console.log(data.checked ? c('green', 'true') : c('red', 'false'));
164
+ }
165
+ else if (data.count !== undefined) {
166
+ console.log(data.count);
167
+ }
168
+ else if (data.box) {
169
+ const box = data.box;
170
+ console.log(`x:${box.x} y:${box.y} w:${box.width} h:${box.height}`);
171
+ }
172
+ else if (data.url) {
173
+ console.log(data.url);
174
+ }
175
+ else if (data.title) {
176
+ console.log(data.title);
177
+ }
178
+ else if (data.base64) {
179
+ console.log(c('green', '✓'), 'Screenshot captured');
180
+ }
181
+ else if (data.path) {
182
+ console.log(c('green', '✓'), `Saved: ${data.path}`);
183
+ }
184
+ else if (data.cookies) {
185
+ const cookies = data.cookies;
186
+ if (cookies.length === 0)
187
+ console.log(c('dim', 'No cookies'));
188
+ else
189
+ cookies.forEach((ck) => console.log(`${c('cyan', ck.name)}: ${ck.value}`));
190
+ }
191
+ else if (data.tabs) {
192
+ const tabs = data.tabs;
193
+ tabs.forEach((t) => {
194
+ const marker = t.active ? c('green', '→') : ' ';
195
+ console.log(`${marker} [${t.index}] ${t.title || c('dim', '(untitled)')}`);
196
+ if (t.url)
197
+ console.log(c('dim', ` ${t.url}`));
198
+ });
199
+ }
200
+ else if (data.index !== undefined && data.total !== undefined) {
201
+ console.log(c('green', '✓'), `Tab ${data.index} (${data.total} total)`);
202
+ }
203
+ else if (data.messages) {
204
+ const msgs = data.messages;
205
+ if (msgs.length === 0)
206
+ console.log(c('dim', 'No messages'));
207
+ else
208
+ msgs.forEach((m) => {
209
+ const col = m.type === 'error' ? 'red' : m.type === 'warning' ? 'yellow' : 'dim';
210
+ console.log(`${c(col, `[${m.type}]`)} ${m.text}`);
211
+ });
212
+ }
213
+ else if (data.errors) {
214
+ const errs = data.errors;
215
+ if (errs.length === 0)
216
+ console.log(c('dim', 'No errors'));
217
+ else
218
+ errs.forEach((e) => console.log(c('red', '✗'), e.message));
219
+ }
220
+ else if (data.requests) {
221
+ const reqs = data.requests;
222
+ if (reqs.length === 0)
223
+ console.log(c('dim', 'No requests'));
224
+ else
225
+ reqs.forEach((r) => console.log(`${c('cyan', r.method)} ${r.url}`));
226
+ }
227
+ else if (data.moved) {
228
+ console.log(c('green', '✓'), `Moved to (${data.x}, ${data.y})`);
229
+ }
230
+ else if (data.body !== undefined && data.status !== undefined) {
231
+ // Response body
232
+ console.log(c('green', '✓'), `${data.status} ${data.url}`);
233
+ console.log(typeof data.body === 'object' ? JSON.stringify(data.body, null, 2) : data.body);
234
+ }
235
+ else if (data.filename) {
236
+ // Download
237
+ console.log(c('green', '✓'), `Downloaded: ${data.filename}`);
238
+ console.log(c('dim', ` Path: ${data.path}`));
239
+ }
240
+ else if (data.inserted) {
241
+ console.log(c('green', '✓'), 'Text inserted');
242
+ }
243
+ else if (data.key) {
244
+ console.log(c('green', '✓'), `Key ${data.down ? 'down' : 'up'}: ${data.key}`);
245
+ }
246
+ else if (data.note) {
247
+ console.log(c('yellow', '⚠'), data.note);
248
+ }
249
+ else if (data.closed === true) {
250
+ console.log(c('green', '✓'), 'Browser closed');
251
+ }
252
+ else if (data.launched) {
253
+ console.log(c('green', '✓'), 'Browser launched');
254
+ }
255
+ else if (data.state) {
256
+ console.log(c('green', '✓'), `Load state: ${data.state}`);
257
+ }
258
+ else if (Object.keys(data).some((k) => [
259
+ 'clicked',
260
+ 'typed',
261
+ 'filled',
262
+ 'pressed',
263
+ 'hovered',
264
+ 'scrolled',
265
+ 'selected',
266
+ 'waited',
267
+ 'checked',
268
+ 'unchecked',
269
+ 'focused',
270
+ 'set',
271
+ 'cleared',
272
+ 'started',
273
+ 'down',
274
+ 'up',
275
+ ].includes(k))) {
276
+ console.log(c('green', '✓'), 'Done');
277
+ }
278
+ else {
279
+ console.log(c('green', '✓'), JSON.stringify(data));
280
+ }
281
+ }
282
+ // ============================================================================
283
+ // Command Handlers
284
+ // ============================================================================
285
+ async function handleGet(args, id) {
286
+ const what = args[0];
287
+ const selector = args[1];
288
+ switch (what) {
289
+ case 'text':
290
+ if (!selector)
291
+ err('Selector required: agent-browser get text <selector>');
292
+ return { id, action: 'gettext', selector };
293
+ case 'html':
294
+ if (!selector)
295
+ err('Selector required: agent-browser get html <selector>');
296
+ return { id, action: 'innerhtml', selector };
297
+ case 'value':
298
+ if (!selector)
299
+ err('Selector required: agent-browser get value <selector>');
300
+ return { id, action: 'inputvalue', selector };
301
+ case 'attr':
302
+ if (!selector || !args[2])
303
+ err('Usage: agent-browser get attr <selector> <attribute>');
304
+ return { id, action: 'getattribute', selector, attribute: args[2] };
305
+ case 'title':
306
+ return { id, action: 'title' };
307
+ case 'url':
308
+ return { id, action: 'url' };
309
+ case 'count':
310
+ if (!selector)
311
+ err('Selector required: agent-browser get count <selector>');
312
+ return { id, action: 'count', selector };
313
+ case 'box':
314
+ if (!selector)
315
+ err('Selector required: agent-browser get box <selector>');
316
+ return { id, action: 'boundingbox', selector };
317
+ default:
318
+ err(`Unknown: agent-browser get ${what}. Options: text, html, value, attr, title, url, count, box`);
319
+ }
320
+ }
321
+ async function handleIs(args, id) {
322
+ const what = args[0];
323
+ const selector = args[1];
324
+ if (!selector)
325
+ err(`Selector required: agent-browser is ${what} <selector>`);
326
+ switch (what) {
327
+ case 'visible':
328
+ return { id, action: 'isvisible', selector };
329
+ case 'enabled':
330
+ return { id, action: 'isenabled', selector };
331
+ case 'checked':
332
+ return { id, action: 'ischecked', selector };
333
+ default:
334
+ err(`Unknown: agent-browser is ${what}. Options: visible, enabled, checked`);
335
+ }
336
+ }
337
+ async function handleFind(args, id, flags) {
338
+ const locator = args[0];
339
+ const value = args[1];
340
+ const subaction = args[2] || 'click';
341
+ const fillValue = args[3];
342
+ if (!value)
343
+ err(`Value required: agent-browser find ${locator} <value> <action>`);
344
+ const exact = flags.exact;
345
+ const name = flags.name;
346
+ switch (locator) {
347
+ case 'role':
348
+ return { id, action: 'getbyrole', role: value, subaction, value: fillValue, name, exact };
349
+ case 'text':
350
+ return { id, action: 'getbytext', text: value, subaction, exact };
351
+ case 'label':
352
+ return { id, action: 'getbylabel', label: value, subaction, value: fillValue, exact };
353
+ case 'placeholder':
354
+ return {
355
+ id,
356
+ action: 'getbyplaceholder',
357
+ placeholder: value,
358
+ subaction,
359
+ value: fillValue,
360
+ exact,
361
+ };
362
+ case 'alt':
363
+ return { id, action: 'getbyalttext', text: value, subaction, exact };
364
+ case 'title':
365
+ return { id, action: 'getbytitle', text: value, subaction, exact };
366
+ case 'testid':
367
+ return { id, action: 'getbytestid', testId: value, subaction, value: fillValue };
368
+ case 'first':
369
+ return { id, action: 'nth', selector: value, index: 0, subaction, value: fillValue };
370
+ case 'last':
371
+ return { id, action: 'nth', selector: value, index: -1, subaction, value: fillValue };
372
+ case 'nth': {
373
+ const idx = parseInt(value, 10);
374
+ const sel = args[2];
375
+ const act = args[3] || 'click';
376
+ const val = args[4];
377
+ if (isNaN(idx) || !sel)
378
+ err('Usage: agent-browser find nth <index> <selector> <action>');
379
+ return { id, action: 'nth', selector: sel, index: idx, subaction: act, value: val };
380
+ }
381
+ default:
382
+ err(`Unknown locator: ${locator}. Options: role, text, label, placeholder, alt, title, testid, first, last, nth`);
383
+ }
384
+ }
385
+ async function handleMouse(args, id) {
386
+ const action = args[0];
387
+ switch (action) {
388
+ case 'move': {
389
+ const x = parseInt(args[1], 10);
390
+ const y = parseInt(args[2], 10);
391
+ if (isNaN(x) || isNaN(y))
392
+ err('Usage: agent-browser mouse move <x> <y>');
393
+ return { id, action: 'mousemove', x, y };
394
+ }
395
+ case 'down':
396
+ return { id, action: 'mousedown', button: args[1] || 'left' };
397
+ case 'up':
398
+ return { id, action: 'mouseup', button: args[1] || 'left' };
399
+ case 'wheel': {
400
+ const dy = parseInt(args[1], 10) || 100;
401
+ const dx = parseInt(args[2], 10) || 0;
402
+ return { id, action: 'wheel', deltaY: dy, deltaX: dx };
403
+ }
404
+ default:
405
+ err(`Unknown: agent-browser mouse ${action}. Options: move, down, up, wheel`);
406
+ }
407
+ }
408
+ async function handleSet(args, id) {
409
+ const setting = args[0];
410
+ switch (setting) {
411
+ case 'viewport': {
412
+ const w = parseInt(args[1], 10);
413
+ const h = parseInt(args[2], 10);
414
+ if (isNaN(w) || isNaN(h))
415
+ err('Usage: agent-browser set viewport <width> <height>');
416
+ return { id, action: 'viewport', width: w, height: h };
417
+ }
418
+ case 'device':
419
+ if (!args[1])
420
+ err('Usage: agent-browser set device <name>');
421
+ return { id, action: 'device', device: args[1] };
422
+ case 'geo':
423
+ case 'geolocation': {
424
+ const lat = parseFloat(args[1]);
425
+ const lng = parseFloat(args[2]);
426
+ if (isNaN(lat) || isNaN(lng))
427
+ err('Usage: agent-browser set geo <lat> <lng>');
428
+ return { id, action: 'geolocation', latitude: lat, longitude: lng };
429
+ }
430
+ case 'offline':
431
+ return { id, action: 'offline', offline: args[1] !== 'off' && args[1] !== 'false' };
432
+ case 'headers':
433
+ if (!args[1])
434
+ err('Usage: agent-browser set headers <json>');
435
+ try {
436
+ return { id, action: 'headers', headers: JSON.parse(args[1]) };
437
+ }
438
+ catch {
439
+ err('Invalid JSON for headers');
440
+ }
441
+ break;
442
+ case 'credentials':
443
+ case 'auth':
444
+ if (!args[1] || !args[2])
445
+ err('Usage: agent-browser set credentials <user> <pass>');
446
+ return { id, action: 'credentials', username: args[1], password: args[2] };
447
+ case 'media': {
448
+ const colorScheme = args.includes('dark')
449
+ ? 'dark'
450
+ : args.includes('light')
451
+ ? 'light'
452
+ : undefined;
453
+ const media = args.includes('print')
454
+ ? 'print'
455
+ : args.includes('screen')
456
+ ? 'screen'
457
+ : undefined;
458
+ return { id, action: 'emulatemedia', colorScheme, media };
459
+ }
460
+ default:
461
+ err(`Unknown: agent-browser set ${setting}. Options: viewport, device, geo, offline, headers, credentials, media`);
462
+ }
463
+ return {};
464
+ }
465
+ async function handleNetwork(args, id, allArgs) {
466
+ const action = args[0];
467
+ switch (action) {
468
+ case 'route': {
469
+ const url = args[1];
470
+ if (!url)
471
+ err('Usage: agent-browser network route <url> [--abort|--body <json>]');
472
+ const abort = allArgs.includes('--abort');
473
+ const bodyIdx = allArgs.indexOf('--body');
474
+ const body = bodyIdx !== -1 ? allArgs[bodyIdx + 1] : undefined;
475
+ return {
476
+ id,
477
+ action: 'route',
478
+ url,
479
+ abort,
480
+ response: body ? { body, contentType: 'application/json' } : undefined,
481
+ };
482
+ }
483
+ case 'unroute':
484
+ return { id, action: 'unroute', url: args[1] };
485
+ case 'requests': {
486
+ const clear = allArgs.includes('--clear');
487
+ const filterIdx = allArgs.indexOf('--filter');
488
+ const filter = filterIdx !== -1 ? allArgs[filterIdx + 1] : undefined;
489
+ return { id, action: 'requests', clear, filter };
490
+ }
491
+ default:
492
+ err(`Unknown: agent-browser network ${action}. Options: route, unroute, requests`);
493
+ }
494
+ return {};
495
+ }
496
+ async function handleStorage(args, id) {
497
+ const type = args[0];
498
+ const sub = args[1];
499
+ if (type !== 'local' && type !== 'session') {
500
+ err('Usage: agent-browser storage <local|session> [get|set|clear] [key] [value]');
501
+ }
502
+ if (sub === 'set') {
503
+ if (!args[2] || !args[3])
504
+ err(`Usage: agent-browser storage ${type} set <key> <value>`);
505
+ return { id, action: 'storage_set', type, key: args[2], value: args[3] };
506
+ }
507
+ else if (sub === 'clear') {
508
+ return { id, action: 'storage_clear', type };
509
+ }
510
+ else {
511
+ // get (default)
512
+ return { id, action: 'storage_get', type, key: sub };
513
+ }
514
+ }
515
+ async function handleCookies(args, id) {
516
+ const sub = args[0];
517
+ if (sub === 'set') {
518
+ if (!args[1])
519
+ err('Usage: agent-browser cookies set <json>');
520
+ try {
521
+ return { id, action: 'cookies_set', cookies: JSON.parse(args[1]) };
522
+ }
523
+ catch {
524
+ err('Invalid JSON for cookies');
525
+ }
526
+ }
527
+ else if (sub === 'clear') {
528
+ return { id, action: 'cookies_clear' };
529
+ }
530
+ else {
531
+ return { id, action: 'cookies_get' };
532
+ }
533
+ return {};
534
+ }
535
+ async function handleTab(args, id) {
536
+ const sub = args[0];
537
+ if (sub === 'new') {
538
+ return { id, action: 'tab_new' };
539
+ }
540
+ else if (sub === 'list' || sub === 'ls' || !sub) {
541
+ return { id, action: 'tab_list' };
542
+ }
543
+ else if (sub === 'close') {
544
+ const idx = args[1] !== undefined ? parseInt(args[1], 10) : undefined;
545
+ return { id, action: 'tab_close', index: idx };
546
+ }
547
+ else {
548
+ const idx = parseInt(sub, 10);
549
+ if (isNaN(idx))
550
+ err(`Unknown: agent-browser tab ${sub}. Options: new, list, close, <index>`);
551
+ return { id, action: 'tab_switch', index: idx };
552
+ }
553
+ }
554
+ async function handleTrace(args, id) {
555
+ const sub = args[0];
556
+ if (sub === 'start') {
557
+ return { id, action: 'trace_start', screenshots: true, snapshots: true };
558
+ }
559
+ else if (sub === 'stop') {
560
+ if (!args[1])
561
+ err('Usage: agent-browser trace stop <path>');
562
+ return { id, action: 'trace_stop', path: args[1] };
563
+ }
564
+ else {
565
+ err('Usage: agent-browser trace start|stop');
566
+ }
567
+ return {};
568
+ }
569
+ async function handleState(args, id) {
570
+ const sub = args[0];
571
+ const path = args[1];
572
+ if (sub === 'save') {
573
+ if (!path)
574
+ err('Usage: agent-browser state save <path>');
575
+ return { id, action: 'state_save', path };
576
+ }
577
+ else if (sub === 'load') {
578
+ if (!path)
579
+ err('Usage: agent-browser state load <path>');
580
+ return { id, action: 'state_load', path };
581
+ }
582
+ else {
583
+ err('Usage: agent-browser state save|load <path>');
584
+ }
585
+ return {};
586
+ }
587
+ function parseFlags(args) {
588
+ const flags = {
589
+ json: false,
590
+ full: false,
591
+ text: false,
592
+ debug: false,
593
+ headed: false,
594
+ session: process.env.AGENT_BROWSER_SESSION || 'default',
595
+ exact: false,
596
+ };
597
+ const cleanArgs = [];
598
+ let i = 0;
599
+ while (i < args.length) {
600
+ const arg = args[i];
601
+ if (arg === '--json') {
602
+ flags.json = true;
603
+ }
604
+ else if (arg === '--full' || arg === '-f') {
605
+ flags.full = true;
606
+ }
607
+ else if (arg === '--text' || arg === '-t') {
608
+ flags.text = true;
609
+ }
610
+ else if (arg === '--debug') {
611
+ flags.debug = true;
612
+ }
613
+ else if (arg === '--headed' || arg === '--head') {
614
+ flags.headed = true;
615
+ }
616
+ else if (arg === '--exact') {
617
+ flags.exact = true;
618
+ }
619
+ else if (arg === '--session' && args[i + 1]) {
620
+ flags.session = args[++i];
621
+ }
622
+ else if ((arg === '--selector' || arg === '-s') && args[i + 1]) {
623
+ flags.selector = args[++i];
624
+ }
625
+ else if ((arg === '--name' || arg === '-n') && args[i + 1]) {
626
+ flags.name = args[++i];
627
+ }
628
+ else if (arg === '--url' && args[i + 1]) {
629
+ flags.url = args[++i];
630
+ }
631
+ else if (arg === '--load' && args[i + 1]) {
632
+ flags.load = args[++i];
633
+ }
634
+ else if ((arg === '--fn' || arg === '--function') && args[i + 1]) {
635
+ flags.fn = args[++i];
636
+ }
637
+ else if (!arg.startsWith('-')) {
638
+ cleanArgs.push(arg);
639
+ }
640
+ i++;
641
+ }
642
+ return { flags, cleanArgs };
643
+ }
644
+ // ============================================================================
645
+ // Main
646
+ // ============================================================================
647
+ async function main() {
648
+ const rawArgs = process.argv.slice(2);
649
+ const { flags, cleanArgs } = parseFlags(rawArgs);
650
+ if (flags.debug)
651
+ setDebug(true);
652
+ setSession(flags.session);
653
+ if (cleanArgs.length === 0 || rawArgs.includes('--help') || rawArgs.includes('-h')) {
654
+ printHelp();
655
+ process.exit(0);
656
+ }
657
+ const command = cleanArgs[0];
658
+ const args = cleanArgs.slice(1);
659
+ const id = genId();
660
+ let cmd;
661
+ switch (command) {
662
+ // === Core Commands ===
663
+ case 'open':
664
+ case 'goto':
665
+ case 'navigate': {
666
+ if (!args[0])
667
+ err('URL required');
668
+ const url = args[0].startsWith('http') ? args[0] : `https://${args[0]}`;
669
+ // If --headed, launch with headless=false first
670
+ if (flags.headed) {
671
+ await send({ id: genId(), action: 'launch', headless: false });
672
+ }
673
+ cmd = { id, action: 'navigate', url };
674
+ break;
675
+ }
676
+ case 'click':
677
+ if (!args[0])
678
+ err('Selector required');
679
+ cmd = { id, action: 'click', selector: args[0] };
680
+ break;
681
+ case 'dblclick':
682
+ if (!args[0])
683
+ err('Selector required');
684
+ cmd = { id, action: 'dblclick', selector: args[0] };
685
+ break;
686
+ case 'type':
687
+ if (!args[0] || !args[1])
688
+ err('Usage: agent-browser type <selector> <text>');
689
+ cmd = { id, action: 'type', selector: args[0], text: args.slice(1).join(' ') };
690
+ break;
691
+ case 'fill':
692
+ if (!args[0] || !args[1])
693
+ err('Usage: agent-browser fill <selector> <text>');
694
+ cmd = { id, action: 'fill', selector: args[0], value: args.slice(1).join(' ') };
695
+ break;
696
+ case 'press':
697
+ case 'key':
698
+ if (!args[0])
699
+ err('Key required');
700
+ cmd = { id, action: 'press', key: args[0] };
701
+ break;
702
+ case 'keydown':
703
+ if (!args[0])
704
+ err('Key required');
705
+ cmd = { id, action: 'keydown', key: args[0] };
706
+ break;
707
+ case 'keyup':
708
+ if (!args[0])
709
+ err('Key required');
710
+ cmd = { id, action: 'keyup', key: args[0] };
711
+ break;
712
+ case 'hover':
713
+ if (!args[0])
714
+ err('Selector required');
715
+ cmd = { id, action: 'hover', selector: args[0] };
716
+ break;
717
+ case 'focus':
718
+ if (!args[0])
719
+ err('Selector required');
720
+ cmd = { id, action: 'focus', selector: args[0] };
721
+ break;
722
+ case 'check':
723
+ if (!args[0])
724
+ err('Selector required');
725
+ cmd = { id, action: 'check', selector: args[0] };
726
+ break;
727
+ case 'uncheck':
728
+ if (!args[0])
729
+ err('Selector required');
730
+ cmd = { id, action: 'uncheck', selector: args[0] };
731
+ break;
732
+ case 'select':
733
+ if (!args[0] || !args[1])
734
+ err('Usage: agent-browser select <selector> <value>');
735
+ cmd = { id, action: 'select', selector: args[0], value: args[1] };
736
+ break;
737
+ case 'drag':
738
+ if (!args[0] || !args[1])
739
+ err('Usage: agent-browser drag <source> <target>');
740
+ cmd = { id, action: 'drag', source: args[0], target: args[1] };
741
+ break;
742
+ case 'upload':
743
+ if (!args[0] || !args[1])
744
+ err('Usage: agent-browser upload <selector> <files...>');
745
+ cmd = { id, action: 'upload', selector: args[0], files: args.slice(1) };
746
+ break;
747
+ case 'scroll': {
748
+ const dir = args[0] || 'down';
749
+ const amount = parseInt(args[1], 10) || 300;
750
+ cmd = { id, action: 'scroll', direction: dir, amount, selector: flags.selector };
751
+ break;
752
+ }
753
+ case 'wait': {
754
+ const target = args[0];
755
+ // Check for flags
756
+ if (flags.fn) {
757
+ cmd = { id, action: 'waitforfunction', expression: flags.fn };
758
+ }
759
+ else if (flags.url) {
760
+ cmd = { id, action: 'waitforurl', url: flags.url };
761
+ }
762
+ else if (flags.load) {
763
+ cmd = { id, action: 'waitforloadstate', state: flags.load };
764
+ }
765
+ else if (flags.text) {
766
+ if (!target)
767
+ err('Text required with --text flag');
768
+ cmd = { id, action: 'wait', text: target };
769
+ }
770
+ else if (target && /^\d+$/.test(target)) {
771
+ cmd = { id, action: 'wait', timeout: parseInt(target, 10) };
772
+ }
773
+ else if (target) {
774
+ cmd = { id, action: 'wait', selector: target };
775
+ }
776
+ else {
777
+ err('Usage: agent-browser wait <selector|ms|--text|--url|--load|--fn>');
778
+ }
779
+ break;
780
+ }
781
+ case 'screenshot': {
782
+ const path = args[0];
783
+ cmd = { id, action: 'screenshot', path, fullPage: flags.full, selector: flags.selector };
784
+ break;
785
+ }
786
+ case 'pdf':
787
+ if (!args[0])
788
+ err('Path required');
789
+ cmd = { id, action: 'pdf', path: args[0] };
790
+ break;
791
+ case 'snapshot':
792
+ cmd = { id, action: 'snapshot' };
793
+ break;
794
+ case 'eval':
795
+ if (!args[0])
796
+ err('Script required');
797
+ cmd = { id, action: 'evaluate', script: args.join(' ') };
798
+ break;
799
+ case 'close':
800
+ case 'quit':
801
+ case 'exit':
802
+ cmd = { id, action: 'close' };
803
+ break;
804
+ // === Navigation ===
805
+ case 'back':
806
+ cmd = { id, action: 'back' };
807
+ break;
808
+ case 'forward':
809
+ cmd = { id, action: 'forward' };
810
+ break;
811
+ case 'reload':
812
+ cmd = { id, action: 'reload' };
813
+ break;
814
+ // === Grouped Commands ===
815
+ case 'get':
816
+ cmd = await handleGet(args, id);
817
+ break;
818
+ case 'is':
819
+ cmd = await handleIs(args, id);
820
+ break;
821
+ case 'find':
822
+ cmd = await handleFind(args, id, flags);
823
+ break;
824
+ case 'mouse':
825
+ cmd = await handleMouse(args, id);
826
+ break;
827
+ case 'set':
828
+ cmd = await handleSet(args, id);
829
+ break;
830
+ case 'network':
831
+ cmd = await handleNetwork(args, id, rawArgs);
832
+ break;
833
+ case 'storage':
834
+ cmd = await handleStorage(args, id);
835
+ break;
836
+ case 'cookies':
837
+ cmd = await handleCookies(args, id);
838
+ break;
839
+ case 'tab':
840
+ cmd = await handleTab(args, id);
841
+ break;
842
+ case 'window':
843
+ if (args[0] === 'new') {
844
+ cmd = { id, action: 'window_new' };
845
+ }
846
+ else {
847
+ err('Usage: agent-browser window new');
848
+ }
849
+ break;
850
+ case 'frame':
851
+ if (!args[0])
852
+ err('Selector required');
853
+ if (args[0] === 'main') {
854
+ cmd = { id, action: 'mainframe' };
855
+ }
856
+ else {
857
+ cmd = { id, action: 'frame', selector: args[0] };
858
+ }
859
+ break;
860
+ case 'dialog':
861
+ if (args[0] === 'accept') {
862
+ cmd = { id, action: 'dialog', response: 'accept', promptText: args[1] };
863
+ }
864
+ else if (args[0] === 'dismiss') {
865
+ cmd = { id, action: 'dialog', response: 'dismiss' };
866
+ }
867
+ else {
868
+ err('Usage: agent-browser dialog accept|dismiss');
869
+ }
870
+ break;
871
+ case 'trace':
872
+ cmd = await handleTrace(args, id);
873
+ break;
874
+ case 'state':
875
+ cmd = await handleState(args, id);
876
+ break;
877
+ case 'console':
878
+ cmd = { id, action: 'console', clear: rawArgs.includes('--clear') };
879
+ break;
880
+ case 'errors':
881
+ cmd = { id, action: 'errors', clear: rawArgs.includes('--clear') };
882
+ break;
883
+ case 'highlight':
884
+ if (!args[0])
885
+ err('Selector required');
886
+ cmd = { id, action: 'highlight', selector: args[0] };
887
+ break;
888
+ case 'scrollintoview':
889
+ case 'scrollinto':
890
+ if (!args[0])
891
+ err('Selector required');
892
+ cmd = { id, action: 'scrollintoview', selector: args[0] };
893
+ break;
894
+ case 'initscript':
895
+ if (!args[0])
896
+ err('Script required');
897
+ cmd = { id, action: 'addinitscript', script: args.join(' ') };
898
+ break;
899
+ case 'inserttext':
900
+ case 'insert':
901
+ if (!args[0])
902
+ err('Text required');
903
+ cmd = { id, action: 'inserttext', text: args.join(' ') };
904
+ break;
905
+ case 'multiselect':
906
+ if (!args[0] || args.length < 2)
907
+ err('Usage: agent-browser multiselect <selector> <value1> [value2...]');
908
+ cmd = { id, action: 'multiselect', selector: args[0], values: args.slice(1) };
909
+ break;
910
+ case 'download':
911
+ cmd = { id, action: 'waitfordownload', path: args[0] };
912
+ break;
913
+ case 'response':
914
+ if (!args[0])
915
+ err('URL pattern required');
916
+ cmd = { id, action: 'responsebody', url: args[0] };
917
+ break;
918
+ case 'session':
919
+ if (args[0] === 'list' || args[0] === 'ls') {
920
+ const sessions = listSessions();
921
+ const current = getSession();
922
+ if (sessions.length === 0) {
923
+ console.log(c('dim', 'No active sessions'));
924
+ }
925
+ else {
926
+ sessions.forEach((s) => {
927
+ const marker = s === current ? c('green', '→') : ' ';
928
+ console.log(`${marker} ${c('cyan', s)}`);
929
+ });
930
+ }
931
+ process.exit(0);
932
+ }
933
+ else {
934
+ console.log(c('cyan', getSession()));
935
+ process.exit(0);
936
+ }
937
+ // === Legacy aliases for backwards compatibility ===
938
+ case 'url':
939
+ cmd = { id, action: 'url' };
940
+ break;
941
+ case 'title':
942
+ cmd = { id, action: 'title' };
943
+ break;
944
+ case 'gettext':
945
+ cmd = { id, action: 'gettext', selector: args[0] };
946
+ break;
947
+ case 'extract':
948
+ cmd = { id, action: 'content', selector: args[0] };
949
+ break;
950
+ default:
951
+ console.error(c('red', 'Unknown command:'), command);
952
+ console.error(c('dim', 'Run: agent-browser --help'));
953
+ process.exit(1);
954
+ }
955
+ try {
956
+ const response = await send(cmd);
957
+ printResponse(response, flags.json);
958
+ process.exit(0);
959
+ }
960
+ catch (error) {
961
+ const message = error instanceof Error ? error.message : String(error);
962
+ if (flags.json) {
963
+ console.log(JSON.stringify({ id, success: false, error: message }));
964
+ }
965
+ else {
966
+ console.error(c('red', '✗ Error:'), message);
967
+ }
968
+ process.exit(1);
969
+ }
970
+ }
971
+ main();
972
+ //# sourceMappingURL=index.js.map