agent-browser-loop 0.1.1 → 0.2.1

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.
@@ -16,15 +16,15 @@ agent-browser open <url> [options]
16
16
  | Flag | Description |
17
17
  |------|-------------|
18
18
  | `--headed` | Show browser window (default: headless) |
19
- | `--session <name>` | Named session for isolation |
20
- | `--viewport <WxH>` | Viewport size (default: 1280x720) |
19
+ | `--new, -n` | Create new session with auto-generated ID |
20
+ | `--session, -s <id>` | Target session (from `--new`) |
21
21
  | `--json` | Output as JSON |
22
22
 
23
23
  **Examples:**
24
24
  ```bash
25
25
  agent-browser open http://localhost:3000
26
26
  agent-browser open http://localhost:3000 --headed
27
- agent-browser open http://localhost:3000 --session test1 --viewport 1920x1080
27
+ agent-browser open --new http://localhost:3000 # Output: Session: swift-fox
28
28
  ```
29
29
 
30
30
  ---
@@ -40,7 +40,8 @@ agent-browser act <actions...> [options]
40
40
  **Options:**
41
41
  | Flag | Description |
42
42
  |------|-------------|
43
- | `--session <name>` | Target session |
43
+ | `--new, -n` | Create new session with auto-generated ID |
44
+ | `--session, -s <id>` | Target session (from `--new`) |
44
45
  | `--no-state` | Skip state in response |
45
46
  | `--json` | Output as JSON |
46
47
 
@@ -110,7 +111,7 @@ agent-browser wait [options]
110
111
  | `--not-text <string>` | Wait for text to disappear |
111
112
  | `--not-selector <css>` | Wait for element to disappear |
112
113
  | `--timeout <ms>` | Timeout in milliseconds (default: 30000) |
113
- | `--session <name>` | Target session |
114
+ | `--session, -s <id>` | Target session (from `--new`) |
114
115
  | `--json` | Output as JSON |
115
116
 
116
117
  **Examples:**
@@ -148,7 +149,7 @@ agent-browser state [options]
148
149
  **Options:**
149
150
  | Flag | Description |
150
151
  |------|-------------|
151
- | `--session <name>` | Target session |
152
+ | `--session, -s <id>` | Target session (from `--new`) |
152
153
  | `--json` | Output as JSON |
153
154
 
154
155
  **Output includes:**
@@ -196,7 +197,7 @@ agent-browser screenshot [options]
196
197
  |------|-------------|
197
198
  | `--output, -o <path>` | Save to file (PNG) instead of base64 output |
198
199
  | `--full-page` | Capture full scrollable page |
199
- | `--session <name>` | Target session |
200
+ | `--session, -s <id>` | Target session (from `--new`) |
200
201
 
201
202
  **Examples:**
202
203
  ```bash
@@ -214,7 +215,7 @@ agent-browser screenshot
214
215
 
215
216
  ### `close`
216
217
 
217
- Close browser and stop daemon.
218
+ Close browser session or stop daemon.
218
219
 
219
220
  ```bash
220
221
  agent-browser close [options]
@@ -223,13 +224,24 @@ agent-browser close [options]
223
224
  **Options:**
224
225
  | Flag | Description |
225
226
  |------|-------------|
226
- | `--session <name>` | Close specific session only |
227
+ | `--session, -s <id>` | Close specific session (daemon keeps running) |
228
+ | `--all` | Close all sessions and stop daemon |
229
+
230
+ ---
231
+
232
+ ### `sessions`
233
+
234
+ List all active browser sessions.
235
+
236
+ ```bash
237
+ agent-browser sessions [--json]
238
+ ```
227
239
 
228
240
  ---
229
241
 
230
242
  ### `status`
231
243
 
232
- Check if daemon is running.
244
+ Check if daemon is running and list active sessions.
233
245
 
234
246
  ```bash
235
247
  agent-browser status
@@ -304,7 +316,7 @@ These options work with most commands:
304
316
 
305
317
  | Flag | Description |
306
318
  |------|-------------|
307
- | `--session <name>` | Named session for isolation |
319
+ | `--session, -s <id>` | Target session ID (from `--new`) |
308
320
  | `--json` | JSON output format |
309
321
  | `--help` | Show help |
310
322
 
@@ -360,15 +372,20 @@ agent-browser close
360
372
 
361
373
  ### Multiple Sessions
362
374
  ```bash
363
- # Session A: Admin user
364
- agent-browser open http://localhost:3000/login --session admin
365
- agent-browser act type:input_0:admin@test.com --session admin
375
+ # Create sessions with auto-generated IDs
376
+ agent-browser open --new http://localhost:3000/login # Output: Session: swift-fox
377
+ agent-browser open --new http://localhost:3000/login # Output: Session: calm-river
378
+
379
+ # Interact with specific sessions
380
+ agent-browser act -s swift-fox type:input_0:admin@test.com
381
+ agent-browser act -s calm-river type:input_0:user@test.com
382
+
383
+ # List all sessions
384
+ agent-browser sessions
366
385
 
