browser-pilot 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -171,6 +171,8 @@ await page.click(['ref:e4', '#submit', 'button[type=submit]']);
171
171
  ```
172
172
 
173
173
  Refs are stable until page navigation. Always take a fresh snapshot after navigating.
174
+ CLI note: refs are cached per session+URL after a snapshot, so you can reuse them across CLI calls
175
+ until navigation changes the URL.
174
176
 
175
177
  ## Page API
176
178
 
@@ -343,6 +345,7 @@ bp snapshot -s my-session --format text
343
345
  # Output: button "Submit" [ref=e4], textbox "Email" [ref=e5], ...
344
346
 
345
347
  # Use refs from snapshot for reliable targeting
348
+ # Refs are cached per session+URL after snapshot
346
349
  bp exec -s my-session '{"action":"click","selector":"ref:e4"}'
347
350
  bp exec -s my-session '{"action":"fill","selector":"ref:e5","value":"test@example.com"}'
348
351
 
@@ -1,5 +1,5 @@
1
- import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-Cs89wle0.cjs';
2
- export { A as ActionType, c as StepResult } from './types-Cs89wle0.cjs';
1
+ import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-C6m0bT04.cjs';
2
+ export { A as ActionType, c as StepResult } from './types-C6m0bT04.cjs';
3
3
  import './client-7Nqka5MV.cjs';
4
4
 
5
5
  /**
package/dist/actions.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-DL_-3BZk.js';
2
- export { A as ActionType, c as StepResult } from './types-DL_-3BZk.js';
1
+ import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-BJv2dzu0.js';
2
+ export { A as ActionType, c as StepResult } from './types-BJv2dzu0.js';
3
3
  import './client-7Nqka5MV.js';
4
4
 
5
5
  /**
package/dist/browser.cjs CHANGED
@@ -1608,10 +1608,14 @@ var Page = class {
1608
1608
  this.currentFrame = frameKey;
1609
1609
  this.rootNodeId = descResult.node.contentDocument.nodeId;
1610
1610
  if (descResult.node.frameId) {
1611
- let contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1612
- if (!contextId) {
1613
- await new Promise((resolve) => setTimeout(resolve, 50));
1614
- contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1611
+ const frameId = descResult.node.frameId;
1612
+ const { timeout = DEFAULT_TIMEOUT2 } = options;
1613
+ const pollInterval = 50;
1614
+ const deadline = Date.now() + timeout;
1615
+ let contextId = this.frameExecutionContexts.get(frameId);
1616
+ while (!contextId && Date.now() < deadline) {
1617
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1618
+ contextId = this.frameExecutionContexts.get(frameId);
1615
1619
  }
1616
1620
  if (contextId) {
1617
1621
  this.currentFrameContextId = contextId;
@@ -1947,6 +1951,27 @@ var Page = class {
1947
1951
  text
1948
1952
  };
1949
1953
  }
1954
+ /**
1955
+ * Export the current ref map for cross-exec reuse (CLI).
1956
+ */
1957
+ exportRefMap() {
1958
+ const map = {};
1959
+ for (const [ref, backendNodeId] of this.refMap.entries()) {
1960
+ map[ref] = backendNodeId;
1961
+ }
1962
+ return map;
1963
+ }
1964
+ /**
1965
+ * Import a ref map previously captured from a snapshot.
1966
+ */
1967
+ importRefMap(refMap) {
1968
+ this.refMap.clear();
1969
+ for (const [ref, backendNodeId] of Object.entries(refMap)) {
1970
+ if (typeof backendNodeId === "number") {
1971
+ this.refMap.set(ref, backendNodeId);
1972
+ }
1973
+ }
1974
+ }
1950
1975
  // ============ Batch Execution ============
1951
1976
  /**
1952
1977
  * Execute a batch of steps
@@ -1,7 +1,7 @@
1
1
  import { C as CDPClient } from './client-7Nqka5MV.cjs';
2
2
  import { C as ConnectOptions } from './types-D_uDqh0Z.cjs';
3
- import { P as Page } from './types-Cs89wle0.cjs';
4
- export { d as ActionOptions, e as ActionResult, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, h as CustomSelectConfig, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, F as FileInput, o as FillOptions, G as GeolocationOptions, I as InteractiveElement, N as NavigationError, p as NetworkIdleOptions, q as PageError, r as PageSnapshot, s as SnapshotNode, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions } from './types-Cs89wle0.cjs';
3
+ import { P as Page } from './types-C6m0bT04.cjs';
4
+ export { d as ActionOptions, e as ActionResult, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, h as CustomSelectConfig, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, F as FileInput, o as FillOptions, G as GeolocationOptions, I as InteractiveElement, N as NavigationError, p as NetworkIdleOptions, q as PageError, r as PageSnapshot, s as SnapshotNode, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions } from './types-C6m0bT04.cjs';
5
5
 
6
6
  /**
7
7
  * Browser class - manages CDP connection and pages
package/dist/browser.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { C as CDPClient } from './client-7Nqka5MV.js';
2
2
  import { C as ConnectOptions } from './types-D_uDqh0Z.js';
3
- import { P as Page } from './types-DL_-3BZk.js';
4
- export { d as ActionOptions, e as ActionResult, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, h as CustomSelectConfig, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, F as FileInput, o as FillOptions, G as GeolocationOptions, I as InteractiveElement, N as NavigationError, p as NetworkIdleOptions, q as PageError, r as PageSnapshot, s as SnapshotNode, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions } from './types-DL_-3BZk.js';
3
+ import { P as Page } from './types-BJv2dzu0.js';
4
+ export { d as ActionOptions, e as ActionResult, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, h as CustomSelectConfig, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, F as FileInput, o as FillOptions, G as GeolocationOptions, I as InteractiveElement, N as NavigationError, p as NetworkIdleOptions, q as PageError, r as PageSnapshot, s as SnapshotNode, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions } from './types-BJv2dzu0.js';
5
5
 
6
6
  /**
7
7
  * Browser class - manages CDP connection and pages
package/dist/browser.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  Page,
6
6
  TimeoutError,
7
7
  connect
8
- } from "./chunk-FI55U7JS.mjs";
8
+ } from "./chunk-CWSTSVWO.mjs";
9
9
  import "./chunk-BCOZUKWS.mjs";
10
10
  import "./chunk-R3PS4PCM.mjs";
11
11
  import "./chunk-YEHK2XY3.mjs";
@@ -946,10 +946,14 @@ var Page = class {
946
946
  this.currentFrame = frameKey;
947
947
  this.rootNodeId = descResult.node.contentDocument.nodeId;
948
948
  if (descResult.node.frameId) {
949
- let contextId = this.frameExecutionContexts.get(descResult.node.frameId);
950
- if (!contextId) {
951
- await new Promise((resolve) => setTimeout(resolve, 50));
952
- contextId = this.frameExecutionContexts.get(descResult.node.frameId);
949
+ const frameId = descResult.node.frameId;
950
+ const { timeout = DEFAULT_TIMEOUT } = options;
951
+ const pollInterval = 50;
952
+ const deadline = Date.now() + timeout;
953
+ let contextId = this.frameExecutionContexts.get(frameId);
954
+ while (!contextId && Date.now() < deadline) {
955
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
956
+ contextId = this.frameExecutionContexts.get(frameId);
953
957
  }
954
958
  if (contextId) {
955
959
  this.currentFrameContextId = contextId;
@@ -1285,6 +1289,27 @@ var Page = class {
1285
1289
  text
1286
1290
  };
1287
1291
  }
1292
+ /**
1293
+ * Export the current ref map for cross-exec reuse (CLI).
1294
+ */
1295
+ exportRefMap() {
1296
+ const map = {};
1297
+ for (const [ref, backendNodeId] of this.refMap.entries()) {
1298
+ map[ref] = backendNodeId;
1299
+ }
1300
+ return map;
1301
+ }
1302
+ /**
1303
+ * Import a ref map previously captured from a snapshot.
1304
+ */
1305
+ importRefMap(refMap) {
1306
+ this.refMap.clear();
1307
+ for (const [ref, backendNodeId] of Object.entries(refMap)) {
1308
+ if (typeof backendNodeId === "number") {
1309
+ this.refMap.set(ref, backendNodeId);
1310
+ }
1311
+ }
1312
+ }
1288
1313
  // ============ Batch Execution ============
1289
1314
  /**
1290
1315
  * Execute a batch of steps
package/dist/cli.cjs CHANGED
@@ -150,6 +150,7 @@ REF SELECTORS (from snapshot)
150
150
  bp exec '{"action":"click","selector":"ref:e4"}'
151
151
 
152
152
  Refs are stable until navigation. Prefix with "ref:" to use.
153
+ CLI caches refs per session+URL after snapshot, so they can be reused across exec calls.
153
154
  Example: {"action":"fill","selector":"ref:e23","value":"hello"}
154
155
 
155
156
  MULTI-SELECTOR PATTERN
@@ -1801,10 +1802,14 @@ var Page = class {
1801
1802
  this.currentFrame = frameKey;
1802
1803
  this.rootNodeId = descResult.node.contentDocument.nodeId;
1803
1804
  if (descResult.node.frameId) {
1804
- let contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1805
- if (!contextId) {
1806
- await new Promise((resolve) => setTimeout(resolve, 50));
1807
- contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1805
+ const frameId = descResult.node.frameId;
1806
+ const { timeout = DEFAULT_TIMEOUT2 } = options;
1807
+ const pollInterval = 50;
1808
+ const deadline = Date.now() + timeout;
1809
+ let contextId = this.frameExecutionContexts.get(frameId);
1810
+ while (!contextId && Date.now() < deadline) {
1811
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1812
+ contextId = this.frameExecutionContexts.get(frameId);
1808
1813
  }
1809
1814
  if (contextId) {
1810
1815
  this.currentFrameContextId = contextId;
@@ -2140,6 +2145,27 @@ var Page = class {
2140
2145
  text
2141
2146
  };
2142
2147
  }
2148
+ /**
2149
+ * Export the current ref map for cross-exec reuse (CLI).
2150
+ */
2151
+ exportRefMap() {
2152
+ const map = {};
2153
+ for (const [ref, backendNodeId] of this.refMap.entries()) {
2154
+ map[ref] = backendNodeId;
2155
+ }
2156
+ return map;
2157
+ }
2158
+ /**
2159
+ * Import a ref map previously captured from a snapshot.
2160
+ */
2161
+ importRefMap(refMap) {
2162
+ this.refMap.clear();
2163
+ for (const [ref, backendNodeId] of Object.entries(refMap)) {
2164
+ if (typeof backendNodeId === "number") {
2165
+ this.refMap.set(ref, backendNodeId);
2166
+ }
2167
+ }
2168
+ }
2143
2169
  // ============ Batch Execution ============
2144
2170
  /**
2145
2171
  * Execute a batch of steps
@@ -2977,9 +3003,11 @@ async function loadSession(id) {
2977
3003
  }
2978
3004
  async function updateSession(id, updates) {
2979
3005
  const session = await loadSession(id);
3006
+ const mergedMetadata = updates.metadata !== void 0 ? { ...session.metadata ?? {}, ...updates.metadata ?? {} } : session.metadata;
2980
3007
  const updated = {
2981
3008
  ...session,
2982
3009
  ...updates,
3010
+ metadata: mergedMetadata,
2983
3011
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
2984
3012
  };
2985
3013
  await saveSession(updated);
@@ -3197,6 +3225,11 @@ Run 'bp actions' for complete action reference.`
3197
3225
  });
