browser-pilot 0.0.2 → 0.0.4

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);
@@ -3166,15 +3194,6 @@ function parseExecArgs(args) {
3166
3194
  }
3167
3195
  async function execCommand(args, globalOptions) {
3168
3196
  const { actionsJson, options: execOptions } = parseExecArgs(args);
3169
- let session;
3170
- if (globalOptions.session) {
3171
- session = await loadSession(globalOptions.session);
3172
- } else {
3173
- session = await getDefaultSession();
3174
- if (!session) {
3175
- throw new Error('No session found. Run "bp connect" first.');
3176
- }
3177
- }
3178
3197
  if (!actionsJson) {
3179
3198
  throw new Error(
3180
3199
  `No actions provided. Usage: bp exec '{"action":"goto","url":"..."}'
@@ -3190,6 +3209,15 @@ Run 'bp actions' for complete action reference.`
3190
3209
  "Invalid JSON. Actions must be valid JSON.\n\nRun 'bp actions' for complete action reference."
3191
3210
  );
3192
3211
  }
3212
+ let session;
3213
+ if (globalOptions.session) {
3214
+ session = await loadSession(globalOptions.session);
3215
+ } else {
3216
+ session = await getDefaultSession();
3217
+ if (!session) {
3218
+ throw new Error('No session found. Run "bp connect" first.');
3219
+ }
3220
+ }
3193
3221
  const browser = await connect({
3194
3222
  provider: session.provider,
3195
3223
  wsUrl: session.wsUrl,
@@ -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,69 @@ 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 CLI - Quick Start Guide
3321
+
3322
+ STEP 1: CONNECT TO A BROWSER
3323
+ bp connect --provider generic --name mysite
3324
+
3325
+ This creates a session. The CLI remembers it for subsequent commands.
3326
+
3327
+ STEP 2: NAVIGATE
3328
+ bp exec '{"action":"goto","url":"https://example.com"}'
3329
+
3330
+ STEP 3: GET PAGE SNAPSHOT
3331
+ bp snapshot --format text
3332
+
3333
+ Output shows the page as an accessibility tree with element refs:
3334
+ - heading "Welcome" [ref=e1]
3335
+ - button "Sign In" [ref=e2]
3336
+ - textbox "Email" [ref=e3]
3337
+
3338
+ STEP 4: INTERACT USING REFS
3339
+ bp exec '{"action":"fill","selector":"ref:e3","value":"test@example.com"}'
3340
+ bp exec '{"action":"click","selector":"ref:e2"}'
3341
+
3342
+ STEP 5: BATCH MULTIPLE ACTIONS
3343
+ bp exec '[
3344
+ {"action":"fill","selector":"ref:e3","value":"user@test.com"},
3345
+ {"action":"click","selector":"ref:e2"},
3346
+ {"action":"snapshot"}
3347
+ ]'
3348
+
3349
+ FOR AI AGENTS
3350
+ Use -o json for machine-readable output:
3351
+ bp snapshot --format text -o json
3352
+ bp exec '{"action":"click","selector":"ref:e3"}' -o json
3353
+
3354
+ TIPS
3355
+ \u2022 Refs (e1, e2...) are stable within a page - prefer them over CSS selectors
3356
+ \u2022 After navigation, take a new snapshot to get updated refs
3357
+ \u2022 Use multi-selectors for resilience: ["ref:e3", "#email", "input[type=email]"]
3358
+ \u2022 Add "optional":true to skip elements that may not exist
3359
+
3360
+ SELECTOR PRIORITY
3361
+ 1. ref:e5 From snapshot - most reliable
3362
+ 2. #id CSS ID selector
3363
+ 3. [data-testid] Test attributes
3364
+ 4. .class CSS class (less stable)
3365
+
3366
+ COMMON ACTIONS
3367
+ goto {"action":"goto","url":"https://..."}
3368
+ click {"action":"click","selector":"ref:e3"}
3369
+ fill {"action":"fill","selector":"ref:e3","value":"text"}
3370
+ submit {"action":"submit","selector":"form"}
3371
+ select {"action":"select","selector":"ref:e5","value":"option"}
3372
+ snapshot {"action":"snapshot"}
3373
+ screenshot {"action":"screenshot"}
3374
+
3375
+ Run 'bp actions' for the complete action reference.
3376
+ `;
3377
+ async function quickstartCommand() {
3378
+ console.log(QUICKSTART);
3379
+ }
3380
+
3271
3381
  // src/cli/commands/screenshot.ts
3272
3382
  function parseScreenshotArgs(args) {
3273
3383
  const options = {};
@@ -3362,7 +3472,16 @@ async function snapshotCommand(args, globalOptions) {
3362
3472
  try {
3363
3473
  const page = await browser.page();
3364
3474
  const snapshot = await page.snapshot();
3365
- await updateSession(session.id, { currentUrl: snapshot.url });
3475
+ await updateSession(session.id, {
3476
+ currentUrl: snapshot.url,
3477
+ metadata: {
3478
+ refCache: {
3479
+ url: snapshot.url,
3480
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3481
+ refMap: page.exportRefMap()
3482
+ }
3483
+ }
3484
+ });
3366
3485
  switch (options.format) {
3367
3486
  case "interactive":
3368
3487
  output(snapshot.interactiveElements, globalOptions.output);
@@ -3429,54 +3548,30 @@ Usage:
3429
3548
  bp <command> [options]
3430
3549
 
3431
3550
  Commands:
3432
- connect Create or resume browser session
3433
- exec Execute actions on current session
3434
- snapshot Get page accessibility snapshot (includes element refs)
3435
- text Extract text content from page
3551
+ quickstart Getting started guide (start here!)
3552
+ connect Create browser session
3553
+ exec Execute actions
3554
+ snapshot Get page with element refs
3555
+ text Extract text content
3436
3556
  screenshot Take screenshot
3437
3557
  close Close session
3438
- list List all sessions
3439
- actions Show all available actions with examples
3558
+ list List sessions
3559
+ actions Complete action reference
3440
3560
 
3441
- Global Options:
3442
- -s, --session <id> Session ID to use
3443
- -o, --output <fmt> Output format: json | pretty (default: pretty)
3444
- --trace Enable execution tracing
3445
- -h, --help Show this help message
3446
-
3447
- Exec Options:
3448
- --dialog <mode> Auto-handle dialogs: accept | dismiss
3449
-
3450
- 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"}'
3454
-
3455
- Refs are stable until navigation. Combine with CSS fallbacks:
3456
- {"selector": ["ref:e4", "#submit", "button[type=submit]"]}
3561
+ Options:
3562
+ -s, --session <id> Session ID
3563
+ -o, --output <fmt> json | pretty (default: pretty)
3564
+ --trace Enable debug tracing
3565
+ --dialog <mode> Handle dialogs: accept | dismiss
3566
+ -h, --help Show help
3457
3567
 
3458
3568
  Examples:
3459
- # Connect to browser
3460
3569
  bp connect --provider generic --name dev
3461
-
3462
- # Navigate and get snapshot with refs
3463
3570
  bp exec '{"action":"goto","url":"https://example.com"}'
3464
3571
  bp snapshot --format text
3572
+ bp exec '{"action":"click","selector":"ref:e3"}'
3465
3573
 
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"}'
3469
-
3470
- # Handle native dialogs (alert/confirm/prompt)
3471
- bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
3472
-
3473
- # Batch multiple actions
3474
- bp exec '[
3475
- {"action":"fill","selector":"ref:e5","value":"user@example.com"},
3476
- {"action":"click","selector":"ref:e4"},
3477
- {"action":"snapshot"}
3478
- ]'
3479
-
3574
+ Run 'bp quickstart' for CLI workflow guide.
3480
3575
  Run 'bp actions' for complete action reference.
3481
3576
  `;
3482
3577
  function parseGlobalOptions(args) {
@@ -3540,6 +3635,9 @@ async function main() {
3540
3635
  }
3541
3636
  try {
3542
3637
  switch (command) {
3638
+ case "quickstart":
3639
+ await quickstartCommand();
3640
+ break;
3543
3641
  case "connect":
3544
3642
  await connectCommand(remaining, options);
3545
3643
  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);
@@ -407,15 +410,6 @@ function parseExecArgs(args) {
407
410
  }
408
411
  async function execCommand(args, globalOptions) {
409
412
  const { actionsJson, options: execOptions } = parseExecArgs(args);
410
- let session;
411
- if (globalOptions.session) {
412
- session = await loadSession(globalOptions.session);
413
- } else {
414
- session = await getDefaultSession();
415
- if (!session) {
416
- throw new Error('No session found. Run "bp connect" first.');
417
- }
418
- }
419
413
  if (!actionsJson) {
420
414
  throw new Error(
421
415
  `No actions provided. Usage: bp exec '{"action":"goto","url":"..."}'
@@ -431,6 +425,15 @@ Run 'bp actions' for complete action reference.`
431
425
  "Invalid JSON. Actions must be valid JSON.\n\nRun 'bp actions' for complete action reference."
432
426
  );
433
427
  }
428
+ let session;
429
+ if (globalOptions.session) {
430
+ session = await loadSession(globalOptions.session);
431
+ } else {
432
+ session = await getDefaultSession();
433
+ if (!session) {
434
+ throw new Error('No session found. Run "bp connect" first.');
435
+ }
436
+ }
434
437
  const browser = await connect({
435
438
  provider: session.provider,
436
439
  wsUrl: session.wsUrl,
@@ -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,69 @@ 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 CLI - Quick Start Guide
537
+
538
+ STEP 1: CONNECT TO A BROWSER
539
+ bp connect --provider generic --name mysite
540
+
541
+ This creates a session. The CLI remembers it for subsequent commands.
542
+
543
+ STEP 2: NAVIGATE
544
+ bp exec '{"action":"goto","url":"https://example.com"}'
545
+
546
+ STEP 3: GET PAGE SNAPSHOT
547
+ bp snapshot --format text
548
+
549
+ Output shows the page as an accessibility tree with element refs:
550
+ - heading "Welcome" [ref=e1]
551
+ - button "Sign In" [ref=e2]
552
+ - textbox "Email" [ref=e3]
553
+
554
+ STEP 4: INTERACT USING REFS
555
+ bp exec '{"action":"fill","selector":"ref:e3","value":"test@example.com"}'
556
+ bp exec '{"action":"click","selector":"ref:e2"}'
557
+
558
+ STEP 5: BATCH MULTIPLE ACTIONS
559
+ bp exec '[
560
+ {"action":"fill","selector":"ref:e3","value":"user@test.com"},
561
+ {"action":"click","selector":"ref:e2"},
562
+ {"action":"snapshot"}
563
+ ]'
564
+
565
+ FOR AI AGENTS
566
+ Use -o json for machine-readable output:
567
+ bp snapshot --format text -o json
568
+ bp exec '{"action":"click","selector":"ref:e3"}' -o json
569
+
570
+ TIPS
571
+ \u2022 Refs (e1, e2...) are stable within a page - prefer them over CSS selectors
572
+ \u2022 After navigation, take a new snapshot to get updated refs
573
+ \u2022 Use multi-selectors for resilience: ["ref:e3", "#email", "input[type=email]"]
574
+ \u2022 Add "optional":true to skip elements that may not exist
575
+
576
+ SELECTOR PRIORITY
577
+ 1. ref:e5 From snapshot - most reliable
578
+ 2. #id CSS ID selector
579
+ 3. [data-testid] Test attributes
580
+ 4. .class CSS class (less stable)
581
+
582
+ COMMON ACTIONS
583
+ goto {"action":"goto","url":"https://..."}
584
+ click {"action":"click","selector":"ref:e3"}
585
+ fill {"action":"fill","selector":"ref:e3","value":"text"}
586
+ submit {"action":"submit","selector":"form"}
587
+ select {"action":"select","selector":"ref:e5","value":"option"}
588
+ snapshot {"action":"snapshot"}
589
+ screenshot {"action":"screenshot"}
590
+
591
+ Run 'bp actions' for the complete action reference.
592
+ `;
593
+ async function quickstartCommand() {
594
+ console.log(QUICKSTART);
595
+ }
596
+
512
597
  // src/cli/commands/screenshot.ts
513
598
  function parseScreenshotArgs(args) {
514
599
  const options = {};
@@ -603,7 +688,16 @@ async function snapshotCommand(args, globalOptions) {
603
688
  try {
604
689
  const page = await browser.page();
605
690
  const snapshot = await page.snapshot();
606
- await updateSession(session.id, { currentUrl: snapshot.url });
691
+ await updateSession(session.id, {
692
+ currentUrl: snapshot.url,
693
+ metadata: {
694
+ refCache: {
695
+ url: snapshot.url,
696
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
697
+ refMap: page.exportRefMap()
698
+ }
699
+ }
700
+ });
607
701
  switch (options.format) {
608
702
  case "interactive":
609
703
  output(snapshot.interactiveElements, globalOptions.output);
@@ -670,54 +764,30 @@ Usage:
670
764
  bp <command> [options]
671
765
 
672
766
  Commands:
673
- connect Create or resume browser session
674
- exec Execute actions on current session
675
- snapshot Get page accessibility snapshot (includes element refs)
676
- text Extract text content from page
767
+ quickstart Getting started guide (start here!)
768
+ connect Create browser session
769
+ exec Execute actions
770
+ snapshot Get page with element refs
771
+ text Extract text content
677
772
  screenshot Take screenshot
678
773
  close Close session
679
- list List all sessions
680
- actions Show all available actions with examples
681
-
682
- Global Options:
683
- -s, --session <id> Session ID to use
684
- -o, --output <fmt> Output format: json | pretty (default: pretty)
685
- --trace Enable execution tracing
686
- -h, --help Show this help message
687
-
688
- Exec Options:
689
- --dialog <mode> Auto-handle dialogs: accept | dismiss
690
-
691
- 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"}'
774
+ list List sessions
775
+ actions Complete action reference
695
776
 
696
- Refs are stable until navigation. Combine with CSS fallbacks:
697
- {"selector": ["ref:e4", "#submit", "button[type=submit]"]}
777
+ Options:
778
+ -s, --session <id> Session ID
779
+ -o, --output <fmt> json | pretty (default: pretty)
780
+ --trace Enable debug tracing
781
+ --dialog <mode> Handle dialogs: accept | dismiss
782
+ -h, --help Show help
698
783
 
699
784
  Examples:
700
- # Connect to browser
701
785
  bp connect --provider generic --name dev
702
-
703
- # Navigate and get snapshot with refs
704
786
  bp exec '{"action":"goto","url":"https://example.com"}'
705
787
  bp snapshot --format text
788
+ bp exec '{"action":"click","selector":"ref:e3"}'
706
789
 
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"}'
710
-
711
- # Handle native dialogs (alert/confirm/prompt)
712
- bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
713
-
714
- # Batch multiple actions
715
- bp exec '[
716
- {"action":"fill","selector":"ref:e5","value":"user@example.com"},
717
- {"action":"click","selector":"ref:e4"},
718
- {"action":"snapshot"}
719
- ]'
720
-
790
+ Run 'bp quickstart' for CLI workflow guide.
721
791
  Run 'bp actions' for complete action reference.
722
792
  `;
723
793
  function parseGlobalOptions(args) {
@@ -781,6 +851,9 @@ async function main() {
781
851
  }
782
852
  try {
783
853
  switch (command) {
854
+ case "quickstart":
855
+ await quickstartCommand();
856
+ break;
784
857
  case "connect":
785
858
  await connectCommand(remaining, options);
786
859
  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.2",
3
+ "version": "0.0.4",
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",