browser-pilot 0.0.16 → 0.0.17

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/dist/cli.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env bun
2
+ import "./chunk-MIJ7UIKB.mjs";
3
+ import "./chunk-OIHU7OFY.mjs";
2
4
  import {
3
5
  BrowserEndpointResolutionError,
4
6
  connect,
5
7
  resolveBrowserEndpoint
6
- } from "./chunk-TJ5B56NV.mjs";
8
+ } from "./chunk-IRLHCVNH.mjs";
7
9
  import "./chunk-LCNFBXB5.mjs";
8
10
  import {
9
11
  DEEP_QUERY_SCRIPT,
10
- LiveTraceCollector,
11
12
  SENSITIVE_AUTOCOMPLETE_TOKENS,
12
13
  TRACE_BINDING_NAME,
13
14
  TRACE_SCRIPT,
@@ -17,13 +18,17 @@ import {
17
18
  canonicalizeRecordingArtifact,
18
19
  createRecordingManifest,
19
20
  createTraceId,
21
+ formatConsoleArg,
20
22
  fuzzyMatchElements,
23
+ globToRegex,
21
24
  grantAudioPermissions,
22
25
  normalizeTraceEvent,
23
26
  pcmToWav,
27
+ readString,
28
+ readStringOr,
24
29
  redactValueForRecording,
25
30
  validateSteps
26
- } from "./chunk-6GBYX7C2.mjs";
31
+ } from "./chunk-MRY3HRFJ.mjs";
27
32
  import {
28
33
  isRecord
29
34
  } from "./chunk-DTVRFXKI.mjs";
@@ -32,6 +37,68 @@ import {
32
37
  DAEMON_READY_TIMEOUT_MS
33
38
  } from "./chunk-LUGLEMVR.mjs";
34
39
 
40
+ // src/cli/command-registry.ts
41
+ var CLI_COMMANDS = [
42
+ { name: "quickstart", description: "Getting started guide", showInRootHelp: true },
43
+ { name: "connect", description: "Create or resume a browser session", showInRootHelp: true },
44
+ { name: "exec", description: "Execute high-level actions", showInRootHelp: true },
45
+ { name: "eval", description: "Run raw JavaScript as an escape hatch", showInRootHelp: true },
46
+ { name: "snapshot", description: "Inspect current page with refs", showInRootHelp: true },
47
+ { name: "text", description: "Extract readable page text", showInRootHelp: true },
48
+ { name: "page", description: "Compact page overview", showInRootHelp: true },
49
+ { name: "forms", description: "List form controls", showInRootHelp: true },
50
+ { name: "targets", description: "List available browser tabs", showInRootHelp: true },
51
+ { name: "diagnose", description: "Debug selectors and targeting failures", showInRootHelp: true },
52
+ { name: "review", description: "Structured business state after actions", showInRootHelp: true },
53
+ { name: "screenshot", description: "Capture a page screenshot", showInRootHelp: true },
54
+ { name: "run", description: "Run a workflow file", showInRootHelp: true },
55
+ {
56
+ name: "record",
57
+ description: "Record a human workflow and derive replayable output",
58
+ showInRootHelp: true
59
+ },
60
+ { name: "trace", description: "Inspect and analyze behavior over time", showInRootHelp: true },
61
+ {
62
+ name: "audio",
63
+ description: "Set up, validate, and drive voice pipelines",
64
+ showInRootHelp: true
65
+ },
66
+ { name: "env", description: "Session and browser-environment controls", showInRootHelp: true },
67
+ { name: "daemon", description: "Manage session daemon", showInRootHelp: true },
68
+ { name: "list", description: "List sessions", showInRootHelp: true },
69
+ { name: "close", description: "Close session", showInRootHelp: true },
70
+ { name: "clean", description: "Clean old sessions and artifacts", showInRootHelp: true },
71
+ { name: "actions", description: "Complete action reference", showInRootHelp: true }
72
+ ];
73
+ var ROOT_HELP_COMMANDS = CLI_COMMANDS.filter((command) => command.showInRootHelp);
74
+ var CLI_ROUTE_GROUPS = [
75
+ {
76
+ label: "Inspect page state",
77
+ commands: ["snapshot", "page", "forms", "review", "text", "targets", "diagnose"]
78
+ },
79
+ {
80
+ label: "Act in the browser",
81
+ commands: ["exec", "run"]
82
+ },
83
+ {
84
+ label: "Capture a human demo",
85
+ commands: ["record"]
86
+ },
87
+ {
88
+ label: "Analyze behavior over time",
89
+ commands: ["trace"],
90
+ note: "(listen is a compatibility alias)"
91
+ },
92
+ {
93
+ label: "Exercise voice/media",
94
+ commands: ["audio"]
95
+ },
96
+ {
97
+ label: "Change browser conditions",
98
+ commands: ["env"]
99
+ }
100
+ ];
101
+
35
102
  // src/cli/commands/actions.ts