367
- # Session B: Regular user
368
- agent-browser open http://localhost:3000/login --session user
369
- agent-browser act type:input_0:user@test.com --session user
386
+ # Close specific session (daemon keeps running)
387
+ agent-browser close -s swift-fox
370
388
 
371
- # Close both
372
- agent-browser close --session admin
373
- agent-browser close --session user
389
+ # Close all sessions and stop daemon
390
+ agent-browser close --all
374
391
  ```
@@ -165,10 +165,6 @@ agent-browser close
165
165
  # Headed mode (visible browser)
166
166
  agent-browser open http://localhost:3000 --headed
167
167
 
168
- # Named session
169
- agent-browser open http://localhost:3000 --session my-test
170
- agent-browser act click:button_0 --session my-test
171
-
172
168
  # JSON output
173
169
  agent-browser state --json
174
170
 
@@ -176,6 +172,20 @@ agent-browser state --json
176
172
  agent-browser act click:button_0 --no-state
177
173
  ```
178
174
 
175
+ ## Multi-Session
176
+
177
+ Run multiple browsers in parallel with `--new`:
178
+
179
+ ```bash
180
+ agent-browser open --new http://localhost:3000 # Output: Session: swift-fox
181
+ agent-browser open --new http://localhost:3000 # Output: Session: calm-river
182
+
183
+ agent-browser act -s swift-fox click:button_0 # Target session
184
+ agent-browser sessions # List all
185
+ agent-browser close -s swift-fox # Close one session
186
+ agent-browser close --all # Close all, stop daemon
187
+ ```
188
+
179
189
  ## Screenshots
180
190
 
181
191
  ```bash
package/README.md CHANGED
@@ -66,7 +66,8 @@ Every command returns the current page state - interactive elements, form values
66
66
  | `wait` | Wait for condition |
67
67
  | `state` | Get current page state |
68
68
  | `screenshot` | Capture screenshot |
69
- | `close` | Close browser and daemon |
69
+ | `sessions` | List all active sessions |
70
+ | `close` | Close session or daemon |
70
71
  | `setup` | Install browser + skill files |
71
72
 
72
73
  ### Actions
@@ -95,11 +96,29 @@ agent-browser wait --timeout 60000 # Custom timeout
95
96
 
96
97
  ```bash
97
98
  --headed # Show browser window
98
- --session <name> # Named session
99
+ --new # Create new session with auto-generated ID
100
+ --session <id> # Target specific session (from --new)
99
101
  --json # JSON output
100
102
  --no-state # Skip state in response
