agent-browser 0.1.3 → 0.2.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.
- package/AGENTS.md +26 -0
- package/README.md +68 -11
- package/benchmark/benchmark.ts +521 -0
- package/benchmark/run.ts +322 -0
- package/bin/agent-browser +0 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +46 -35
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +30 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +59 -0
- package/dist/browser.js.map +1 -1
- package/dist/cli-light.d.ts +11 -0
- package/dist/cli-light.d.ts.map +1 -0
- package/dist/cli-light.js +409 -0
- package/dist/cli-light.js.map +1 -0
- package/dist/index.js +13 -8
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -1
- package/dist/snapshot.d.ts +63 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +298 -0
- package/dist/snapshot.js.map +1 -0
- package/package.json +5 -4
- package/src/actions.ts +50 -36
- package/src/browser.ts +70 -0
- package/src/cli-light.ts +457 -0
- package/src/index.ts +13 -8
- package/src/protocol.ts +4 -0
- package/src/snapshot.ts +380 -0
- package/tsconfig.json +12 -3
package/src/cli-light.ts
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight CLI client for agent-browser
|
|
4
|
+
*
|
|
5
|
+
* This file contains ONLY the client logic (no Playwright imports).
|
|
6
|
+
* It can be compiled with Bun for fast startup times.
|
|
7
|
+
*
|
|
8
|
+
* The actual browser automation runs in a separate daemon process.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as net from 'net';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Configuration
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
const SESSION = process.env.AGENT_BROWSER_SESSION || 'default';
|
|
22
|
+
const SOCKET_PATH = path.join(os.tmpdir(), `agent-browser-${SESSION}.sock`);
|
|
23
|
+
const PID_FILE = path.join(os.tmpdir(), `agent-browser-${SESSION}.pid`);
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Daemon Management
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
function isDaemonRunning(): boolean {
|
|
30
|
+
if (!fs.existsSync(PID_FILE)) return false;
|
|
31
|
+
try {
|
|
32
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
33
|
+
process.kill(pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function ensureDaemon(): Promise<void> {
|
|
41
|
+
if (isDaemonRunning() && fs.existsSync(SOCKET_PATH)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Find the daemon script - look relative to this script
|
|
46
|
+
const scriptDir = path.dirname(process.argv[1]);
|
|
47
|
+
let daemonPath = path.join(scriptDir, 'daemon.js');
|
|
48
|
+
|
|
49
|
+
// Fallback paths
|
|
50
|
+
if (!fs.existsSync(daemonPath)) {
|
|
51
|
+
daemonPath = path.join(scriptDir, '../dist/daemon.js');
|
|
52
|
+
}
|
|
53
|
+
if (!fs.existsSync(daemonPath)) {
|
|
54
|
+
daemonPath = path.join(process.cwd(), 'dist/daemon.js');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(daemonPath)) {
|
|
58
|
+
throw new Error(`Daemon not found. Looked in: ${daemonPath}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const child = spawn('node', [daemonPath], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: 'ignore',
|
|
64
|
+
env: { ...process.env, AGENT_BROWSER_DAEMON: '1', AGENT_BROWSER_SESSION: SESSION },
|
|
65
|
+
});
|
|
66
|
+
child.unref();
|
|
67
|
+
|
|
68
|
+
// Wait for socket
|
|
69
|
+
for (let i = 0; i < 50; i++) {
|
|
70
|
+
if (fs.existsSync(SOCKET_PATH)) return;
|
|
71
|
+
await new Promise(r => setTimeout(r, 100));
|
|
72
|
+
}
|
|
73
|
+
throw new Error('Failed to start daemon');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Command Execution
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
interface Response {
|
|
81
|
+
id: string;
|
|
82
|
+
success: boolean;
|
|
83
|
+
data?: unknown;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function sendCommand(cmd: Record<string, unknown>): Promise<Response> {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
let buffer = '';
|
|
90
|
+
let resolved = false;
|
|
91
|
+
const socket = net.createConnection(SOCKET_PATH);
|
|
92
|
+
|
|
93
|
+
const cleanup = () => {
|
|
94
|
+
socket.removeAllListeners();
|
|
95
|
+
socket.destroy();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
socket.on('connect', () => {
|
|
99
|
+
socket.write(JSON.stringify(cmd) + '\n');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
socket.on('data', (data) => {
|
|
103
|
+
buffer += data.toString();
|
|
104
|
+
const idx = buffer.indexOf('\n');
|
|
105
|
+
if (idx !== -1 && !resolved) {
|
|
106
|
+
resolved = true;
|
|
107
|
+
try {
|
|
108
|
+
const response = JSON.parse(buffer.substring(0, idx)) as Response;
|
|
109
|
+
cleanup();
|
|
110
|
+
resolve(response);
|
|
111
|
+
} catch {
|
|
112
|
+
cleanup();
|
|
113
|
+
reject(new Error('Invalid JSON response'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
socket.on('error', (err) => {
|
|
119
|
+
if (!resolved) {
|
|
120
|
+
resolved = true;
|
|
121
|
+
cleanup();
|
|
122
|
+
reject(err);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
socket.on('close', () => {
|
|
127
|
+
if (!resolved && buffer.trim()) {
|
|
128
|
+
resolved = true;
|
|
129
|
+
try {
|
|
130
|
+
resolve(JSON.parse(buffer.trim()) as Response);
|
|
131
|
+
} catch {
|
|
132
|
+
reject(new Error('Connection closed'));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
if (!resolved) {
|
|
139
|
+
resolved = true;
|
|
140
|
+
cleanup();
|
|
141
|
+
reject(new Error('Timeout'));
|
|
142
|
+
}
|
|
143
|
+
}, 30000);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Command Parsing
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
function parseCommand(parts: string[]): Record<string, unknown> | null {
|
|
152
|
+
if (parts.length === 0) return null;
|
|
153
|
+
|
|
154
|
+
const command = parts[0];
|
|
155
|
+
const rest = parts.slice(1);
|
|
156
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
157
|
+
|
|
158
|
+
switch (command) {
|
|
159
|
+
case 'open':
|
|
160
|
+
case 'goto':
|
|
161
|
+
case 'navigate':
|
|
162
|
+
return { id, action: 'navigate', url: rest[0]?.startsWith('http') ? rest[0] : `https://${rest[0]}` };
|
|
163
|
+
|
|
164
|
+
case 'click':
|
|
165
|
+
return { id, action: 'click', selector: rest[0] };
|
|
166
|
+
|
|
167
|
+
case 'fill':
|
|
168
|
+
return { id, action: 'fill', selector: rest[0], value: rest.slice(1).join(' ') };
|
|
169
|
+
|
|
170
|
+
case 'type':
|
|
171
|
+
return { id, action: 'type', selector: rest[0], text: rest.slice(1).join(' ') };
|
|
172
|
+
|
|
173
|
+
case 'hover':
|
|
174
|
+
return { id, action: 'hover', selector: rest[0] };
|
|
175
|
+
|
|
176
|
+
case 'snapshot': {
|
|
177
|
+
const opts: Record<string, unknown> = { id, action: 'snapshot' };
|
|
178
|
+
// Parse snapshot options from rest args
|
|
179
|
+
for (let i = 0; i < rest.length; i++) {
|
|
180
|
+
const arg = rest[i];
|
|
181
|
+
if (arg === '-i' || arg === '--interactive') {
|
|
182
|
+
opts.interactive = true;
|
|
183
|
+
} else if (arg === '-c' || arg === '--compact') {
|
|
184
|
+
opts.compact = true;
|
|
185
|
+
} else if (arg === '--depth' || arg === '-d') {
|
|
186
|
+
opts.maxDepth = parseInt(rest[++i], 10);
|
|
187
|
+
} else if (arg === '--selector' || arg === '-s') {
|
|
188
|
+
opts.selector = rest[++i];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return opts;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case 'screenshot':
|
|
195
|
+
return { id, action: 'screenshot', path: rest[0] };
|
|
196
|
+
|
|
197
|
+
case 'close':
|
|
198
|
+
case 'quit':
|
|
199
|
+
return { id, action: 'close' };
|
|
200
|
+
|
|
201
|
+
case 'get':
|
|
202
|
+
if (rest[0] === 'text') return { id, action: 'gettext', selector: rest[1] };
|
|
203
|
+
if (rest[0] === 'url') return { id, action: 'url' };
|
|
204
|
+
if (rest[0] === 'title') return { id, action: 'title' };
|
|
205
|
+
return null;
|
|
206
|
+
|
|
207
|
+
case 'press':
|
|
208
|
+
return { id, action: 'press', key: rest[0] };
|
|
209
|
+
|
|
210
|
+
case 'wait':
|
|
211
|
+
if (/^\d+$/.test(rest[0])) {
|
|
212
|
+
return { id, action: 'wait', timeout: parseInt(rest[0], 10) };
|
|
213
|
+
}
|
|
214
|
+
return { id, action: 'wait', selector: rest[0] };
|
|
215
|
+
|
|
216
|
+
case 'back':
|
|
217
|
+
return { id, action: 'back' };
|
|
218
|
+
|
|
219
|
+
case 'forward':
|
|
220
|
+
return { id, action: 'forward' };
|
|
221
|
+
|
|
222
|
+
case 'reload':
|
|
223
|
+
return { id, action: 'reload' };
|
|
224
|
+
|
|
225
|
+
case 'eval':
|
|
226
|
+
return { id, action: 'evaluate', script: rest.join(' ') };
|
|
227
|
+
|
|
228
|
+
default:
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function parseBatchCommands(args: string[]): Record<string, unknown>[] {
|
|
234
|
+
const commands: Record<string, unknown>[] = [];
|
|
235
|
+
|
|
236
|
+
// Each argument after 'batch' is a command string
|
|
237
|
+
for (const arg of args) {
|
|
238
|
+
// Split the command string into parts
|
|
239
|
+
const parts = arg.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
|
|
240
|
+
const cleanParts = parts.map(p => p.replace(/^"|"$/g, ''));
|
|
241
|
+
|
|
242
|
+
const cmd = parseCommand(cleanParts);
|
|
243
|
+
if (cmd) {
|
|
244
|
+
commands.push(cmd);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return commands;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Output Formatting
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
function formatResponse(response: Response): string {
|
|
256
|
+
if (!response.success) {
|
|
257
|
+
return `\x1b[31m✗ Error:\x1b[0m ${response.error}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const data = response.data as Record<string, unknown>;
|
|
261
|
+
|
|
262
|
+
if (data?.url && data?.title) {
|
|
263
|
+
return `\x1b[32m✓\x1b[0m \x1b[1m${data.title}\x1b[0m\n\x1b[2m ${data.url}\x1b[0m`;
|
|
264
|
+
} else if (data?.snapshot) {
|
|
265
|
+
return String(data.snapshot);
|
|
266
|
+
} else if (data?.text !== undefined) {
|
|
267
|
+
return String(data.text);
|
|
268
|
+
} else if (data?.url) {
|
|
269
|
+
return String(data.url);
|
|
270
|
+
} else if (data?.title) {
|
|
271
|
+
return String(data.title);
|
|
272
|
+
} else if (data?.result !== undefined) {
|
|
273
|
+
return typeof data.result === 'object' ? JSON.stringify(data.result, null, 2) : String(data.result);
|
|
274
|
+
} else if (data?.closed) {
|
|
275
|
+
return '\x1b[32m✓\x1b[0m Browser closed';
|
|
276
|
+
} else {
|
|
277
|
+
return '\x1b[32m✓\x1b[0m Done';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function printResponse(response: Response, json: boolean): void {
|
|
282
|
+
if (json) {
|
|
283
|
+
console.log(JSON.stringify(response));
|
|
284
|
+
} else {
|
|
285
|
+
console.log(formatResponse(response));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Main
|
|
291
|
+
// ============================================================================
|
|
292
|
+
|
|
293
|
+
const HELP = `
|
|
294
|
+
agent-browser - fast browser automation CLI
|
|
295
|
+
|
|
296
|
+
Usage:
|
|
297
|
+
agent-browser <command> [args] [--json]
|
|
298
|
+
agent-browser batch <cmd1> <cmd2> ... [--json]
|
|
299
|
+
|
|
300
|
+
Commands:
|
|
301
|
+
open <url> Navigate to URL
|
|
302
|
+
click <sel> Click element (use @ref from snapshot)
|
|
303
|
+
fill <sel> <text> Fill input
|
|
304
|
+
type <sel> <text> Type text
|
|
305
|
+
hover <sel> Hover element
|
|
306
|
+
snapshot [options] Get accessibility tree with refs
|
|
307
|
+
screenshot [path] Take screenshot
|
|
308
|
+
get text <sel> Get text content
|
|
309
|
+
get url Get current URL
|
|
310
|
+
get title Get page title
|
|
311
|
+
press <key> Press keyboard key
|
|
312
|
+
wait <ms|sel> Wait for time or element
|
|
313
|
+
eval <js> Evaluate JavaScript
|
|
314
|
+
close Close browser
|
|
315
|
+
|
|
316
|
+
Snapshot Options:
|
|
317
|
+
-i, --interactive Only show interactive elements (buttons, links, inputs)
|
|
318
|
+
-c, --compact Remove empty structural elements
|
|
319
|
+
-d, --depth <n> Limit tree depth (e.g., --depth 3)
|
|
320
|
+
-s, --selector <sel> Scope snapshot to CSS selector
|
|
321
|
+
|
|
322
|
+
Batch Mode:
|
|
323
|
+
batch <cmd1> <cmd2> ... Execute multiple commands in sequence
|
|
324
|
+
Each command is a quoted string
|
|
325
|
+
|
|
326
|
+
Options:
|
|
327
|
+
--json Output JSON (for AI agents)
|
|
328
|
+
|
|
329
|
+
Examples:
|
|
330
|
+
agent-browser open example.com
|
|
331
|
+
agent-browser snapshot
|
|
332
|
+
agent-browser click @e2
|
|
333
|
+
agent-browser fill @e3 "hello"
|
|
334
|
+
|
|
335
|
+
# Batch mode - execute multiple commands efficiently
|
|
336
|
+
agent-browser batch "open example.com" "snapshot" "click a"
|
|
337
|
+
agent-browser batch "open google.com" "snapshot" "get title" --json
|
|
338
|
+
`;
|
|
339
|
+
|
|
340
|
+
async function runBatch(commands: Record<string, unknown>[], json: boolean): Promise<void> {
|
|
341
|
+
const results: Response[] = [];
|
|
342
|
+
let hasError = false;
|
|
343
|
+
|
|
344
|
+
for (const cmd of commands) {
|
|
345
|
+
try {
|
|
346
|
+
const response = await sendCommand(cmd);
|
|
347
|
+
results.push(response);
|
|
348
|
+
|
|
349
|
+
if (!json) {
|
|
350
|
+
// Print each result as we go for non-JSON mode
|
|
351
|
+
console.log(`\x1b[36m[${cmd.action}]\x1b[0m`);
|
|
352
|
+
console.log(formatResponse(response));
|
|
353
|
+
console.log();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!response.success) {
|
|
357
|
+
hasError = true;
|
|
358
|
+
break; // Stop on first error
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
const errorResponse: Response = {
|
|
363
|
+
id: String(cmd.id),
|
|
364
|
+
success: false,
|
|
365
|
+
error: message,
|
|
366
|
+
};
|
|
367
|
+
results.push(errorResponse);
|
|
368
|
+
hasError = true;
|
|
369
|
+
|
|
370
|
+
if (!json) {
|
|
371
|
+
console.log(`\x1b[36m[${cmd.action}]\x1b[0m`);
|
|
372
|
+
console.log(`\x1b[31m✗ Error:\x1b[0m ${message}`);
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (json) {
|
|
379
|
+
console.log(JSON.stringify({
|
|
380
|
+
success: !hasError,
|
|
381
|
+
results,
|
|
382
|
+
completed: results.length,
|
|
383
|
+
total: commands.length,
|
|
384
|
+
}));
|
|
385
|
+
} else {
|
|
386
|
+
console.log(`\x1b[2m─────────────────────────────────────\x1b[0m`);
|
|
387
|
+
console.log(`Completed ${results.length}/${commands.length} commands`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
process.exit(hasError ? 1 : 0);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function main(): Promise<void> {
|
|
394
|
+
const args = process.argv.slice(2);
|
|
395
|
+
const json = args.includes('--json');
|
|
396
|
+
const cleanArgs = args.filter(a => !a.startsWith('--'));
|
|
397
|
+
|
|
398
|
+
if (cleanArgs.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
399
|
+
console.log(HELP);
|
|
400
|
+
process.exit(0);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check for batch mode
|
|
404
|
+
if (cleanArgs[0] === 'batch') {
|
|
405
|
+
const batchArgs = cleanArgs.slice(1);
|
|
406
|
+
if (batchArgs.length === 0) {
|
|
407
|
+
console.error('\x1b[31mBatch mode requires at least one command\x1b[0m');
|
|
408
|
+
console.log('\nExample: agent-browser batch "open example.com" "snapshot"');
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const commands = parseBatchCommands(batchArgs);
|
|
413
|
+
if (commands.length === 0) {
|
|
414
|
+
console.error('\x1b[31mNo valid commands found\x1b[0m');
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
await ensureDaemon();
|
|
420
|
+
await runBatch(commands, json);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
423
|
+
if (json) {
|
|
424
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
425
|
+
} else {
|
|
426
|
+
console.error('\x1b[31m✗ Error:\x1b[0m', message);
|
|
427
|
+
}
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Single command mode
|
|
434
|
+
const cmd = parseCommand(cleanArgs);
|
|
435
|
+
|
|
436
|
+
if (!cmd) {
|
|
437
|
+
console.error('\x1b[31mUnknown command:\x1b[0m', cleanArgs[0]);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
await ensureDaemon();
|
|
443
|
+
const response = await sendCommand(cmd);
|
|
444
|
+
printResponse(response, json);
|
|
445
|
+
process.exit(response.success ? 0 : 1);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
448
|
+
if (json) {
|
|
449
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
450
|
+
} else {
|
|
451
|
+
console.error('\x1b[31m✗ Error:\x1b[0m', message);
|
|
452
|
+
}
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
main();
|
package/src/index.ts
CHANGED
|
@@ -230,7 +230,7 @@ ${c('yellow', 'Usage:')} agent-browser <command> [options]
|
|
|
230
230
|
|
|
231
231
|
${c('yellow', 'Core Commands:')}
|
|
232
232
|
${c('cyan', 'open')} <url> Navigate to URL
|
|
233
|
-
${c('cyan', 'click')} <sel> Click element
|
|
233
|
+
${c('cyan', 'click')} <sel> Click element (or @ref)
|
|
234
234
|
${c('cyan', 'type')} <sel> <text> Type into element
|
|
235
235
|
${c('cyan', 'fill')} <sel> <text> Clear and fill
|
|
236
236
|
${c('cyan', 'press')} <key> Press key (Enter, Tab, Control+a)
|
|
@@ -239,10 +239,16 @@ ${c('yellow', 'Core Commands:')}
|
|
|
239
239
|
${c('cyan', 'scroll')} <dir> [px] Scroll (up/down/left/right)
|
|
240
240
|
${c('cyan', 'wait')} <sel|ms> Wait for element or time
|
|
241
241
|
${c('cyan', 'screenshot')} [path] Take screenshot
|
|
242
|
-
${c('cyan', 'snapshot')} Accessibility tree (for AI)
|
|
242
|
+
${c('cyan', 'snapshot')} Accessibility tree with refs (for AI)
|
|
243
243
|
${c('cyan', 'eval')} <js> Run JavaScript
|
|
244
244
|
${c('cyan', 'close')} Close browser
|
|
245
245
|
|
|
246
|
+
${c('yellow', 'Selectors:')} CSS, XPath, text=, or ${c('green', '@ref')} from snapshot
|
|
247
|
+
${c('dim', 'CSS:')} "#id", ".class", "button"
|
|
248
|
+
${c('dim', 'XPath:')} "xpath=//button"
|
|
249
|
+
${c('dim', 'Text:')} "text=Submit"
|
|
250
|
+
${c('dim', 'Ref:')} ${c('green', '@e1')}, ${c('green', '@e2')} (from snapshot output)
|
|
251
|
+
|
|
246
252
|
${c('yellow', 'Get Info:')} agent-browser get <what> [selector]
|
|
247
253
|
text, html, value, attr, title, url, count, box
|
|
248
254
|
|
|
@@ -286,13 +292,12 @@ ${c('yellow', 'Options:')}
|
|
|
286
292
|
|
|
287
293
|
${c('yellow', 'Examples:')}
|
|
288
294
|
agent-browser open example.com
|
|
289
|
-
agent-browser
|
|
290
|
-
agent-browser
|
|
291
|
-
agent-browser
|
|
292
|
-
agent-browser
|
|
295
|
+
agent-browser snapshot # Get tree with refs
|
|
296
|
+
agent-browser click @e2 # Click by ref from snapshot
|
|
297
|
+
agent-browser fill @e3 "test@example.com" # Fill by ref
|
|
298
|
+
agent-browser click "#submit" # CSS selector still works
|
|
299
|
+
agent-browser get text @e1 # Get text by ref
|
|
293
300
|
agent-browser find role button click --name Submit
|
|
294
|
-
agent-browser wait 2000
|
|
295
|
-
agent-browser wait --load networkidle
|
|
296
301
|
`);
|
|
297
302
|
}
|
|
298
303
|
|
package/src/protocol.ts
CHANGED
|
@@ -601,6 +601,10 @@ const screenshotSchema = baseCommandSchema.extend({
|
|
|
601
601
|
|
|
602
602
|
const snapshotSchema = baseCommandSchema.extend({
|
|
603
603
|
action: z.literal('snapshot'),
|
|
604
|
+
interactive: z.boolean().optional(),
|
|
605
|
+
maxDepth: z.number().nonnegative().optional(),
|
|
606
|
+
compact: z.boolean().optional(),
|
|
607
|
+
selector: z.string().optional(),
|
|
604
608
|
});
|
|
605
609
|
|
|
606
610
|
const evaluateSchema = baseCommandSchema.extend({
|