36
103
  var ACTIONS_HELP = `
37
104
  bp actions - Complete action reference
@@ -232,7 +299,7 @@ EXAMPLES
232
299
  ]'
233
300
 
234
301
  # Use ref from snapshot
235
- bp snapshot --format text # Note the refs
302
+ bp snapshot -i # Note the refs
236
303
  bp exec '{"action":"click","selector":"ref:e4"}'
237
304
 
238
305
  # Scroll and wait
@@ -359,6 +426,268 @@ Content-Type: ${contentType}\r
359
426
  parts.push(data);
360
427
  }
361
428
 
429
+ // src/trace/live.ts
430
+ var LiveTraceCollector = class {
431
+ cdp;
432
+ options;
433
+ handlers = [];
434
+ wsUrls = /* @__PURE__ */ new Map();
435
+ httpUrls = /* @__PURE__ */ new Map();
436
+ events = [];
437
+ startTime = Date.now();
438
+ matchRegex;
439
+ constructor(cdp, options = {}) {
440
+ this.cdp = cdp;
441
+ this.options = options;
442
+ this.matchRegex = options.match ? globToRegex(options.match) : null;
443
+ }
444
+ async start() {
445
+ await this.cdp.send("Runtime.enable");
446
+ await this.cdp.send("Page.enable");
447
+ await this.cdp.send("Network.enable");
448
+ await this.cdp.send("Runtime.addBinding", { name: TRACE_BINDING_NAME });
449
+ await this.cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: TRACE_SCRIPT });
450
+ await this.cdp.send("Runtime.evaluate", { expression: TRACE_SCRIPT, awaitPromise: false });
451
+ if ((this.options.mode ?? "all") !== "http") {
452
+ this.subscribe("Network.webSocketCreated", (params) => {
453
+ const requestId = readStringOr(params["requestId"]);
454
+ const url = readStringOr(params["url"]);
455
+ if (!this.matchesUrl(url)) {
456
+ return;
457
+ }
458
+ this.wsUrls.set(requestId, url);
459
+ void this.emit({
460
+ channel: "ws",
461
+ event: "ws.connection.created",
462
+ summary: `WebSocket opened ${url}`,
463
+ connectionId: requestId,
464
+ requestId,
465
+ url,
466
+ data: { url }
467
+ });
468
+ });
469
+ this.subscribe("Network.webSocketFrameSent", (params) => {
470
+ const requestId = readStringOr(params["requestId"]);
471
+ const response = params["response"];
472
+ const payload = this.formatPayload(response?.payloadData, response?.opcode ?? 1);
473
+ const url = this.wsUrls.get(requestId);
474
+ if (this.matchRegex && !this.matchRegex.test(url ?? "") && !this.matchRegex.test(payload)) {
475
+ return;
476
+ }
477
+ void this.emit({
478
+ channel: "ws",
479
+ event: "ws.frame.sent",
480
+ summary: `WebSocket frame sent ${requestId}`,
481
+ connectionId: requestId,
482
+ requestId,
483
+ url,
484
+ data: {
485
+ opcode: response?.opcode ?? 1,
486
+ payload,
487
+ length: response?.payloadData?.length ?? 0
488
+ }
489
+ });
490
+ });
491
+ this.subscribe("Network.webSocketFrameReceived", (params) => {
492
+ const requestId = readStringOr(params["requestId"]);
493
+ const response = params["response"];
494
+ const payload = this.formatPayload(response?.payloadData, response?.opcode ?? 1);
495
+ const url = this.wsUrls.get(requestId);
496
+ if (this.matchRegex && !this.matchRegex.test(url ?? "") && !this.matchRegex.test(payload)) {
497
+ return;
498
+ }
499
+ void this.emit({
500
+ channel: "ws",
501
+ event: "ws.frame.received",
502
+ summary: `WebSocket frame received ${requestId}`,
503
+ connectionId: requestId,
504
+ requestId,
505
+ url,
506
+ data: {
507
+ opcode: response?.opcode ?? 1,
508
+ payload,
509
+ length: response?.payloadData?.length ?? 0
510
+ }
511
+ });
512
+ });
513
+ this.subscribe("Network.webSocketClosed", (params) => {
514
+ const requestId = readStringOr(params["requestId"]);
515
+ const url = this.wsUrls.get(requestId);
516
+ this.wsUrls.delete(requestId);
517
+ void this.emit({
518
+ channel: "ws",
519
+ event: "ws.connection.closed",
520
+ summary: `WebSocket closed ${requestId}`,
521
+ severity: "warn",
522
+ connectionId: requestId,
523
+ requestId,
524
+ url,
525
+ data: { url }
526
+ });
527
+ });
528
+ }
529
+ if ((this.options.mode ?? "all") !== "ws") {
530
+ this.subscribe("Network.requestWillBeSent", (params) => {
531
+ const request = params["request"];
532
+ const requestId = readStringOr(params["requestId"]);
533
+ const url = request?.url ?? "";
534
+ if (!this.matchesUrl(url)) {
535
+ return;
536
+ }
537
+ this.httpUrls.set(requestId, url);
538
+ void this.emit({
539
+ channel: "http",
540
+ event: "http.request.sent",
541
+ summary: `${request?.method ?? "GET"} ${url}`,
542
+ requestId,
543
+ url,
544
+ data: {
545
+ method: request?.method ?? "GET",
546
+ headers: request?.headers ?? {},
547
+ body: request?.postData ?? null
548
+ }
549
+ });
550
+ });
551
+ this.subscribe("Network.responseReceived", (params) => {
552
+ const requestId = readStringOr(params["requestId"]);
553
+ if (!this.httpUrls.has(requestId)) {
554
+ return;
555
+ }
556
+ const response = params["response"];
557
+ void this.emit({
558
+ channel: "http",
559
+ event: "http.response.received",
560
+ summary: `${response?.status ?? 0} ${response?.url ?? this.httpUrls.get(requestId) ?? ""}`,
561
+ requestId,
562
+ url: response?.url ?? this.httpUrls.get(requestId),
563
+ data: {
564
+ status: response?.status ?? 0,
565
+ headers: response?.headers ?? {},
566
+ mimeType: response?.mimeType ?? null
567
+ }
568
+ });
569
+ });
570
+ this.subscribe("Network.loadingFailed", (params) => {
571
+ const requestId = readStringOr(params["requestId"]);
572
+ const url = readString(params["blockedReason"]) ?? this.httpUrls.get(requestId) ?? "";
573
+ void this.emit({
574
+ channel: "http",
575
+ event: "http.response.failed",
576
+ summary: `HTTP request failed ${requestId}`,
577
+ severity: "error",
578
+ requestId,
579
+ url,
580
+ data: {
581
+ errorText: params["errorText"] ?? null,
582
+ blockedReason: params["blockedReason"] ?? null,
583
+ canceled: params["canceled"] ?? false
584
+ }
585
+ });
586
+ });
587
+ }
588
+ this.subscribe("Runtime.consoleAPICalled", (params) => {
589
+ const type = readStringOr(params["type"], "log");
590
+ if (type !== "log" && type !== "warn" && type !== "error") {
591
+ return;
592
+ }
593
+ const args = Array.isArray(params["args"]) ? params["args"] : [];
594
+ const text = args.map(formatConsoleArg).filter(Boolean).join(" ");
595
+ void this.emit({
596
+ channel: "console",
597
+ event: `console.${type}`,
598
+ severity: type === "error" ? "error" : type === "warn" ? "warn" : "info",
599
+ summary: text || `console.${type}`,
600
+ data: { args }
601
+ });
602
+ });
603
+ this.subscribe("Runtime.exceptionThrown", (params) => {
604
+ const details = params["exceptionDetails"] ?? {};
605
+ const text = readString(details["text"]) ?? "Runtime exception";
606
+ void this.emit({
607
+ channel: "runtime",
608
+ event: "runtime.exception",
609
+ severity: "error",
610
+ summary: text,
611
+ data: details
612
+ });
613
+ });
614
+ this.subscribe("Runtime.bindingCalled", (params) => {
615
+ if (params["name"] !== TRACE_BINDING_NAME) {
616
+ return;
617
+ }
618
+ const raw = readStringOr(params["payload"]);
619
+ try {
620
+ const payload = JSON.parse(raw);
621
+ const channel = this.channelForTraceEvent(payload.event);
622
+ void this.emit({
623
+ channel,
624
+ event: payload.event,
625
+ severity: payload.severity,
626
+ summary: payload.summary ?? payload.event,
627
+ ts: payload.ts ? new Date(payload.ts).toISOString() : void 0,
628
+ data: payload.data ?? {},
629
+ url: readString(payload.data?.["url"])
630
+ });
631
+ } catch {
632
+ }
633
+ });
634
+ }
635
+ async stop() {
636
+ for (const { event, handler } of this.handlers) {
637
+ this.cdp.off(event, handler);
638
+ }
639
+ this.handlers.length = 0;
640
+ return [...this.events];
641
+ }
642
+ getEvents() {
643
+ return [...this.events];
644
+ }
645
+ subscribe(event, handler) {
646
+ this.cdp.on(event, handler);
647
+ this.handlers.push({ event, handler });
648
+ }
649
+ matchesUrl(url) {
650
+ if (!this.matchRegex) {
651
+ return true;
652
+ }
653
+ return this.matchRegex.test(url);
654
+ }
655
+ formatPayload(payloadData, opcode) {
656
+ const data = payloadData ?? "";
657
+ const maxPayload = this.options.maxPayload ?? 256;
658
+ if (opcode === 2) {
659
+ const byteLength = Math.floor(data.length * 3 / 4);
660
+ return `[binary: ${byteLength} bytes]`;
661
+ }
662
+ if (data.length > maxPayload) {
663
+ return `${data.slice(0, maxPayload)}... [truncated, ${data.length} total]`;
664
+ }
665
+ return data;
666
+ }
667
+ channelForTraceEvent(eventName) {
668
+ if (eventName.startsWith("ws.")) return "ws";
669
+ if (eventName.startsWith("http.")) return "http";
670
+ if (eventName.startsWith("console.")) return "console";
671
+ if (eventName.startsWith("permission.")) return "permission";
672
+ if (eventName.startsWith("media.")) return "media";
673
+ if (eventName.startsWith("voice.")) return "voice";
674
+ if (eventName.startsWith("dom.")) return "dom";
675
+ if (eventName.startsWith("runtime.")) return "runtime";
676
+ return "session";
677
+ }
678
+ async emit(event) {
679
+ const normalized = normalizeTraceEvent({
680
+ traceId: event.traceId ?? createTraceId(event.channel),
681
+ sessionId: this.options.sessionId,
682
+ targetId: this.options.targetId,
683
+ elapsedMs: event.elapsedMs ?? Date.now() - this.startTime,
684
+ ...event
685
+ });
686
+ this.events.push(normalized);
687
+ await this.options.onEvent?.(normalized);
688
+ }
689
+ };
690
+
362
691
  // src/trace/store.ts
363
692
  import * as fs from "fs";
364
693
  import { homedir } from "os";
@@ -1220,7 +1549,7 @@ async function audioCommand(args, globalOptions) {
1220
1549
  event: checkJson.ready ? "voice.pipeline.ready" : "voice.pipeline.notReady",
1221
1550
  severity: checkJson.ready ? "info" : "error",
1222
1551
  summary: checkJson.ready ? "Audio pipeline ready" : "Audio pipeline not ready",
1223
- data: checkJson
1552
+ data: { ...checkJson }
1224
1553
  });
1225
1554
  if (checkJson.agentDetected) {
1226
1555
  logger.logTrace({
@@ -1521,13 +1850,15 @@ bp clean - Remove stale browser sessions
1521
1850
  Usage:
1522
1851
  bp clean [options]
1523
1852
 
1524
- Options:
1853
+ Local options:
1525
1854
  --max-age <hours> Remove sessions older than N hours (default: 24)
1526
1855
  --max-size <size> Remove oldest sessions until total size < limit (e.g. "100MB", "1GB")
1527
1856
  --dry-run Show what would be removed without deleting
1528
1857
  --all Remove all sessions regardless of age
1529
- -f, --format <fmt> Output format: json | pretty (default: pretty)
1530
- --json Alias for -f json
1858
+
1859
+ Global options:
1860
+ --json Output JSON
1861
+ --pretty Output readable text (default)
1531
1862
  -h, --help Show this help
1532
1863
 
1533
1864
  Examples:
@@ -1731,11 +2062,11 @@ bp close - Close a browser session
1731
2062
  Usage:
1732
2063
  bp close [session-id]
1733
2064
 
1734
- Options:
2065
+ Global options:
1735
2066
  -s, --session <id> Session to close (default: most recent)
1736
- -f, --format <fmt> Output format: json | pretty (default: pretty)
1737
- --json Alias for -f json
1738
- --trace Enable debug tracing
2067
+ --json Output JSON
2068
+ --pretty Output readable text (default)
2069
+ --debug Enable CDP transport debugging
1739
2070
  -h, --help Show this help
1740
2071
 
1741
2072
  Examples:
@@ -1840,18 +2171,30 @@ async function waitForDaemonReady(sessionFilePath, expectedPid, timeoutMs = DAEM
1840
2171
  var CONNECT_HELP = `