101
103
  ```
102
104
 
105
+ ## Multi-Session
106
+
107
+ Run multiple browser sessions in parallel:
108
+
109
+ ```bash
110
+ # Create sessions with auto-generated IDs
111
+ agent-browser open --new http://localhost:3000 # Output: Session: swift-fox
112
+ agent-browser open --new http://localhost:3000 # Output: Session: calm-river
113
+
114
+ # Target specific sessions
115
+ agent-browser act -s swift-fox click:button_0
116
+ agent-browser state -s calm-river
117
+
118
+ # List all sessions
119
+ agent-browser sessions
120
+ ```
121
+
103
122
  ## State Output
104
123
 
105
124
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-browser-loop",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Let your AI coding agent drive a browser to verify its own work",
5
5
  "license": "MIT",
6
6
  "author": "Jason Silberman",
package/src/cli.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  cleanupDaemonFiles,
21
21
  DaemonClient,
22
22
  ensureDaemon,
23
+ ensureDaemonNewSession,
23
24
  isDaemonRunning,
24
25
  } from "./daemon";
25
26
  import { log, withLog } from "./log";
@@ -83,12 +84,17 @@ async function loadConfig(configPath: string): Promise<BrowserCliConfig> {
83
84
  // Shared CLI Options
84
85
  // ============================================================================
85
86
 
87
+ const newSessionFlag = flag({
88
+ long: "new",
89
+ short: "n",
90
+ description: "Create a new session with auto-generated ID",
91
+ });
92
+
86
93
  const sessionOption = option({
87
94
  long: "session",
88
95
  short: "s",
89
- type: string,
90
- defaultValue: () => "default",
91
- description: "Session name (default: default)",
96
+ type: optional(string),
97
+ description: "Session ID (from --new)",
92
98
  });
93
99
 
94
100
  const headlessFlag = flag({
@@ -308,6 +314,7 @@ const openCommand = command({
308
314
  args: {
309
315
  url: positional({ type: string, displayName: "url" }),
310
316
  session: sessionOption,
317
+ new: newSessionFlag,
311
318
  headless: headlessFlag,
312
319
  headed: headedFlag,
313
320
  config: configOption,
@@ -315,7 +322,17 @@ const openCommand = command({
315
322
  },
316
323
  handler: async (args) => {
317
324
  const browserOptions = await resolveBrowserOptions(args);
318
- const client = await ensureDaemon(args.session, browserOptions);
325
+
326
+ let client: DaemonClient;
327
+ if (args.new) {
328
+ client = await ensureDaemonNewSession(browserOptions);
329
+ } else if (args.session) {
330
+ client = await ensureDaemon(args.session, browserOptions, {
331
+ createIfMissing: false,
332
+ });
333
+ } else {
334
+ client = await ensureDaemon("default", browserOptions);
335
+ }
319
336
 
320
337
  const response = await client.act([{ type: "navigate", url: args.url }]);
321
338
 
@@ -325,9 +342,18 @@ const openCommand = command({
325
342
  }
326
343
 
327
344
  const data = response.data as { text?: string };
345
+ const sessionId = client.getSessionId();
346
+
328
347
  if (args.json) {
329
- console.log(JSON.stringify(response.data, null, 2));
348
+ const jsonData =
349
+ typeof response.data === "object" && response.data !== null
350
+ ? { ...(response.data as object), sessionId }
351
+ : { data: response.data, sessionId };
352
+ console.log(JSON.stringify(jsonData, null, 2));
330
353
  } else {
354
+ if (args.new && sessionId) {
355
+ console.log(`Session: ${sessionId}`);
356
+ }
331
357
  console.log(data.text ?? "Navigated successfully");
332
358
  }
333
359
  },
@@ -341,6 +367,7 @@ const actCommand = command({
341
367
  args: {
342
368
  actions: restPositionals({ type: string, displayName: "actions" }),
343
369
  session: sessionOption,
370
+ new: newSessionFlag,
344
371
  headless: headlessFlag,
345
372
  headed: headedFlag,
346
373
  config: configOption,
@@ -360,7 +387,17 @@ const actCommand = command({
360
387
  }
361
388
 
362
389
  const browserOptions = await resolveBrowserOptions(args);
363
- const client = await ensureDaemon(args.session, browserOptions);
390
+
391
+ let client: DaemonClient;
392
+ if (args.new) {
393
+ client = await ensureDaemonNewSession(browserOptions);
394
+ } else if (args.session) {
395
+ client = await ensureDaemon(args.session, browserOptions, {
396
+ createIfMissing: false,
397
+ });
398
+ } else {
399
+ client = await ensureDaemon("default", browserOptions);
400
+ }
364
401
 
365
402
  const actions = args.actions.map(parseAction);
366
403
  const response = await client.act(actions, {
@@ -376,6 +413,9 @@ const actCommand = command({
376
413
  if (args.json) {
377
414
  console.log(JSON.stringify(response.data, null, 2));
378
415
  } else {
416
+ if (args.new) {
417
+ console.log(`Session: ${client.getSessionId()}`);
418
+ }
379
419
  console.log(data.text ?? "Actions completed");
380
420
  }
381
421
 
@@ -541,16 +581,20 @@ const screenshotCommand = command({
541
581
  process.exit(1);
542
582
  }
543
583
 
544
- const data = response.data as { base64: string };
584
+ // Handle both raw string (from executeCommand) and object format
585
+ const base64 =
586
+ typeof response.data === "string"
587
+ ? response.data
588
+ : (response.data as { base64: string }).base64;
545
589
 
546
590
  if (args.output) {
547
591
  // Write to file
548
- const buffer = Buffer.from(data.base64, "base64");
592
+ const buffer = Buffer.from(base64, "base64");
549
593
  await Bun.write(args.output, buffer);
550
594
  console.log(`Screenshot saved to ${args.output}`);
551
595
  } else {
552
596
  // Output base64
553
- console.log(data.base64);
597
+ console.log(base64);
554
598
  }
555
599
  },
556
600
  });
@@ -558,26 +602,95 @@ const screenshotCommand = command({
558
602
  // --- close ---
559
603
  const closeCommand = command({
560
604
  name: "close",
561
- description: "Close the browser and stop the daemon",
605
+ description: "Close the browser session or shutdown daemon",
562
606
  args: {
563
607
  session: sessionOption,
608
+ all: flag({
609
+ long: "all",
610
+ description: "Close all sessions and shutdown daemon",
611
+ }),
564
612
  },
565
613
  handler: async (args) => {
566
614
  const client = new DaemonClient(args.session);
567
615
 
568
616
  if (!(await client.ping())) {
569
617
  console.log("Daemon not running.");
570
- cleanupDaemonFiles(args.session);
618
+ cleanupDaemonFiles();
571
619
  return;
572
620
  }
573
621
 
574
622
  try {
575
- await client.shutdown();
576
- console.log("Browser closed.");
623
+ if (args.all) {
624
+ await client.shutdown();
625
+ console.log("All sessions closed. Daemon stopped.");
626
+ } else {
627
+ const sessionId = args.session ?? "default";
628
+ const response = await client.closeSession(sessionId);
629
+ if (response.success) {
630
+ console.log(`Session "${sessionId}" closed.`);
631
+ } else {
632
+ console.error("Error:", response.error);
633
+ process.exit(1);
634
+ }
635
+ }
577
636
  } catch {
578
- // Daemon may have already exited
579
- cleanupDaemonFiles(args.session);
580
- console.log("Browser closed.");
637
+ cleanupDaemonFiles();
638
+ console.log("Session closed.");
639
+ }
640
+ },
641
+ });
642
+
643
+ // --- sessions ---
644
+ const sessionsCommand = command({
645
+ name: "sessions",
646
+ description: "List all active browser sessions",
647
+ args: {
648
+ json: jsonFlag,
649
+ },
650
+ handler: async (args) => {
651
+ const client = new DaemonClient();
652
+
653
+ if (!(await client.ping())) {
654
+ if (args.json) {
655
+ console.log(JSON.stringify({ sessions: [] }, null, 2));
656
+ } else {
657
+ console.log("Daemon not running. No active sessions.");
658
+ }
659
+ return;
660
+ }
661
+
662
+ const response = await client.list();
663
+
664
+ if (!response.success) {
665
+ console.error("Error:", response.error);
666
+ process.exit(1);
667
+ }
668
+
669
+ const data = response.data as {
670
+ sessions: Array<{
671
+ id: string;
672
+ url: string;
673
+ title: string;
674
+ busy: boolean;
675
+ lastUsed: number;
676
+ }>;
677
+ };
678
+
679
+ if (args.json) {
680
+ console.log(JSON.stringify(data, null, 2));
681
+ } else {
682
+ if (data.sessions.length === 0) {
683
+ console.log("No active sessions.");
684
+ } else {
685
+ console.log(`Sessions (${data.sessions.length}):\n`);
686
+ for (const s of data.sessions) {
687
+ const status = s.busy ? "[busy]" : "[idle]";
688
+ console.log(` ${s.id} ${status}`);
689
+ console.log(` ${s.title || "(no title)"}`);
690
+ console.log(` ${s.url}`);
691
+ console.log();
692
+ }
693
+ }
581
694
  }
582
695
  },
583
696
  });
@@ -586,14 +699,40 @@ const closeCommand = command({
586
699
  const statusCommand = command({
587
700
  name: "status",
588
701
  description: "Check if daemon is running",
589
- args: {
590
- session: sessionOption,
591
- },
592
- handler: async (args) => {
593
- const running = isDaemonRunning(args.session);
594
- console.log(
595
- `Session "${args.session}": ${running ? "running" : "not running"}`,
596
- );
702
+ args: {},
703
+ handler: async () => {
704
+ const running = isDaemonRunning();
705
+ if (running) {
706
+ console.log("Daemon is running.");
707
+ // Try to list sessions
708
+ const client = new DaemonClient();
709
+ if (await client.ping()) {
710
+ const response = await client.list();
711
+ if (response.success) {
712
+ const data = response.data as {
713
+ sessions: Array<{
714
+ id: string;
715
+ url: string;
716
+ title: string;
717
+ busy: boolean;
718
+ }>;
719
+ };
720
+ if (data.sessions.length === 0) {
721
+ console.log("No active sessions.");
722
+ } else {
723
+ console.log(`\nSessions (${data.sessions.length}):`);
724
+ for (const s of data.sessions) {
725
+ const status = s.busy ? "[busy]" : "[idle]";
726
+ console.log(` ${s.id} ${status}`);
727
+ console.log(` ${s.title || "(no title)"}`);
728
+ console.log(` ${s.url}`);
729
+ }
730
+ }
731
+ }
732
+ }
733
+ } else {
734
+ console.log("Daemon is not running.");
735
+ }
597
736
  },
598
737
  });
599
738
 
@@ -773,6 +912,7 @@ const cli = subcommands({
773
912
  state: stateCommand,
774
913
  screenshot: screenshotCommand,
775
914
  close: closeCommand,
915
+ sessions: sessionsCommand,
776
916
  status: statusCommand,
777
917
 
778
918
  // Setup & configuration