hyper-agent-browser 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.
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from "commander";
3
+ import { BrowserContextManager } from "./browser/context";
4
+ import * as actionCommands from "./commands/actions";
5
+ import * as infoCommands from "./commands/info";
6
+ import * as navigationCommands from "./commands/navigation";
7
+ import * as sessionCommands from "./commands/session";
8
+ import * as configCommands from "./commands/config";
9
+ import { SessionManager } from "./session/manager";
10
+ import { ReferenceStore } from "./snapshot/reference-store";
11
+ import { formatError, getExitCode } from "./utils/errors";
12
+
13
+ const program = new Command();
14
+
15
+ // Global state
16
+ let sessionManager: SessionManager;
17
+ let currentSessionName: string;
18
+ let browserContext: BrowserContextManager | null = null;
19
+ let referenceStore: ReferenceStore | null = null;
20
+
21
+ program
22
+ .name("hba")
23
+ .description("hyperagentbrowser - Browser automation CLI for AI Agents")
24
+ .version("0.1.0");
25
+
26
+ // Global options
27
+ program
28
+ .option("-s, --session <name>", "Session name", "default")
29
+ .option("-H, --headed", "Show browser window", false)
30
+ .option("-c, --channel <browser>", "Browser channel (chrome/msedge/chromium)", "chrome")
31
+ .option("-t, --timeout <ms>", "Timeout in milliseconds", "30000")
32
+ .option("-v, --verbose", "Verbose output", false);
33
+
34
+ // Navigation commands
35
+ program
36
+ .command("open <url>")
37
+ .description("Open a URL")
38
+ .option("--wait-until <state>", "Wait until state (load/domcontentloaded/networkidle)", "load")
39
+ .action(async (url, options, command) => {
40
+ try {
41
+ await initSession(command.parent);
42
+ const page = await browserContext!.getPage(getBrowserOptions(command.parent));
43
+
44
+ await withOperationIndicator(`打开页面 ${url}`, async () => {
45
+ await navigationCommands.open(page, url, {
46
+ waitUntil: options.waitUntil as any,
47
+ timeout: getTimeout(command.parent),
48
+ });
49
+ });
50
+
51
+ await updateSessionInfo();
52
+ console.log(`Opened: ${url}`);
53
+ } catch (error) {
54
+ handleError(error);
55
+ }
56
+ });
57
+
58
+ program
59
+ .command("reload")
60
+ .description("Reload current page")
61
+ .action(async (_options, command) => {
62
+ try {
63
+ await ensureConnected(command.parent);
64
+ const page = await browserContext!.getPage();
65
+ await withOperationIndicator("刷新页面", async () => {
66
+ await navigationCommands.reload(page);
67
+ });
68
+ console.log("Page reloaded");
69
+ } catch (error) {
70
+ handleError(error);
71
+ }
72
+ });
73
+
74
+ program
75
+ .command("back")
76
+ .description("Go back in history")
77
+ .action(async (_options, command) => {
78
+ try {
79
+ await ensureConnected(command.parent);
80
+ const page = await browserContext!.getPage();
81
+ await withOperationIndicator("后退", async () => {
82
+ await navigationCommands.back(page);
83
+ });
84
+ console.log("Navigated back");
85
+ } catch (error) {
86
+ handleError(error);
87
+ }
88
+ });
89
+
90
+ program
91
+ .command("forward")
92
+ .description("Go forward in history")
93
+ .action(async (_options, command) => {
94
+ try {
95
+ await ensureConnected(command.parent);
96
+ const page = await browserContext!.getPage();
97
+ await withOperationIndicator("前进", async () => {
98
+ await navigationCommands.forward(page);
99
+ });
100
+ console.log("Navigated forward");
101
+ } catch (error) {
102
+ handleError(error);
103
+ }
104
+ });
105
+
106
+ // Action commands
107
+ program
108
+ .command("click <selector>")
109
+ .description("Click an element")
110
+ .action(async (selector, _options, command) => {
111
+ try {
112
+ await ensureConnected(command.parent);
113
+ const page = await browserContext!.getPage();
114
+ await withOperationIndicator(`点击 ${selector}`, async () => {
115
+ await actionCommands.click(page, selector);
116
+ });
117
+ console.log(`Clicked: ${selector}`);
118
+ } catch (error) {
119
+ handleError(error);
120
+ }
121
+ });
122
+
123
+ program
124
+ .command("fill <selector> <value>")
125
+ .description("Fill an input field")
126
+ .action(async (selector, value, _options, command) => {
127
+ try {
128
+ await ensureConnected(command.parent);
129
+ const page = await browserContext!.getPage();
130
+ await withOperationIndicator(`填写 ${selector}`, async () => {
131
+ await actionCommands.fill(page, selector, value);
132
+ });
133
+ console.log(`Filled: ${selector}`);
134
+ } catch (error) {
135
+ handleError(error);
136
+ }
137
+ });
138
+
139
+ program
140
+ .command("type <selector> <text>")
141
+ .description("Type text into an element")
142
+ .option("--delay <ms>", "Delay between keystrokes", "0")
143
+ .action(async (selector, text, options, command) => {
144
+ try {
145
+ await ensureConnected(command.parent);
146
+ const page = await browserContext!.getPage();
147
+ await withOperationIndicator(`输入文本到 ${selector}`, async () => {
148
+ await actionCommands.type(page, selector, text, Number.parseInt(options.delay));
149
+ });
150
+ console.log(`Typed into: ${selector}`);
151
+ } catch (error) {
152
+ handleError(error);
153
+ }
154
+ });
155
+
156
+ program
157
+ .command("press <key>")
158
+ .description("Press a key")
159
+ .action(async (key, _options, command) => {
160
+ try {
161
+ await ensureConnected(command.parent);
162
+ const page = await browserContext!.getPage();
163
+ await withOperationIndicator(`按键 ${key}`, async () => {
164
+ await actionCommands.press(page, key);
165
+ });
166
+ console.log(`Pressed: ${key}`);
167
+ } catch (error) {
168
+ handleError(error);
169
+ }
170
+ });
171
+
172
+ program
173
+ .command("scroll <direction>")
174
+ .description("Scroll page (up/down/left/right)")
175
+ .option("--amount <pixels>", "Scroll amount in pixels", "500")
176
+ .option("--selector <selector>", "Scroll within element")
177
+ .action(async (direction, options, command) => {
178
+ try {
179
+ await ensureConnected(command.parent);
180
+ const page = await browserContext!.getPage();
181
+ await withOperationIndicator(`滚动 ${direction}`, async () => {
182
+ await actionCommands.scroll(
183
+ page,
184
+ direction as any,
185
+ Number.parseInt(options.amount),
186
+ options.selector,
187
+ );
188
+ });
189
+ console.log(`Scrolled ${direction}`);
190
+ } catch (error) {
191
+ handleError(error);
192
+ }
193
+ });
194
+
195
+ program
196
+ .command("hover <selector>")
197
+ .description("Hover over an element")
198
+ .action(async (selector, _options, command) => {
199
+ try {
200
+ await ensureConnected(command.parent);
201
+ const page = await browserContext!.getPage();
202
+ await withOperationIndicator(`悬停 ${selector}`, async () => {
203
+ await actionCommands.hover(page, selector);
204
+ });
205
+ console.log(`Hovered: ${selector}`);
206
+ } catch (error) {
207
+ handleError(error);
208
+ }
209
+ });
210
+
211
+ program
212
+ .command("select <selector> <value>")
213
+ .description("Select option from dropdown")
214
+ .action(async (selector, value, _options, command) => {
215
+ try {
216
+ await ensureConnected(command.parent);
217
+ const page = await browserContext!.getPage();
218
+ await withOperationIndicator(`选择 ${value}`, async () => {
219
+ await actionCommands.select(page, selector, value);
220
+ });
221
+ console.log(`Selected: ${value} in ${selector}`);
222
+ } catch (error) {
223
+ handleError(error);
224
+ }
225
+ });
226
+
227
+ program
228
+ .command("wait <condition>")
229
+ .description("Wait for condition (ms/selector=/hidden=/navigation)")
230
+ .option("--timeout <ms>", "Timeout in milliseconds")
231
+ .action(async (condition, options, command) => {
232
+ try {
233
+ await ensureConnected(command.parent);
234
+ const page = await browserContext!.getPage();
235
+
236
+ const conditionValue = /^\d+$/.test(condition) ? Number.parseInt(condition) : condition;
237
+ await actionCommands.wait(page, conditionValue, {
238
+ timeout: options.timeout ? Number.parseInt(options.timeout) : undefined,
239
+ });
240
+ console.log("Wait completed");
241
+ } catch (error) {
242
+ handleError(error);
243
+ }
244
+ });
245
+
246
+ // Info commands
247
+ program
248
+ .command("snapshot")
249
+ .description("Get page snapshot")
250
+ .option("-i, --interactive", "Show only interactive elements")
251
+ .option("-f, --full", "Show all elements")
252
+ .option("-r, --raw", "Output raw JSON")
253
+ .option("-o, --output <file>", "Output to file")
254
+ .action(async (options, command) => {
255
+ try {
256
+ await ensureConnected(command.parent);
257
+ const page = await browserContext!.getPage();
258
+
259
+ const result = await withOperationIndicator("获取页面快照", async () => {
260
+ return await infoCommands.snapshot(page, {
261
+ interactive: options.interactive,
262
+ full: options.full,
263
+ raw: options.raw,
264
+ referenceStore: referenceStore,
265
+ });
266
+ });
267
+
268
+ if (options.output) {
269
+ await Bun.write(options.output, result);
270
+ console.log(`Snapshot saved to: ${options.output}`);
271
+ } else {
272
+ console.log(result);
273
+ }
274
+ } catch (error) {
275
+ handleError(error);
276
+ }
277
+ });
278
+
279
+ program
280
+ .command("screenshot")
281
+ .description("Take a screenshot")
282
+ .option("-o, --output <file>", "Output file", "screenshot.png")
283
+ .option("--full-page", "Capture full page")
284
+ .option("--selector <selector>", "Capture specific element")
285
+ .action(async (options, command) => {
286
+ try {
287
+ await ensureConnected(command.parent);
288
+ const page = await browserContext!.getPage();
289
+
290
+ const output = await withOperationIndicator("截图", async () => {
291
+ return await infoCommands.screenshot(page, {
292
+ output: options.output,
293
+ fullPage: options.fullPage,
294
+ selector: options.selector,
295
+ });
296
+ });
297
+ console.log(`Screenshot saved to: ${output}`);
298
+ } catch (error) {
299
+ handleError(error);
300
+ }
301
+ });
302
+
303
+ program
304
+ .command("evaluate <script>")
305
+ .description("Execute JavaScript in page context")
306
+ .action(async (script, _options, command) => {
307
+ try {
308
+ await ensureConnected(command.parent);
309
+ const page = await browserContext!.getPage();
310
+ const result = await infoCommands.evaluate(page, script);
311
+ console.log(result);
312
+ } catch (error) {
313
+ handleError(error);
314
+ }
315
+ });
316
+
317
+ program
318
+ .command("url")
319
+ .description("Get current URL")
320
+ .action(async (_options, command) => {
321
+ try {
322
+ await ensureConnected(command.parent);
323
+ const page = await browserContext!.getPage();
324
+ const url = await infoCommands.url(page);
325
+ console.log(url);
326
+ } catch (error) {
327
+ handleError(error);
328
+ }
329
+ });
330
+
331
+ program
332
+ .command("title")
333
+ .description("Get page title")
334
+ .action(async (_options, command) => {
335
+ try {
336
+ await ensureConnected(command.parent);
337
+ const page = await browserContext!.getPage();
338
+ const title = await infoCommands.title(page);
339
+ console.log(title);
340
+ } catch (error) {
341
+ handleError(error);
342
+ }
343
+ });
344
+
345
+ program
346
+ .command("content")
347
+ .description("Get page text content")
348
+ .option("--selector <selector>", "Get content from specific element")
349
+ .option("--max-length <n>", "Maximum length", "10000")
350
+ .action(async (options, command) => {
351
+ try {
352
+ await ensureConnected(command.parent);
353
+ const page = await browserContext!.getPage();
354
+ const text = await infoCommands.content(page, {
355
+ selector: options.selector,
356
+ maxLength: Number.parseInt(options.maxLength),
357
+ });
358
+ console.log(text);
359
+ } catch (error) {
360
+ handleError(error);
361
+ }
362
+ });
363
+
364
+ // Session commands
365
+ program
366
+ .command("sessions")
367
+ .description("List all sessions")
368
+ .option("--json", "Output as JSON")
369
+ .action(async (options) => {
370
+ try {
371
+ sessionManager = new SessionManager();
372
+ const output = await sessionCommands.listSessions(sessionManager, {
373
+ json: options.json,
374
+ });
375
+ console.log(output);
376
+ } catch (error) {
377
+ handleError(error);
378
+ }
379
+ });
380
+
381
+ program
382
+ .command("close")
383
+ .description("Close current session")
384
+ .option("--all", "Close all sessions")
385
+ .action(async (options, command) => {
386
+ try {
387
+ sessionManager = sessionManager || new SessionManager();
388
+ const sessionName = options.all ? undefined : getSessionName(command.parent);
389
+
390
+ if (browserContext) {
391
+ await browserContext.close();
392
+ browserContext = null;
393
+ }
394
+
395
+ const output = await sessionCommands.closeSession(sessionManager, sessionName, options.all);
396
+ console.log(output);
397
+ } catch (error) {
398
+ handleError(error);
399
+ }
400
+ });
401
+
402
+ // Config commands
403
+ program
404
+ .command("config")
405
+ .description("Manage configuration")
406
+ .argument("[action]", "Action: list, get, set")
407
+ .argument("[key]", "Config key (for get/set)")
408
+ .argument("[value]", "Config value (for set)")
409
+ .action(async (action, key, value) => {
410
+ try {
411
+ if (!action || action === "list") {
412
+ const output = await configCommands.listConfig();
413
+ console.log(output);
414
+ } else if (action === "get") {
415
+ if (!key) {
416
+ throw new Error("Config key required for 'get' action");
417
+ }
418
+ const output = await configCommands.getConfig(key);
419
+ console.log(output);
420
+ } else if (action === "set") {
421
+ if (!key || !value) {
422
+ throw new Error("Config key and value required for 'set' action");
423
+ }
424
+ const output = await configCommands.setConfig(key, value);
425
+ console.log(output);
426
+ } else {
427
+ throw new Error(`Unknown config action: ${action}`);
428
+ }
429
+ } catch (error) {
430
+ handleError(error);
431
+ }
432
+ });
433
+
434
+ // Version command
435
+ program
436
+ .command("version")
437
+ .description("Show version information")
438
+ .action(() => {
439
+ console.log("hyperagentbrowser v0.1.0");
440
+ console.log(`Bun v${Bun.version}`);
441
+ console.log("Patchright v1.55.1");
442
+ });
443
+
444
+ // Helper functions
445
+ async function withOperationIndicator<T>(
446
+ operation: string,
447
+ fn: () => Promise<T>
448
+ ): Promise<T> {
449
+ try {
450
+ await browserContext?.showOperationIndicator(operation);
451
+ const result = await fn();
452
+ return result;
453
+ } finally {
454
+ await browserContext?.hideOperationIndicator();
455
+ }
456
+ }
457
+
458
+ async function initSession(parentCommand: any) {
459
+ currentSessionName = getSessionName(parentCommand);
460
+ sessionManager = new SessionManager();
461
+
462
+ const channel = getChannel(parentCommand);
463
+ const session = await sessionManager.getOrCreate(currentSessionName, channel);
464
+
465
+ // Initialize reference store for this session
466
+ referenceStore = new ReferenceStore(session);
467
+ await referenceStore.load();
468
+
469
+ // Set the global reference store for actions
470
+ actionCommands.setReferenceStore(referenceStore);
471
+
472
+ browserContext = new BrowserContextManager(session);
473
+ await browserContext.getPage(getBrowserOptions(parentCommand));
474
+
475
+ // Update session with wsEndpoint and PID
476
+ const wsEndpoint = browserContext.getWsEndpoint();
477
+ const pid = browserContext.getPid();
478
+ if (wsEndpoint) {
479
+ await sessionManager.markRunning(currentSessionName, wsEndpoint, pid);
480
+ }
481
+ }
482
+
483
+ async function ensureConnected(parentCommand: any) {
484
+ if (!browserContext) {
485
+ await initSession(parentCommand);
486
+ }
487
+ }
488
+
489
+ async function updateSessionInfo() {
490
+ if (!browserContext || !sessionManager) return;
491
+
492
+ const page = await browserContext.getPage();
493
+ const url = await page.url();
494
+ const title = await page.title();
495
+
496
+ await sessionManager.updatePageInfo(currentSessionName, url, title);
497
+ }
498
+
499
+ function getBrowserOptions(parentCommand: any) {
500
+ return {
501
+ headed: parentCommand.opts().headed,
502
+ timeout: getTimeout(parentCommand),
503
+ channel: getChannel(parentCommand),
504
+ };
505
+ }
506
+
507
+ function getSessionName(parentCommand: any): string {
508
+ return parentCommand.opts().session || "default";
509
+ }
510
+
511
+ function getChannel(parentCommand: any): "chrome" | "msedge" | "chromium" {
512
+ return parentCommand.opts().channel || "chrome";
513
+ }
514
+
515
+ function getTimeout(parentCommand: any): number {
516
+ return Number.parseInt(parentCommand.opts().timeout || "30000");
517
+ }
518
+
519
+ function handleError(error: any) {
520
+ if (error instanceof Error) {
521
+ console.error(formatError(error));
522
+ process.exit(getExitCode(error));
523
+ } else {
524
+ console.error("Error:", error);
525
+ process.exit(1);
526
+ }
527
+ }
528
+
529
+ program.parse();