1841
2172
  bp connect - Create or resume a browser session
1842
2173
 
2174
+ When to use:
2175
+ Create a session before running inspect, exec, record, trace, audio, or env commands.
2176
+
2177
+ When not to use:
2178
+ You already have a session and only need to open a page. Use \`bp exec '{"action":"goto","url":"..."}'\`.
2179
+
2180
+ Browser and page URL guidance:
2181
+ Use \`--browser-url\` for a DevTools WebSocket endpoint.
2182
+ Use \`--page-url\` to open a page in the attached tab or a new tab.
2183
+ \`--url\` remains for compatibility and is ambiguous when paired with \`--new-tab\`.
2184
+
1843
2185
  Usage:
1844
2186
  bp connect [options]
1845
2187
 
1846
- Options:
2188
+ Local options:
1847
2189
  -p, --provider <type> Provider: generic | browserbase | browserless (default: generic)
1848
- --url <value> Browser WebSocket URL, or page URL when used with --new-tab
1849
- --browser-url <ws-url> Explicit browser WebSocket URL
2190
+ --browser-url <ws-url> Explicit browser WebSocket URL (preferred)
2191
+ --page-url <url> Page URL to open in the attached tab/new tab (preferred)
2192
+ --url <value> Compatibility shorthand; browser URL, or page URL with --new-tab
1850
2193
  --channel <name> Local Chrome channel: stable | beta | dev | canary
1851
2194
  --user-data-dir <path> Explicit local Chrome user data dir for auto-discovery
1852
- --page-url <url> URL to open in the attached page/new tab
1853
2195
  -n, --name <id> Custom session name (default: auto-generated)
1854
2196
  -r, --resume <id> Resume an existing session by ID
2197
+ -s, --session <id> Alias for --resume
1855
2198
  --new-tab Create and attach to a fresh tab instead of reusing an existing one
1856
2199
  --target-url <str> Filter targets to those whose URL contains this string
1857
2200
  --api-key <key> API key for cloud providers
@@ -1863,28 +2206,57 @@ Options:
1863
2206
  --no-highlights Disable visual highlights on screenshots
1864
2207
  --no-daemon Skip daemon creation (direct WebSocket only)
1865
2208
  --daemon-idle <mins> Daemon idle timeout in minutes (default: 60)
1866
- -s, --session <id> Alias for --resume
1867
- --trace Enable debug tracing
2209
+
2210
+ Global options:
2211
+ --json Output JSON
2212
+ --pretty Output readable text (default)
2213
+ --debug Enable CDP transport debugging
1868
2214
  -h, --help Show this help
1869
2215
 
1870
2216
  Examples:
1871
- bp connect # Auto-connect to local Chrome
1872
- bp connect --channel beta # Narrow auto-discovery to Chrome Beta
1873
- bp connect --user-data-dir ~/tmp/chrome-dev # Use a specific Chrome profile
1874
- bp connect --record # Connect with session-level recording
1875
- bp connect --name dev # Auto-connect with a custom session name
1876
- bp connect --url ws://localhost:9222/devtools/browser/abc123 # Explicit WebSocket URL
1877
- bp connect --resume dev # Resume a previous session
2217
+ bp connect # Auto-connect to local Chrome
2218
+ bp connect --name dev # Auto-connect with a custom session name
2219
+ bp connect --resume dev # Resume a previous session
2220
+ bp connect --browser-url ws://localhost:9222/devtools/browser/abc123
2221
+ bp connect --channel beta # Narrow auto-discovery to Chrome Beta
2222
+ bp connect --user-data-dir ~/tmp/chrome-dev # Use a specific Chrome profile
1878
2223
  bp connect --target-url localhost:3000 # Attach to tab matching URL
1879
- bp connect --new-tab --url https://example.com # Create and attach to a fresh tab
1880
- bp connect --no-daemon # Connect without daemon (file-based only)
2224
+ bp connect --record # Connect with session-level recording
2225
+ bp connect --new-tab --page-url https://example.com
2226
+ bp connect --no-daemon # Connect without daemon (file-based only)
2227
+
2228
+ Likely next commands:
2229
+ bp exec -s dev '{"action":"goto","url":"https://example.com"}'
2230
+ bp snapshot -i -s dev
2231
+ bp text -s dev
1881
2232
  `.trimEnd();