3198
3226
  try {
3199
3227
  const page = addBatchToPage(await browser.page());
3228
+ const currentUrlForCache = await page.url();
3229
+ const refCache = session.metadata?.refCache;
3230
+ if (refCache && refCache.url === currentUrlForCache) {
3231
+ page.importRefMap(refCache.refMap);
3232
+ }
3200
3233
  if (execOptions.dialog) {
3201
3234
  await page.onDialog(async (dialog) => {
3202
3235
  if (execOptions.dialog === "accept") {
@@ -3209,7 +3242,21 @@ Run 'bp actions' for complete action reference.`
3209
3242
  const steps = Array.isArray(actions) ? actions : [actions];
3210
3243
  const result = await page.batch(steps);
3211
3244
  const currentUrl = await page.url();
3212
- await updateSession(session.id, { currentUrl });
3245
+ const hasSnapshot = steps.some((step) => step.action === "snapshot");
3246
+ if (hasSnapshot) {
3247
+ await updateSession(session.id, {
3248
+ currentUrl,
3249
+ metadata: {
3250
+ refCache: {
3251
+ url: currentUrl,
3252
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3253
+ refMap: page.exportRefMap()
3254
+ }
3255
+ }
3256
+ });
3257
+ } else {
3258
+ await updateSession(session.id, { currentUrl });
3259
+ }
3213
3260
  output(
3214
3261
  {
3215
3262
  success: result.success,
@@ -3268,6 +3315,74 @@ function getAge(date) {
3268
3315
  return `${days}d ago`;
3269
3316
  }
3270
3317
 
3318
+ // src/cli/commands/quickstart.ts
3319
+ var QUICKSTART = `
3320
+ browser-pilot - CDP-based browser automation for AI agents
3321
+
3322
+ Zero production dependencies. Works in Node.js, Bun, and Cloudflare Workers.
3323
+
3324
+ RUNNING
3325
+ npx browser-pilot ... # Node.js projects
3326
+ bunx browser-pilot ... # Bun projects (faster)
3327
+
3328
+ CONNECTING
3329
+ npx browser-pilot connect <wsUrl> Connect to existing browser
3330
+ npx browser-pilot connect --provider browserbase --api-key <key>
3331
+
3332
+ BASIC USAGE (Code)
3333
+ import { Browser } from 'browser-pilot';
3334
+
3335
+ const browser = await Browser.connect({ wsUrl: '...' });
3336
+ const page = await browser.newPage();
3337
+ await page.goto('https://example.com');
3338
+ await page.click('#button');
3339
+ await page.fill('#input', 'text');
3340
+ await browser.close();
3341
+
3342
+ KEY PATTERNS
3343
+ Multi-Selector await page.click(['#primary', '.fallback', 'button']);
3344
+ Smart Waiting Every action waits for visibility automatically
3345
+ Optional Actions await page.click('#banner', { optional: true });
3346
+
3347
+ BATCH EXECUTION
3348
+ await page.batch([
3349
+ { action: 'goto', url: 'https://example.com' },
3350
+ { action: 'fill', selector: '#email', value: 'test@test.com' },
3351
+ { action: 'submit', selector: 'form' },
3352
+ ]);
3353
+
3354
+ SNAPSHOTS (FOR AI AGENTS)
3355
+ const snapshot = await page.snapshot();
3356
+ // Returns accessibility tree with refs: e1, e2, e3...
3357
+ await page.click({ ref: 'e5' });
3358
+
3359
+ PROVIDERS
3360
+ BrowserBase Browser.connect({ provider: 'browserbase', apiKey })
3361
+ Browserless Browser.connect({ provider: 'browserless', apiKey })
3362
+ Generic Browser.connect({ wsUrl: 'ws://...' })
3363
+
3364
+ COMMON ACTIONS
3365
+ page.goto(url) Navigate to URL
3366
+ page.click(selector) Click element
3367
+ page.fill(selector, value) Fill input field
3368
+ page.submit(selector) Submit form
3369
+ page.select(selector, val) Select dropdown option
3370
+ page.screenshot() Capture screenshot
3371
+ page.snapshot() Get accessibility tree
3372
+
3373
+ AGENT INTEGRATION
3374
+ - Use snapshot() to get page state as accessibility tree
3375
+ - Refs (e1, e2...) identify elements without fragile selectors
3376
+ - Multi-selector arrays handle UI variations
3377
+ - optional: true prevents failures on transient elements
3378
+
3379
+ Ready to automate!
3380
+ Run: npx browser-pilot connect <wsUrl>
3381
+ `;
3382
+ async function quickstartCommand() {
3383
+ console.log(QUICKSTART);
3384
+ }
3385
+
3271
3386
  // src/cli/commands/screenshot.ts
3272
3387
  function parseScreenshotArgs(args) {
3273
3388
  const options = {};
@@ -3362,7 +3477,16 @@ async function snapshotCommand(args, globalOptions) {
3362
3477
  try {
3363
3478
  const page = await browser.page();
3364
3479
  const snapshot = await page.snapshot();
3365
- await updateSession(session.id, { currentUrl: snapshot.url });
3480
+ await updateSession(session.id, {
3481
+ currentUrl: snapshot.url,
3482
+ metadata: {
3483
+ refCache: {
3484
+ url: snapshot.url,
3485
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3486
+ refMap: page.exportRefMap()
3487
+ }
3488
+ }
3489
+ });
3366
3490
  switch (options.format) {
3367
3491
  case "interactive":
3368
3492
  output(snapshot.interactiveElements, globalOptions.output);
@@ -3429,6 +3553,7 @@ Usage:
3429
3553
  bp <command> [options]
3430
3554
 
3431
3555
  Commands:
3556
+ quickstart Show getting started guide
3432
3557
  connect Create or resume browser session
3433
3558
  exec Execute actions on current session
3434
3559
  snapshot Get page accessibility snapshot (includes element refs)
@@ -3448,11 +3573,13 @@ Exec Options:
3448
3573
  --dialog <mode> Auto-handle dialogs: accept | dismiss
3449
3574
 
3450
3575
  Ref Selectors (Recommended for AI Agents):
3451
- 1. Take snapshot: bp snapshot --format text
3452
- Output shows: button "Submit" [ref=e4], textbox "Email" [ref=e5]
3453
- 2. Use ref directly: bp exec '{"action":"click","selector":"ref:e4"}'
3576
+ 1. Navigate + snapshot: bp exec '[{"action":"goto","url":"..."},{"action":"snapshot"}]'
3577
+ Output shows: button "Submit" [ref=e4], textbox "Email" [ref=e5]
3578
+ 2. Use refs (snapshot cached for same session+URL):
3579
+ bp exec '[{"action":"click","selector":"ref:e4"}]'
3454
3580
 
3455
- Refs are stable until navigation. Combine with CSS fallbacks:
3581
+ Refs are cached per session+URL after snapshot. Use new snapshot after navigation.
3582
+ Combine with CSS fallbacks:
3456
3583
  {"selector": ["ref:e4", "#submit", "button[type=submit]"]}
3457
3584
 
3458
3585
  Examples:
@@ -3460,18 +3587,17 @@ Examples:
3460
3587
  bp connect --provider generic --name dev
3461
3588
 
3462
3589
  # Navigate and get snapshot with refs
3463
- bp exec '{"action":"goto","url":"https://example.com"}'
3464
- bp snapshot --format text
3590
+ bp exec '[{"action":"goto","url":"https://example.com"},{"action":"snapshot"}]'
3465
3591
 
3466
- # Use ref from snapshot (most reliable)
3467
- bp exec '{"action":"click","selector":"ref:e4"}'
3468
- bp exec '{"action":"fill","selector":"ref:e5","value":"test@example.com"}'
3592
+ # Use refs (snapshot cached for same session+URL)
3593
+ bp exec '[{"action":"fill","selector":"ref:e5","value":"test@example.com"},{"action":"click","selector":"ref:e4"}]'
3469
3594
 
3470
3595
  # Handle native dialogs (alert/confirm/prompt)
3471
3596
  bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
3472
3597
 
3473
- # Batch multiple actions
3598
+ # Batch multiple actions (snapshot optional if already cached)
3474
3599
  bp exec '[
3600
+ {"action":"snapshot"},
3475
3601
  {"action":"fill","selector":"ref:e5","value":"user@example.com"},
3476
3602
  {"action":"click","selector":"ref:e4"},
3477
3603
  {"action":"snapshot"}
@@ -3540,6 +3666,9 @@ async function main() {
3540
3666
  }
3541
3667
  try {
3542
3668
  switch (command) {
3669
+ case "quickstart":
3670
+ await quickstartCommand();
3671
+ break;
3543
3672
  case "connect":
3544
3673
  await connectCommand(remaining, options);
3545
3674
  break;
package/dist/cli.d.cts CHANGED
@@ -7,6 +7,7 @@
7
7
  * 2. bp exec '{"selector":"ref:e4",...}' → Use refs for reliable targeting
8
8
  *
9
9
  * Commands:
10
+ * quickstart Getting started guide
10
11
  * connect Create browser session
11
12
  * exec Execute actions (supports --dialog accept|dismiss)
12
13
  * snapshot Get page snapshot with element refs
@@ -16,7 +17,7 @@
16
17
  * list List sessions
17
18
  * actions Complete action reference
18
19
  *
19
- * Run 'bp actions' for full action DSL documentation.
20
+ * Run 'bp quickstart' for getting started guide.
20
21
  */
21
22
  declare function output(data: unknown, format?: 'json' | 'pretty'): void;
22
23
 
package/dist/cli.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * 2. bp exec '{"selector":"ref:e4",...}' → Use refs for reliable targeting
8
8
  *
9
9
  * Commands:
10
+ * quickstart Getting started guide
10
11
  * connect Create browser session
11
12
  * exec Execute actions (supports --dialog accept|dismiss)
12
13
  * snapshot Get page snapshot with element refs
@@ -16,7 +17,7 @@
16
17
  * list List sessions
17
18
  * actions Complete action reference
18
19
  *
19
- * Run 'bp actions' for full action DSL documentation.
20
+ * Run 'bp quickstart' for getting started guide.
20
21
  */
21
22
  declare function output(data: unknown, format?: 'json' | 'pretty'): void;
22
23
 
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import "./chunk-ZIQA4JOT.mjs";
3
3
  import {
4
4
  connect
5
- } from "./chunk-FI55U7JS.mjs";
5
+ } from "./chunk-CWSTSVWO.mjs";
6
6
  import "./chunk-BCOZUKWS.mjs";
7
7
  import {
8
8
  getBrowserWebSocketUrl
@@ -126,6 +126,7 @@ REF SELECTORS (from snapshot)
126
126
  bp exec '{"action":"click","selector":"ref:e4"}'
127
127
 
128
128
  Refs are stable until navigation. Prefix with "ref:" to use.
129
+ CLI caches refs per session+URL after snapshot, so they can be reused across exec calls.
129
130
  Example: {"action":"fill","selector":"ref:e23","value":"hello"}
130
131
 
131
132
  MULTI-SELECTOR PATTERN
@@ -218,9 +219,11 @@ async function loadSession(id) {
218
219
  }
219
220
  async function updateSession(id, updates) {
220
221
  const session = await loadSession(id);
222
+ const mergedMetadata = updates.metadata !== void 0 ? { ...session.metadata ?? {}, ...updates.metadata ?? {} } : session.metadata;
221
223
  const updated = {
222
224
  ...session,
223
225
  ...updates,
226
+ metadata: mergedMetadata,
224
227
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
225
228
  };
226
229
  await saveSession(updated);
@@ -438,6 +441,11 @@ Run 'bp actions' for complete action reference.`
438
441
  });
439
442
  try {
440
443
  const page = addBatchToPage(await browser.page());
444
+ const currentUrlForCache = await page.url();
445
+ const refCache = session.metadata?.refCache;
446
+ if (refCache && refCache.url === currentUrlForCache) {
447
+ page.importRefMap(refCache.refMap);
448
+ }
441
449
  if (execOptions.dialog) {
442
450
  await page.onDialog(async (dialog) => {
443
451
  if (execOptions.dialog === "accept") {
@@ -450,7 +458,21 @@ Run 'bp actions' for complete action reference.`
450
458
  const steps = Array.isArray(actions) ? actions : [actions];
451
459
  const result = await page.batch(steps);
452
460
  const currentUrl = await page.url();
453
- await updateSession(session.id, { currentUrl });
461
+ const hasSnapshot = steps.some((step) => step.action === "snapshot");
462
+ if (hasSnapshot) {
463
+ await updateSession(session.id, {
464
+ currentUrl,
465
+ metadata: {
466
+ refCache: {
467
+ url: currentUrl,
468
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
469
+ refMap: page.exportRefMap()
470
+ }
471
+ }
472
+ });
473
+ } else {
474
+ await updateSession(session.id, { currentUrl });
475
+ }
454
476
  output(
455
477
  {
456
478
  success: result.success,
@@ -509,6 +531,74 @@ function getAge(date) {
509
531
  return `${days}d ago`;
510
532
  }
511
533
 
534
+ // src/cli/commands/quickstart.ts
535
+ var QUICKSTART = `
536
+ browser-pilot - CDP-based browser automation for AI agents
537
+
538
+ Zero production dependencies. Works in Node.js, Bun, and Cloudflare Workers.
539
+
540
+ RUNNING
541
+ npx browser-pilot ... # Node.js projects
542
+ bunx browser-pilot ... # Bun projects (faster)
543
+
544
+ CONNECTING
545
+ npx browser-pilot connect <wsUrl> Connect to existing browser
546
+ npx browser-pilot connect --provider browserbase --api-key <key>
547
+
548
+ BASIC USAGE (Code)
549
+ import { Browser } from 'browser-pilot';
550
+
551
+ const browser = await Browser.connect({ wsUrl: '...' });
552
+ const page = await browser.newPage();
553
+ await page.goto('https://example.com');
554
+ await page.click('#button');
555
+ await page.fill('#input', 'text');
556
+ await browser.close();
557
+
558
+ KEY PATTERNS
559
+ Multi-Selector await page.click(['#primary', '.fallback', 'button']);
560
+ Smart Waiting Every action waits for visibility automatically
561
+ Optional Actions await page.click('#banner', { optional: true });
562
+
563
+ BATCH EXECUTION
564
+ await page.batch([
565
+ { action: 'goto', url: 'https://example.com' },
566
+ { action: 'fill', selector: '#email', value: 'test@test.com' },
567
+ { action: 'submit', selector: 'form' },
568
+ ]);
569
+
570
+ SNAPSHOTS (FOR AI AGENTS)
571
+ const snapshot = await page.snapshot();
572
+ // Returns accessibility tree with refs: e1, e2, e3...
573
+ await page.click({ ref: 'e5' });
574
+
575
+ PROVIDERS
576
+ BrowserBase Browser.connect({ provider: 'browserbase', apiKey })
577
+ Browserless Browser.connect({ provider: 'browserless', apiKey })
578
+ Generic Browser.connect({ wsUrl: 'ws://...' })
579
+
580
+ COMMON ACTIONS
581
+ page.goto(url) Navigate to URL
582
+ page.click(selector) Click element
583
+ page.fill(selector, value) Fill input field
584
+ page.submit(selector) Submit form
585
+ page.select(selector, val) Select dropdown option
586
+ page.screenshot() Capture screenshot
587
+ page.snapshot() Get accessibility tree
588
+
589
+ AGENT INTEGRATION
590
+ - Use snapshot() to get page state as accessibility tree
591
+ - Refs (e1, e2...) identify elements without fragile selectors
592
+ - Multi-selector arrays handle UI variations
593
+ - optional: true prevents failures on transient elements
594
+
595
+ Ready to automate!
596
+ Run: npx browser-pilot connect <wsUrl>
597
+ `;
598
+ async function quickstartCommand() {
599
+ console.log(QUICKSTART);
600
+ }
601
+
512
602
  // src/cli/commands/screenshot.ts
513
603
  function parseScreenshotArgs(args) {
514
604
  const options = {};
@@ -603,7 +693,16 @@ async function snapshotCommand(args, globalOptions) {
603
693
  try {
604
694
  const page = await browser.page();
605
695
  const snapshot = await page.snapshot();
606
- await updateSession(session.id, { currentUrl: snapshot.url });
696
+ await updateSession(session.id, {
697
+ currentUrl: snapshot.url,
698
+ metadata: {
699
+ refCache: {
700
+ url: snapshot.url,
701
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
702
+ refMap: page.exportRefMap()
703
+ }
704
+ }
705
+ });
607
706
  switch (options.format) {
608
707
  case "interactive":
609
708
  output(snapshot.interactiveElements, globalOptions.output);
@@ -670,6 +769,7 @@ Usage:
670
769
  bp <command> [options]
671
770
 
672
771
  Commands:
772
+ quickstart Show getting started guide
673
773
  connect Create or resume browser session
674
774
  exec Execute actions on current session
675
775
  snapshot Get page accessibility snapshot (includes element refs)
@@ -689,11 +789,13 @@ Exec Options:
689
789
  --dialog <mode> Auto-handle dialogs: accept | dismiss
690
790
 
691
791
  Ref Selectors (Recommended for AI Agents):
692
- 1. Take snapshot: bp snapshot --format text
693
- Output shows: button "Submit" [ref=e4], textbox "Email" [ref=e5]
694
- 2. Use ref directly: bp exec '{"action":"click","selector":"ref:e4"}'
792
+ 1. Navigate + snapshot: bp exec '[{"action":"goto","url":"..."},{"action":"snapshot"}]'
793
+ Output shows: button "Submit" [ref=e4], textbox "Email" [ref=e5]
794
+ 2. Use refs (snapshot cached for same session+URL):
795
+ bp exec '[{"action":"click","selector":"ref:e4"}]'
695
796
 
696
- Refs are stable until navigation. Combine with CSS fallbacks:
797
+ Refs are cached per session+URL after snapshot. Use new snapshot after navigation.
798
+ Combine with CSS fallbacks:
697
799
  {"selector": ["ref:e4", "#submit", "button[type=submit]"]}
698
800
 
699
801
  Examples:
@@ -701,18 +803,17 @@ Examples:
701
803
  bp connect --provider generic --name dev
702
804
 
703
805
  # Navigate and get snapshot with refs
704
- bp exec '{"action":"goto","url":"https://example.com"}'
705
- bp snapshot --format text
806
+ bp exec '[{"action":"goto","url":"https://example.com"},{"action":"snapshot"}]'
706
807
 
707
- # Use ref from snapshot (most reliable)
708
- bp exec '{"action":"click","selector":"ref:e4"}'
709
- bp exec '{"action":"fill","selector":"ref:e5","value":"test@example.com"}'
808
+ # Use refs (snapshot cached for same session+URL)
809
+ bp exec '[{"action":"fill","selector":"ref:e5","value":"test@example.com"},{"action":"click","selector":"ref:e4"}]'
710
810
 
711
811
  # Handle native dialogs (alert/confirm/prompt)
712
812
  bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
713
813
 
714
- # Batch multiple actions
814
+ # Batch multiple actions (snapshot optional if already cached)
715
815
  bp exec '[
816
+ {"action":"snapshot"},
716
817
  {"action":"fill","selector":"ref:e5","value":"user@example.com"},
717
818
  {"action":"click","selector":"ref:e4"},
718
819
  {"action":"snapshot"}
@@ -781,6 +882,9 @@ async function main() {
781
882
  }
782
883
  try {
783
884
  switch (command) {
885
+ case "quickstart":
886
+ await quickstartCommand();
887
+ break;
784
888
  case "connect":
785
889
  await connectCommand(remaining, options);
786
890
  break;
package/dist/index.cjs CHANGED
@@ -1678,10 +1678,14 @@ var Page = class {
1678
1678
  this.currentFrame = frameKey;
1679
1679
  this.rootNodeId = descResult.node.contentDocument.nodeId;
1680
1680
  if (descResult.node.frameId) {
1681
- let contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1682
- if (!contextId) {
1683
- await new Promise((resolve) => setTimeout(resolve, 50));
1684
- contextId = this.frameExecutionContexts.get(descResult.node.frameId);
1681
+ const frameId = descResult.node.frameId;
1682
+ const { timeout = DEFAULT_TIMEOUT2 } = options;
1683
+ const pollInterval = 50;
1684
+ const deadline = Date.now() + timeout;
1685
+ let contextId = this.frameExecutionContexts.get(frameId);
1686
+ while (!contextId && Date.now() < deadline) {
1687
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1688
+ contextId = this.frameExecutionContexts.get(frameId);
1685
1689
  }
1686
1690
  if (contextId) {
1687
1691
  this.currentFrameContextId = contextId;
@@ -2017,6 +2021,27 @@ var Page = class {
2017
2021
  text
2018
2022
  };
2019
2023
  }
2024
+ /**
2025
+ * Export the current ref map for cross-exec reuse (CLI).
2026
+ */
2027
+ exportRefMap() {
2028
+ const map = {};
2029
+ for (const [ref, backendNodeId] of this.refMap.entries()) {
2030
+ map[ref] = backendNodeId;
2031
+ }
2032
+ return map;
2033
+ }
2034
+ /**
2035
+ * Import a ref map previously captured from a snapshot.
2036
+ */
2037
+ importRefMap(refMap) {
2038
+ this.refMap.clear();
2039
+ for (const [ref, backendNodeId] of Object.entries(refMap)) {
2040
+ if (typeof backendNodeId === "number") {
2041
+ this.refMap.set(ref, backendNodeId);
2042
+ }
2043
+ }
2044
+ }
2020
2045
  // ============ Batch Execution ============
2021
2046
  /**
2022
2047
  * Execute a batch of steps
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { BatchExecutor, addBatchToPage } from './actions.cjs';
2
- import { R as RequestPattern, a as RequestHandler } from './types-Cs89wle0.cjs';
3
- export { d as ActionOptions, e as ActionResult, A as ActionType, B as BatchOptions, b as BatchResult, Q as ClearCookiesOptions, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, z as ContinueRequestOptions, X as Cookie, h as CustomSelectConfig, Y as DeleteCookieOptions, w as DeviceDescriptor, x as DeviceName, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, H as FailRequestOptions, F as FileInput, o as FillOptions, J as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, K as InterceptedRequest, N as NavigationError, p as NetworkIdleOptions, P as Page, q as PageError, r as PageSnapshot, L as RequestActions, M as ResourceType, O as RouteOptions, Z as SetCookieOptions, s as SnapshotNode, S as Step, c as StepResult, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions, _ as WaitOptions, $ as WaitResult, a0 as WaitState, y as devices, a1 as waitForAnyElement, a2 as waitForElement, a3 as waitForNavigation, a4 as waitForNetworkIdle } from './types-Cs89wle0.cjs';
2
+ import { R as RequestPattern, a as RequestHandler } from './types-C6m0bT04.cjs';
3
+ export { d as ActionOptions, e as ActionResult, A as ActionType, B as BatchOptions, b as BatchResult, Q as ClearCookiesOptions, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, z as ContinueRequestOptions, X as Cookie, h as CustomSelectConfig, Y as DeleteCookieOptions, w as DeviceDescriptor, x as DeviceName, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, H as FailRequestOptions, F as FileInput, o as FillOptions, J as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, K as InterceptedRequest, N as NavigationError, p as NetworkIdleOptions, P as Page, q as PageError, r as PageSnapshot, L as RequestActions, M as ResourceType, O as RouteOptions, Z as SetCookieOptions, s as SnapshotNode, S as Step, c as StepResult, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions, _ as WaitOptions, $ as WaitResult, a0 as WaitState, y as devices, a1 as waitForAnyElement, a2 as waitForElement, a3 as waitForNavigation, a4 as waitForNetworkIdle } from './types-C6m0bT04.cjs';
4
4
  export { Browser, BrowserOptions, connect } from './browser.cjs';
5
5
  import { C as CDPClient } from './client-7Nqka5MV.cjs';
6
6
  export { a as CDPClientOptions, c as createCDPClient } from './client-7Nqka5MV.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { BatchExecutor, addBatchToPage } from './actions.js';
2
- import { R as RequestPattern, a as RequestHandler } from './types-DL_-3BZk.js';
3
- export { d as ActionOptions, e as ActionResult, A as ActionType, B as BatchOptions, b as BatchResult, Q as ClearCookiesOptions, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, z as ContinueRequestOptions, X as Cookie, h as CustomSelectConfig, Y as DeleteCookieOptions, w as DeviceDescriptor, x as DeviceName, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, H as FailRequestOptions, F as FileInput, o as FillOptions, J as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, K as InterceptedRequest, N as NavigationError, p as NetworkIdleOptions, P as Page, q as PageError, r as PageSnapshot, L as RequestActions, M as ResourceType, O as RouteOptions, Z as SetCookieOptions, s as SnapshotNode, S as Step, c as StepResult, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions, _ as WaitOptions, $ as WaitResult, a0 as WaitState, y as devices, a1 as waitForAnyElement, a2 as waitForElement, a3 as waitForNavigation, a4 as waitForNetworkIdle } from './types-DL_-3BZk.js';
2
+ import { R as RequestPattern, a as RequestHandler } from './types-BJv2dzu0.js';
3
+ export { d as ActionOptions, e as ActionResult, A as ActionType, B as BatchOptions, b as BatchResult, Q as ClearCookiesOptions, C as ConsoleHandler, f as ConsoleMessage, g as ConsoleMessageType, z as ContinueRequestOptions, X as Cookie, h as CustomSelectConfig, Y as DeleteCookieOptions, w as DeviceDescriptor, x as DeviceName, D as Dialog, i as DialogHandler, j as DialogType, k as Download, E as ElementInfo, l as ElementNotFoundError, m as EmulationState, n as ErrorHandler, H as FailRequestOptions, F as FileInput, o as FillOptions, J as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, K as InterceptedRequest, N as NavigationError, p as NetworkIdleOptions, P as Page, q as PageError, r as PageSnapshot, L as RequestActions, M as ResourceType, O as RouteOptions, Z as SetCookieOptions, s as SnapshotNode, S as Step, c as StepResult, t as SubmitOptions, T as TimeoutError, u as TypeOptions, U as UserAgentMetadata, v as UserAgentOptions, V as ViewportOptions, W as WaitForOptions, _ as WaitOptions, $ as WaitResult, a0 as WaitState, y as devices, a1 as waitForAnyElement, a2 as waitForElement, a3 as waitForNavigation, a4 as waitForNetworkIdle } from './types-BJv2dzu0.js';
4
4
  export { Browser, BrowserOptions, connect } from './browser.js';
5
5
  import { C as CDPClient } from './client-7Nqka5MV.js';
6
6
  export { a as CDPClientOptions, c as createCDPClient } from './client-7Nqka5MV.js';
package/dist/index.mjs CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  waitForElement,
18
18
  waitForNavigation,
19
19
  waitForNetworkIdle
20
- } from "./chunk-FI55U7JS.mjs";
20
+ } from "./chunk-CWSTSVWO.mjs";
21
21
  import {
22
22
  CDPError,
23
23
  createCDPClient
@@ -626,6 +626,14 @@ declare class Page {
626
626
  * Get an accessibility tree snapshot of the page
627
627
  */
628
628
  snapshot(): Promise<PageSnapshot>;
629
+ /**
630
+ * Export the current ref map for cross-exec reuse (CLI).
631
+ */
632
+ exportRefMap(): Record<string, number>;
633
+ /**
634
+ * Import a ref map previously captured from a snapshot.
635
+ */
636
+ importRefMap(refMap: Record<string, number>): void;
629
637
  /**
630
638
  * Execute a batch of steps
631
639
  */
@@ -626,6 +626,14 @@ declare class Page {
626
626
  * Get an accessibility tree snapshot of the page
627
627
  */
628
628
  snapshot(): Promise<PageSnapshot>;
629
+ /**
630
+ * Export the current ref map for cross-exec reuse (CLI).
631
+ */
632
+ exportRefMap(): Record<string, number>;
633
+ /**
634
+ * Import a ref map previously captured from a snapshot.
635
+ */
636
+ importRefMap(refMap: Record<string, number>): void;
629
637
  /**
630
638
  * Execute a batch of steps
631
639
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-pilot",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Lightweight CDP-based browser automation for Node.js, Bun, and Cloudflare Workers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",