2233
+ async function resolveInitialPageUrl(page, requestedUrl) {
2234
+ const initialUrl = await page.url();
2235
+ if (!requestedUrl || requestedUrl === "about:blank" || initialUrl !== "about:blank") {
2236
+ return initialUrl;
2237
+ }
2238
+ const deadline = Date.now() + 5e3;
2239
+ while (Date.now() < deadline) {
2240
+ await Bun.sleep(100);
2241
+ const currentUrl = await page.url();
2242
+ if (currentUrl !== "about:blank") {
2243
+ return currentUrl;
2244
+ }
2245
+ }
2246
+ return initialUrl;
2247
+ }
1882
2248
  function parseConnectArgs(args) {
1883
2249
  const options = {};
1884
2250
  for (let i = 0; i < args.length; i++) {
1885
2251
  const arg = args[i];
1886
2252
  if (arg === "--provider" || arg === "-p") {
1887
- options.provider = args[++i];
2253
+ const p = args[++i];
2254
+ if (p !== "browserbase" && p !== "browserless" && p !== "generic") {
2255
+ throw new Error(
2256
+ `Invalid provider: ${p}. Must be one of: browserbase, browserless, generic`
2257
+ );
2258
+ }
2259
+ options.provider = p;
1888
2260
  } else if (arg === "--url") {
1889
2261
  options.url = args[++i];
1890
2262
  } else if (arg === "--browser-url") {
@@ -2016,7 +2388,7 @@ async function connectCommand(args, globalOptions) {
2016
2388
  void 0,
2017
2389
  options.targetUrl ? { targetUrl: options.targetUrl } : void 0
2018
2390
  );
2019
- const currentUrl = await page.url();
2391
+ const currentUrl = await resolveInitialPageUrl(page, pageUrl);
2020
2392
  const sessionId = options.name ?? generateSessionId();
2021
2393
  let recordSettings;
2022
2394
  if (options.record) {
@@ -2098,10 +2470,12 @@ Subcommands:
2098
2470
  logs Show daemon log output
2099
2471
 
2100
2472
  Options:
2101
- -s, --session <id> Target session (default: most recent)
2102
- -f, --format <fmt> Output format: json | pretty (default: pretty)
2103
- --json Alias for -f json
2104
2473
  -n, --lines <n> Number of log lines to show (default: 50)
2474
+
2475
+ Global options:
2476
+ -s, --session <id> Target session (default: most recent)
2477
+ --json Output JSON
2478
+ --pretty Output readable text (default)
2105
2479
  -h, --help Show this help
2106
2480
 
2107
2481
  Examples:
@@ -2731,11 +3105,15 @@ Examples:
2731
3105
  bp diagnose "submit" Find elements matching "submit"
2732
3106
  bp diagnose "ref:e4" Diagnose by element ref
2733
3107
 
2734
- Options:
2735
- --json Output as JSON
3108
+ Local options:
2736
3109
  --max <n> Max candidates for fuzzy match (default: 5)
2737
- -s, --session <id> Use specific session
2738
- --help Show this help
3110
+
3111
+ Global options:
3112
+ -s, --session <id> Session to use (default: most recent)
3113
+ --json Output JSON
3114
+ --pretty Output readable text (default)
3115
+ --debug Enable CDP transport debugging
3116
+ -h, --help Show this help
2739
3117
 
2740
3118
  Likely next commands:
2741
3119
  bp exec '[{"action":"click","selector":"<suggested-selector>"}]'
@@ -3770,8 +4148,8 @@ async function attachSession(session, options = {}) {
3770
4148
  const cdp = createCDPClientFromTransport(transport, {
3771
4149
  debug: options.trace
3772
4150
  });
3773
- const { Browser: BrowserClass } = await import("./browser-ZCR6AA4D.mjs");
3774
- const { Page: PageClass } = await import("./page-IUUTJ3SW.mjs");
4151
+ const { Browser: BrowserClass } = await import("./browser-4ZHNAQR5.mjs");
4152
+ const { Page: PageClass } = await import("./page-SD64DY3F.mjs");
3775
4153
  const browser2 = BrowserClass.fromCDP(cdp, session);
3776
4154
  const page2 = session.daemon.cdpSessionId && session.targetId ? addBatchToPage(
3777
4155
  await (async () => {
@@ -3824,25 +4202,29 @@ bp eval - Evaluate JavaScript in the browser
3824
4202
 
3825
4203
  Convenience wrapper around exec's evaluate action.
3826
4204
  No JSON escaping needed -- just pass a JS expression directly.
4205
+ Use this as an escape hatch after higher-level commands like snapshot, text, review, and exec.
3827
4206
 
3828
4207
  Usage:
3829
4208
  bp eval '<expression>' Evaluate inline JavaScript
3830
4209
  bp eval -f <file> Evaluate JavaScript from a file
3831
4210
  echo '<expr>' | bp eval Evaluate from stdin
3832
4211
 
3833
- Options:
3834
- -f, --file <path> Read JavaScript from a file
3835
- --wrap Wrap the expression in an async IIFE
3836
- -s, --session <id> Session to use (default: most recent)
3837
- -f, --format <fmt> Output format: json | pretty (default: pretty)
3838
- --json Alias for -f json
3839
- --trace Enable debug tracing
3840
- -h, --help Show this help
4212
+ Local options:
4213
+ -f, --file <path> Read JavaScript from a file
4214
+ --wrap Wrap the expression in an async IIFE
4215
+
4216
+ Global options:
4217
+ -s, --session <id> Session to use (default: most recent)
4218
+ --json Output JSON
4219
+ --pretty Output readable text (default)
4220
+ --debug Enable CDP transport debugging
4221
+ -h, --help Show this help
3841
4222
 
3842
4223
  Examples:
3843
4224
  bp eval 'document.title'
3844
4225
  bp eval 'document.querySelectorAll("a").length'
3845
4226
  bp eval -f scrape.js
4227
+ bp eval --wrap 'await fetch("/health").then((r) => r.status)'
3846
4228
  `.trimEnd();
3847
4229
  function parseEvalArgs(args) {
3848
4230
  const options = {};
@@ -3938,14 +4320,10 @@ Usage:
3938
4320
  bp exec -f <file> Execute action(s) from a JSON file
3939
4321
  echo '<json>' | bp exec Execute action(s) from stdin
3940
4322
 
3941
- Options:
3942
- -f, --file <path> Read actions from a JSON file
3943
- -o, --output <path> Write command output to a file instead of stdout
3944
- --dialog <mode> Handle native dialogs: accept | dismiss
3945
- -s, --session <id> Session to use (default: most recent)
3946
- -f, --format <fmt> Output format: json | pretty (default: pretty)
3947
- --json Alias for -f json
3948
- --debug Enable CDP transport debugging (global option)
4323
+ Local options:
4324
+ -f, --file <path> Read actions from a JSON file
4325
+ -o, --output <path> Write command output to a file instead of stdout
4326
+ --dialog <mode> Handle native dialogs: accept | dismiss
3949
4327
 
3950
4328
  Recording:
3951
4329
  --record Enable screenshot recording
@@ -3955,6 +4333,11 @@ Recording:
3955
4333
  --no-highlights Disable visual highlights on screenshots
3956
4334
  Sensitive fields (passwords, OTPs, card inputs) are redacted
3957
4335
 
4336
+ Global options:
4337
+ -s, --session <id> Session to use (default: most recent)
4338
+ --json Output JSON
4339
+ --pretty Output readable text (default)
4340
+ --debug Enable CDP transport debugging
3958
4341
  -h, --help Show this help
3959
4342
 
3960
4343
  Examples:
@@ -4181,6 +4564,22 @@ Run 'bp actions' for complete action reference.${evalTip}`
4181
4564
  data: stepResult.result
4182
4565
  });
4183
4566
  }
4567
+ if (stepResult.outcomeStatus) {
4568
+ logger.logTrace({
4569
+ channel: "action",
4570
+ event: `action.outcome.${stepResult.outcomeStatus}`,
4571
+ summary: `Outcome: ${stepResult.outcomeStatus}${stepResult.retrySafe === false ? " (unsafe to retry)" : ""}`,
4572
+ data: {
4573
+ outcomeStatus: stepResult.outcomeStatus,
4574
+ retrySafe: stepResult.retrySafe,
4575
+ matchedConditions: stepResult.matchedConditions?.map((mc) => ({
4576
+ kind: mc.condition.kind,
4577
+ matched: mc.matched,
4578
+ detail: mc.detail
4579
+ }))
4580
+ }
4581
+ });
4582
+ }
4184
4583
  }
4185
4584
  if (result.recordingManifest && session.exportLog) {
4186
4585
  mirrorRecordingToExport(result.recordingManifest, session.exportLog);
@@ -4222,7 +4621,10 @@ Run 'bp actions' for complete action reference.${evalTip}`
4222
4621
  selectorUsed: s.selectorUsed,
4223
4622
  error: s.error,
4224
4623
  text: s.text,
4225
- result: s.result
4624
+ result: s.result,
4625
+ ...s.outcomeStatus !== void 0 ? { outcomeStatus: s.outcomeStatus } : {},
4626
+ ...s.matchedConditions !== void 0 ? { matchedConditions: s.matchedConditions } : {},
4627
+ ...s.retrySafe !== void 0 ? { retrySafe: s.retrySafe } : {}
4226
4628
  }));
4227
4629
  const payload = {
4228
4630
  success: result.success,
@@ -4332,19 +4734,29 @@ function formatInteractiveElementsPretty(elements, limit = elements.length) {
4332
4734
  var FORMS_HELP = `
4333
4735
  bp forms - List form controls on the current page
4334
4736
 
4737
+ When to use:
4738
+ You need field names, types, values, or disabled state without the rest of the page.
4739
+
4740
+ When not to use:
4741
+ You need clickable refs or a broader page summary. Use \`bp snapshot -i\` or \`bp page\`.
4742
+
4335
4743
  Usage:
4336
4744
  bp forms [options]
4337
4745
 
4338
- Options:
4746
+ Global options:
4339
4747
  -s, --session <id> Session to use (default: most recent)
4340
- -f, --format <fmt> json | pretty (default: pretty)
4341
- --json Alias for -f json
4342
- --trace Enable debug tracing
4748
+ --json Output JSON
4749
+ --pretty Output readable text (default)
4750
+ --debug Enable CDP transport debugging
4343
4751
  -h, --help Show this help
4344
4752
 
4345
4753
  Examples:
4346
4754
  bp forms
4347
4755
  bp forms --json
4756
+
4757
+ Likely next commands:
4758
+ bp exec '[{"action":"fill","selector":"ref:e4","value":"..."}]'
4759
+ bp review --json
4348
4760
  `.trimEnd();
4349
4761
  async function formsCommand(_args, globalOptions) {
4350
4762
  if (globalOptions.help) {
@@ -4381,11 +4793,14 @@ Usage:
4381
4793
  bp list -s <id> --log-path Print path to session log file (for analysis)
4382
4794
 
4383
4795
  Options:
4384
- -s, --session <id> Target session (or uses default session)
4385
4796
  --info Show session details and log statistics
4386
4797
  --log-tail [n] Show last n action log entries (default: 20)
4387
4798
  --log-path Print absolute path to log.jsonl file
4388
- -f json, --json Machine-readable JSON output
4799
+
4800
+ Global options:
4801
+ -s, --session <id> Target session (or uses default session)
4802
+ --json Machine-readable JSON output
4803
+ --pretty Output readable text (default)
4389
4804
  -h, --help Show this help
4390
4805
 
4391
4806
  Examples:
@@ -4567,7 +4982,11 @@ When to use:
4567
4982
  You want a quick summary of URL, title, headings, forms, and interactive controls.
4568
4983
 
4569
4984
  When not to use:
4570
- You need the full accessibility tree or reusable refs for precise automation. Use \`bp snapshot\`.
4985
+ You need the full accessibility tree or the full ref inventory for precise automation. Use \`bp snapshot\`.
4986
+
4987
+ Common mistake:
4988
+ Treating \`bp page\` as exhaustive. It is a compact overview; the Actions section caches reusable refs,
4989
+ but use \`bp snapshot -i\` when you need the full actionable surface.
4571
4990
 
4572
4991
  Likely next commands:
4573
4992
  bp snapshot -i
@@ -4577,11 +4996,11 @@ Likely next commands:
4577
4996
  Usage:
4578
4997
  bp page [options]
4579
4998
 
4580
- Options:
4999
+ Global options:
4581
5000
  -s, --session <id> Session to use (default: most recent)
4582
- -f, --format <fmt> json | pretty (default: pretty)
4583
- --json Alias for -f json
4584
- --trace Enable debug tracing
5001
+ --json Output JSON
5002
+ --pretty Output readable text (default)
5003
+ --debug Enable CDP transport debugging
4585
5004
  -h, --help Show this help
4586
5005
 
4587
5006
  Examples:
@@ -4649,7 +5068,16 @@ async function pageCommand(_args, globalOptions) {
4649
5068
  globalOptions.format === "json" ? summary : formatPageSummary(summary),
4650
5069
  globalOptions.format
4651
5070
  );
4652
- await updateSession(session.id, { currentUrl: url });
5071
+ await updateSession(session.id, {
5072
+ currentUrl: url,
5073
+ metadata: {
5074
+ refCache: {
5075
+ url,
5076
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
5077
+ refMap: page.exportRefMap()
5078
+ }
5079
+ }
5080
+ });
4653
5081
  } finally {
4654
5082
  await browser.disconnect();
4655
5083
  }
@@ -4660,14 +5088,14 @@ var QUICKSTART = `
4660
5088
  browser-pilot CLI - Quick Start Guide
4661
5089
 
4662
5090
  STEP 1: CONNECT TO A BROWSER
4663
- bp connect --provider generic --name mysite
5091
+ bp connect --name mysite
4664
5092
 
4665
5093
  This creates a session. The CLI remembers it for subsequent commands.
4666
5094
 
4667
5095
  STEP 2: NAVIGATE
4668
- bp exec '{"action":"goto","url":"https://example.com"}'
5096
+ bp exec -s mysite '{"action":"goto","url":"https://example.com"}'
4669
5097
 
4670
- STEP 3: GET PAGE SNAPSHOT
5098
+ STEP 3: CHOOSE THE RIGHT INSPECTION COMMAND
4671
5099
  bp snapshot -i
4672
5100
 
4673
5101
  Shows only interactive elements (buttons, inputs, links) with refs:
@@ -4675,39 +5103,48 @@ STEP 3: GET PAGE SNAPSHOT
4675
5103
  textbox "Email" ref:e3
4676
5104
  link "Forgot password?" ref:e6
4677
5105
 
4678
- Other formats:
4679
- bp snapshot --format text # Full accessibility tree (all elements)
4680
- bp snapshot --json # Full snapshot as JSON
5106
+ Other inspection commands:
5107
+ bp page # Compact overview: URL, title, headings, forms, actions
5108
+ bp text # Readable page copy or policy text
5109
+ bp review --json # Structured business state after actions
5110
+ bp diagnose 'submit' # Debug selector or targeting failures
4681
5111
 
4682
5112
  STEP 4: INTERACT USING REFS
4683
- bp exec '{"action":"fill","selector":"ref:e3","value":"test@example.com"}'
4684
- bp exec '{"action":"click","selector":"ref:e2"}'
5113
+ bp exec -s mysite '{"action":"fill","selector":"ref:e3","value":"test@example.com"}'
5114
+ bp exec -s mysite '{"action":"click","selector":"ref:e2"}'
4685
5115
 
4686
5116
  STEP 5: BATCH MULTIPLE ACTIONS
4687
- bp exec '[
5117
+ bp exec -s mysite '[
4688
5118
  {"action":"fill","selector":"ref:e3","value":"user@test.com"},
4689
5119
  {"action":"click","selector":"ref:e2"},
4690
5120
  {"action":"snapshot"}
4691
5121
  ]'
4692
5122
 
4693
5123
  FOR AI AGENTS
4694
- Use bp snapshot -i for most workflows - shows only actionable elements.
5124
+ Start with:
5125
+ bp --help
5126
+ bp --version
5127
+
5128
+ Use bp snapshot -i for most workflows - it shows actionable elements.
4695
5129
  Add --json for machine-readable output:
4696
- bp snapshot -i --json
4697
- bp exec '{"action":"click","selector":"ref:e3"}' --json
5130
+ bp snapshot -i -s mysite --json
5131
+ bp exec -s mysite '{"action":"click","selector":"ref:e3"}' --json
4698
5132
 
4699
5133
  PAGE DISCOVERY SHORTCUTS
4700
- bp page # URL, title, headings, forms, and interactive controls
4701
- bp forms # Structured list of form fields only
4702
- bp targets # All available browser tabs
4703
- bp connect --new-tab --url https://example.com
4704
- # Start from a fresh tab instead of reusing one
5134
+ bp page # URL, title, headings, forms, and interactive controls
5135
+ bp forms # Structured list of form fields only
5136
+ bp text --selector '#main' # Focused readable text extraction
5137
+ bp review --json # Structured business state
5138
+ bp targets # All available browser tabs
5139
+ bp connect --new-tab --page-url https://example.com
5140
+ # Convenience: start from a fresh tab
4705
5141
 
4706
5142
  TIPS
4707
- \u2022 Refs (e1, e2...) are stable within a page - prefer them over CSS selectors
4708
- \u2022 After navigation, take a new snapshot to get updated refs
4709
- \u2022 Use multi-selectors for resilience: ["ref:e3", "#email", "input[type=email]"]
4710
- \u2022 Add "optional":true to skip elements that may not exist
5143
+ - Refs (e1, e2...) are stable within the current page state
5144
+ - After navigation or major DOM changes, take a new snapshot to refresh refs
5145
+ - Use multi-selectors for resilience: ["ref:e3", "#email", "input[type=email]"]
5146
+ - Add "optional":true to skip elements that may not exist
5147
+ - Use bp eval only as an escape hatch when higher-level commands are insufficient
4711
5148
 
4712
5149
  SELECTOR PRIORITY
4713
5150
  1. ref:e5 From snapshot - most reliable
@@ -5524,15 +5961,6 @@ var RECORDER_SCRIPT = `(function() {
5524
5961
  })();`;
5525
5962
 
5526
5963
  // src/recording/recorder.ts
5527
- function readString(value) {
5528
- return typeof value === "string" ? value : void 0;
5529
- }
5530
- function readStringOr(value, fallback = "") {
5531
- return readString(value) ?? fallback;
5532
- }
5533
- function formatConsoleArg(entry) {
5534
- return readString(entry["value"]) ?? readString(entry["description"]) ?? "";
5535
- }
5536
5964
  var Recorder = class {
5537
5965
  cdp;
5538
5966
  options;
@@ -6073,11 +6501,6 @@ var Recorder = class {
6073
6501
  return entries;
6074
6502
  }
6075
6503
  };
6076
- function globToRegex(pattern) {
6077
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
6078
- const withWildcards = escaped.replace(/\*/g, ".*");
6079
- return new RegExp(`^${withWildcards}$`);
6080
- }
6081
6504
 
6082
6505
  // src/cli/commands/record.ts
6083
6506
  var RECORD_HELP = `
@@ -6598,6 +7021,114 @@ async function recordCommand(args, globalOptions) {
6598
7021
  }
6599
7022
  }
6600
7023
 
7024
+ // src/cli/commands/review.ts
7025
+ var REVIEW_HELP = `
7026
+ bp review - Extract structured business state from the current page
7027
+
7028
+ When to use:
7029
+ You want a structured summary of the page: headings, forms, alerts, tables,
7030
+ key-value pairs, and status labels. Useful for verifying business state after
7031
+ an action sequence, especially on detail, checkout, and confirmation pages.
7032
+
7033
+ When not to use:
7034
+ You need the full accessibility tree with refs. Use \`bp snapshot\`.
7035
+ You want a compact overview. Use \`bp page\`.
7036
+ You are on a dense catalog or marketing page with lots of nav chrome. Use \`bp text\` or \`bp page\`.
7037
+
7038
+ Likely next commands:
7039
+ bp snapshot -i
7040
+ bp exec '[{"action":"click","selector":"ref:e4"}]'
7041
+
7042
+ Usage:
7043
+ bp review [options]
7044
+
7045
+ Global options:
7046
+ -s, --session <id> Session to use (default: most recent)
7047
+ --json Output JSON
7048
+ --pretty Output readable text (default)
7049
+ --debug Enable CDP transport debugging
7050
+ -h, --help Show this help
7051
+
7052
+ Examples:
7053
+ bp review
7054
+ bp review --json
7055
+ bp review -s my-session
7056
+ `.trimEnd();
7057
+ function formatReviewPretty(review) {
7058
+ const lines = [];
7059
+ lines.push(`URL: ${review.url}`);
7060
+ lines.push(`Title: ${review.title}`);
7061
+ lines.push("", "Headings:");
7062
+ if (review.headings.length === 0) {
7063
+ lines.push(" (none)");
7064
+ } else {
7065
+ for (const h of review.headings) {
7066
+ lines.push(` ${h}`);
7067
+ }
7068
+ }
7069
+ if (review.alerts.length > 0) {
7070
+ lines.push("", "Alerts:");
7071
+ for (const a of review.alerts) {
7072
+ lines.push(` ${a}`);
7073
+ }
7074
+ }
7075
+ if (review.statusLabels.length > 0) {
7076
+ lines.push("", "Status:");
7077
+ for (const s of review.statusLabels) {
7078
+ lines.push(` ${s}`);
7079
+ }
7080
+ }
7081
+ if (review.keyValues.length > 0) {
7082
+ lines.push("", "Key-Value Pairs:");
7083
+ for (const kv of review.keyValues) {
7084
+ lines.push(` ${kv.key}: ${kv.value}`);
7085
+ }
7086
+ }
7087
+ if (review.tables.length > 0) {
7088
+ lines.push("", "Tables:");
7089
+ for (const table of review.tables) {
7090
+ if (table.headers.length > 0) {
7091
+ lines.push(` | ${table.headers.join(" | ")} |`);
7092
+ lines.push(` | ${table.headers.map(() => "---").join(" | ")} |`);
7093
+ }
7094
+ for (const row of table.rows) {
7095
+ lines.push(` | ${row.join(" | ")} |`);
7096
+ }
7097
+ lines.push("");
7098
+ }
7099
+ }
7100
+ lines.push("", "Forms:");
7101
+ if (review.forms.length === 0) {
7102
+ lines.push(" (none)");
7103
+ } else {
7104
+ for (const f of review.forms) {
7105
+ const disabled = f.disabled ? " (disabled)" : "";
7106
+ const label = f.label ?? "(unlabeled)";
7107
+ lines.push(` ${label} [${f.type}]: ${f.value ?? ""}${disabled}`);
7108
+ }
7109
+ }
7110
+ return lines.join("\n");
7111
+ }
7112
+ async function reviewCommand(_args, globalOptions) {
7113
+ if (globalOptions.help) {
7114
+ process.stdout.write(`${REVIEW_HELP}
7115
+ `);
7116
+ return;
7117
+ }
7118
+ const session = await resolveSession(globalOptions.session);
7119
+ const { browser, page } = await attachSession(session, { trace: globalOptions.trace });
7120
+ try {
7121
+ const review = await page.review();
7122
+ output(
7123
+ globalOptions.format === "json" ? review : formatReviewPretty(review),
7124
+ globalOptions.format
7125
+ );
7126
+ await updateSession(session.id, { currentUrl: review.url });
7127
+ } finally {
7128
+ await browser.disconnect();
7129
+ }
7130
+ }
7131
+
6601
7132
  // src/cli/commands/run.ts
6602
7133
  import { readFile } from "fs/promises";
6603
7134
  import { resolve as resolve6 } from "path";
@@ -6740,17 +7271,26 @@ ${result.success ? "Workflow passed" : "Workflow failed"} in ${result.totalDurat
6740
7271
  var SCREENSHOT_HELP = `
6741
7272
  bp screenshot - Take a screenshot of the current page
6742
7273
 
7274
+ When to use:
7275
+ You need a visual artifact, regression evidence, or a screenshot to attach elsewhere.
7276
+
7277
+ When not to use:
7278
+ You need readable copy or structured business data. Use \`bp text\`, \`bp page\`, or \`bp review\`.
7279
+
6743
7280
  Usage:
6744
7281
  bp screenshot [options]
6745
7282
 
6746
- Options:
7283
+ Local options:
6747
7284
  -o, --output <path> Save screenshot to file (default: print base64 to stdout)
6748
7285
  -f, --format <type> Image format: png | jpeg | webp (default: png)
6749
7286
  -q, --quality <n> Image quality 0-100 (jpeg/webp only)
6750
7287
  --full-page Capture the full scrollable page
7288
+
7289
+ Global options:
6751
7290
  -s, --session <id> Session to use (default: most recent)
6752
- --json Output as JSON (base64 data + metadata)
6753
- --trace Enable debug tracing
7291
+ --json Output JSON (base64 data + metadata)
7292
+ --pretty Output readable text for file writes (default)
7293
+ --debug Enable CDP transport debugging
6754
7294
  -h, --help Show this help
6755
7295
 
6756
7296
  Examples:
@@ -7096,23 +7636,27 @@ Common mistake:
7096
7636
  Usage:
7097
7637
  bp snapshot [options]
7098
7638
 
7099
- Options:
7100
- -i, --interactive Show only interactive elements (buttons, inputs, links)
7101
- -f, --format <type> Output format: full | interactive | text (default: text)
7102
- --role <roles> Filter snapshot to accessibility roles (for example: radio,checkbox)
7103
- -o, --output <path> Write command output to a file instead of stdout
7104
- -d, --diff <file> Compare current page against a saved snapshot JSON
7105
- --inspect Inject visual ref labels onto the page (auto-removes after 10s)
7106
- --keep Keep visual ref labels visible (use with --inspect)
7107
- -s, --session <id> Session to use (default: most recent)
7108
- -f, --format <fmt> Output format: json | pretty (default: pretty)
7109
- --json Alias for -f json
7110
- --debug Enable CDP transport debugging (global option)
7111
- -h, --help Show this help
7639
+ Local options:
7640
+ -i, --interactive Shortcut for --view interactive
7641
+ --view <type> Snapshot view: full | interactive | text (default: text)
7642
+ -f, --format <type> Backward-compatible alias for --view
7643
+ --role <roles> Filter snapshot to accessibility roles (for example: radio,checkbox)
7644
+ -o, --output <path> Write command output to a file instead of stdout
7645
+ -d, --diff <file> Compare current page against a saved snapshot JSON
7646
+ --inspect Inject visual ref labels onto the page (auto-removes after 10s)
7647
+ --keep Keep visual ref labels visible (use with --inspect)
7648
+
7649
+ Global options:
7650
+ -s, --session <id> Session to use (default: most recent)
7651
+ --json Output JSON; without --view this returns the full snapshot payload
7652
+ --pretty Output readable text (default)
7653
+ --debug Enable CDP transport debugging
7654
+ -h, --help Show this help
7112
7655
 
7113
7656
  Examples:
7114
7657
  bp snapshot # Full accessibility tree as readable text
7115
7658
  bp snapshot -i # Interactive elements only; best default for automation
7659
+ bp snapshot --view full # Full structured snapshot
7116
7660
  bp snapshot --role radio,checkbox # Focus on specific control roles
7117
7661
  bp snapshot --json > page.json # Save full snapshot to file
7118
7662
  bp snapshot --diff before.json # Show what changed since before.json
@@ -7129,7 +7673,7 @@ function parseSnapshotArgs(args) {
7129
7673
  };
7130
7674
  for (let i = 0; i < args.length; i++) {
7131
7675
  const arg = args[i];
7132
- if (arg === "--format" || arg === "-f") {
7676
+ if (arg === "--view" || arg === "--format" || arg === "-f") {
7133
7677
  options.format = args[++i];
7134
7678
  options.formatExplicit = true;
7135
7679
  } else if (arg === "--diff" || arg === "-d") {
@@ -7262,11 +7806,11 @@ bp targets - List page tabs available in the connected browser
7262
7806
  Usage:
7263
7807
  bp targets [options]
7264
7808
 
7265
- Options:
7809
+ Global options:
7266
7810
  -s, --session <id> Session to use (default: most recent)
7267
- -f, --format <fmt> json | pretty (default: pretty)
7268
- --json Alias for -f json
7269
- --trace Enable debug tracing
7811
+ --json Output JSON
7812
+ --pretty Output readable text (default)
7813
+ --debug Enable CDP transport debugging
7270
7814
  -h, --help Show this help
7271
7815
 
7272
7816
  Examples:
@@ -7331,28 +7875,31 @@ Common mistake:
7331
7875
  Usage:
7332
7876
  bp text [options]
7333
7877
 
7334
- Options:
7335
- --selector <sel> Extract text from a specific element (default: entire page)
7336
- -s, --session <id> Session to use (default: most recent)
7337
- -f, --format <fmt> Output format: json | pretty (default: pretty)
7338
- --json Alias for -f json
7339
- --debug Enable CDP transport debugging (global option)
7340
- -h, --help Show this help
7878
+ Local options:
7879
+ --selector <selector> Extract text from a specific element (default: entire page)
7880
+
7881
+ Global options:
7882
+ -s, --session <id> Session to use (default: most recent)
7883
+ --json Output JSON with text, URL, and selector
7884
+ --pretty Output readable text only (default)
7885
+ --debug Enable CDP transport debugging
7886
+ -h, --help Show this help
7341
7887
 
7342
7888
  Examples:
7343
7889
  bp text # Extract all text from the page
7344
7890
  bp text --selector '#main' # Extract text from #main element only
7345
- bp text --json # Output as JSON with URL and selector info
7891
+ bp text -s dev --json # Output JSON with URL and selector info
7346
7892
 
7347
7893
  Likely next commands:
7348
7894
  bp snapshot -i
7895
+ bp review --json
7349
7896
  bp exec '[{"action":"assertText","expect":"..."}]'
7350
7897
  `.trimEnd();
7351
7898
  function parseTextArgs(args) {
7352
7899
  const options = {};
7353
7900
  for (let i = 0; i < args.length; i++) {
7354
7901
  const arg = args[i];
7355
- if (arg === "--selector" || arg === "-s") {
7902
+ if (arg === "--selector") {
7356
7903
  options.selector = args[++i];
7357
7904
  } else if (arg === "-h" || arg === "--help") {
7358
7905
  options.help = true;
@@ -7360,6 +7907,9 @@ function parseTextArgs(args) {
7360
7907
  }
7361
7908
  return options;
7362
7909
  }
7910
+ function looksLikeSelector(value) {
7911
+ return value.startsWith("#") || value.startsWith(".") || value.startsWith("[") || value.startsWith("/") || value.startsWith("ref:") || value.includes(">");
7912
+ }
7363
7913
  async function textCommand(args, globalOptions) {
7364
7914
  const options = parseTextArgs(args);
7365
7915
  if (options.help || globalOptions.help) {
@@ -7368,7 +7918,18 @@ async function textCommand(args, globalOptions) {
7368
7918
  }
7369
7919
  let session;
7370
7920
  if (globalOptions.session) {
7371
- session = await loadSession(globalOptions.session);
7921
+ try {
7922
+ session = await loadSession(globalOptions.session);
7923
+ } catch (error) {
7924
+ if (!options.selector && looksLikeSelector(globalOptions.session)) {
7925
+ throw new Error(
7926
+ `bp text uses --selector for element targeting. "-s" is reserved for sessions.
7927
+
7928
+ Try: bp text --selector ${JSON.stringify(globalOptions.session)}`
7929
+ );
7930
+ }
7931
+ throw error;
7932
+ }
7372
7933
  } else {
7373
7934
  session = await getDefaultSession();
7374
7935
  if (!session) {
@@ -7726,74 +8287,77 @@ async function traceCommand(args, globalOptions) {
7726
8287
  }
7727
8288
  }
7728
8289
 
8290
+ // src/cli/version.ts
8291
+ import { readFileSync as readFileSync9 } from "fs";
8292
+ var cachedCliVersion;
8293
+ function getCliVersion() {
8294
+ if (cachedCliVersion) {
8295
+ return cachedCliVersion;
8296
+ }
8297
+ cachedCliVersion = "0.0.17";
8298
+ return cachedCliVersion;
8299
+ }
8300
+
7729
8301
  // src/cli/index.ts
7730
- var HELP2 = `
8302
+ function buildRootHelp() {
8303
+ const routeLabelWidth = Math.max(...CLI_ROUTE_GROUPS.map((group) => group.label.length)) + 2;
8304
+ const routeLines = CLI_ROUTE_GROUPS.map((group) => {
8305
+ const note = group.note ? ` ${group.note}` : "";
8306
+ return ` ${group.label.padEnd(routeLabelWidth)}${group.commands.join(", ")}${note}`;
8307
+ });
8308
+ const commandLabelWidth = Math.max(...ROOT_HELP_COMMANDS.map((command) => command.name.length)) + 2;
8309
+ const commandLines = ROOT_HELP_COMMANDS.map((command) => {
8310
+ return ` ${command.name.padEnd(commandLabelWidth)}${command.description}`;
8311
+ });
8312
+ return `
7731
8313
  bp - automation-first browser CLI for agents
7732
8314
 
7733
8315
  Route the job first:
7734
- Inspect page state snapshot, page, forms, text, targets, diagnose
7735
- Act in the browser exec, run
7736
- Capture a human demo record
7737
- Analyze behavior over time trace (listen is a compatibility alias)
7738
- Exercise voice/media audio
7739
- Change browser conditions env
8316
+ ${routeLines.join("\n")}
7740
8317
 
7741
8318
  Usage:
7742
8319
  bp <command> [options]
7743
8320
 
7744
8321
  Commands:
7745
- quickstart Getting started guide
7746
- connect Create a browser session
7747
- exec Execute high-level actions
7748
- snapshot Inspect current page with refs
7749
- record Record a human workflow and derive replayable output
7750
- trace Inspect and analyze behavior over time (listen alias for live stream)
7751
- audio Set up/validate/inject/capture voice pipelines
7752
- env Session and browser-environment controls
7753
- run Run a workflow file
7754
- page Compact page overview
7755
- forms List form controls
7756
- targets List available browser tabs
7757
- daemon Manage session daemon
7758
- list List sessions
7759
- close Close session
7760
- clean Clean old sessions and artifacts
7761
- actions Complete action reference
8322
+ ${commandLines.join("\n")}
7762
8323
 
7763
8324
  Golden paths:
7764
- 1. Automate a page
7765
- bp connect --provider generic --name dev
8325
+ 1. Connect, open a page, inspect it, then act
8326
+ bp connect --name dev
8327
+ bp exec -s dev '{"action":"goto","url":"https://example.com"}'
7766
8328
  bp snapshot -i -s dev
7767
8329
  bp exec -s dev '[{"action":"click","selector":"ref:e4"}]'
7768
8330
 
7769
- 2. Capture a manual workflow and derive automation
8331
+ 2. Read content or verify business state
8332
+ bp text -s dev --selector main
8333
+ bp review -s dev --json
8334
+
8335
+ 3. Capture a manual workflow and derive automation
7770
8336
  bp record -s demo --profile automation
7771
8337
  bp record summary demo/recording.json
7772
8338
  bp record derive demo/recording.json -o workflow.json
7773
8339
  bp run workflow.json
7774
8340
 
7775
- 3. Debug a realtime or voice session
8341
+ 4. Debug a realtime or voice session
7776
8342
  bp trace start -s dev
7777
8343
  bp trace summary -s dev --view ws
7778
8344
  bp audio check -s dev
7779
8345
  bp trace summary -s dev --view voice
7780
8346
 
7781
- 4. Exercise failure modes
7782
- bp env network offline -s dev --duration 10000
7783
- bp trace watch -s dev --view ws --assert profile:reconnect --timeout 15000
7784
-
7785
8347
  Options:
7786
8348
  -s, --session <id> Session ID
7787
8349
  -f, --format <fmt> json | pretty (default: pretty)
7788
8350
  --json Alias for -f json
8351
+ --pretty Alias for -f pretty
7789
8352
  --debug Enable debug logs for CDP transport
7790
8353
  --trace Legacy alias for --debug
7791
- --dialog <mode> Handle dialogs: accept | dismiss
7792
8354
  -h, --help Show help
8355
+ --version Print CLI version
7793
8356
 
7794
8357
  Notes:
7795
8358
  Start with "record summary" or "trace summary" before opening raw artifacts.
7796
- `;
8359
+ `.trim();
8360
+ }
7797
8361
  function parseGlobalOptions(args) {
7798
8362
  const options = {
7799
8363
  format: "pretty"
@@ -7869,14 +8433,28 @@ function prettyPrint(obj, lines, indent = 0) {
7869
8433
  }
7870
8434
  async function main() {
7871
8435
  const args = process.argv.slice(2);
7872
- if (args.length === 0) {
7873
- console.log(HELP2);
8436
+ if (args.length === 0 || args.length === 1 && (args[0] === "--help" || args[0] === "-h" || args[0] === "help")) {
8437
+ console.log(buildRootHelp());
7874
8438
  process.exit(0);
7875
8439
  }
7876
- const command = args[0];
7877
- const { options, remaining } = parseGlobalOptions(args.slice(1));
8440
+ if (args.length === 1 && (args[0] === "--version" || args[0] === "version")) {
8441
+ process.stdout.write(`${getCliVersion()}
8442
+ `);
8443
+ process.exit(0);
8444
+ }
8445
+ let command = args[0];
8446
+ let commandArgs = args.slice(1);
8447
+ if (command === "help") {
8448
+ if (commandArgs.length === 0) {
8449
+ console.log(buildRootHelp());
8450
+ process.exit(0);
8451
+ }
8452
+ command = commandArgs[0];
8453
+ commandArgs = [...commandArgs.slice(1), "--help"];
8454
+ }
8455
+ const { options, remaining } = parseGlobalOptions(commandArgs);
7878
8456
  if (options.help && !command) {
7879
- console.log(HELP2);
8457
+ console.log(buildRootHelp());
7880
8458
  process.exit(0);
7881
8459
  }
7882
8460
  try {
@@ -7932,6 +8510,9 @@ async function main() {
7932
8510
  case "record":
7933
8511
  await recordCommand(remaining, options);
7934
8512
  break;
8513
+ case "review":
8514
+ await reviewCommand(remaining, options);
8515
+ break;
7935
8516
  case "trace":
7936
8517
  await traceCommand(remaining, options);
7937
8518
  break;
@@ -7950,11 +8531,12 @@ async function main() {
7950
8531
  case "help":
7951
8532
  case "--help":
7952
8533
  case "-h":
7953
- console.log(HELP2);
8534
+ console.log(buildRootHelp());
7954
8535
  break;
7955
8536
  default:
7956
8537
  console.error(`Unknown command: ${command}`);
7957
- console.log(HELP2);
8538
+ console.error('Run "bp --help" to see the available command tree.');
8539
+ console.log(buildRootHelp());
7958
8540
  process.exit(1);
7959
8541
  }
7960
8542
  } catch (error) {