browser-pilot 0.0.4 → 0.0.5

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/actions.cjs CHANGED
@@ -110,7 +110,8 @@ var BatchExecutor = class {
110
110
  await this.page.fill(step.selector, step.value, {
111
111
  timeout,
112
112
  optional,
113
- clear: step.clear ?? true
113
+ clear: step.clear ?? true,
114
+ blur: step.blur
114
115
  });
115
116
  return { selectorUsed: this.getUsedSelector(step.selector) };
116
117
  }
@@ -1,5 +1,5 @@
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';
1
+ import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-DQhA8uQZ.cjs';
2
+ export { A as ActionType, c as StepResult } from './types-DQhA8uQZ.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-BJv2dzu0.js';
2
- export { A as ActionType, c as StepResult } from './types-BJv2dzu0.js';
1
+ import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-DKz34hii.js';
2
+ export { A as ActionType, c as StepResult } from './types-DKz34hii.js';
3
3
  import './client-7Nqka5MV.js';
4
4
 
5
5
  /**
package/dist/actions.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BatchExecutor,
3
3
  addBatchToPage
4
- } from "./chunk-YEHK2XY3.mjs";
4
+ } from "./chunk-6RB3GKQP.mjs";
5
5
  export {
6
6
  BatchExecutor,
7
7
  addBatchToPage
package/dist/browser.cjs CHANGED
@@ -542,7 +542,8 @@ var BatchExecutor = class {
542
542
  await this.page.fill(step.selector, step.value, {
543
543
  timeout,
544
544
  optional,
545
- clear: step.clear ?? true
545
+ clear: step.clear ?? true,
546
+ blur: step.blur
546
547
  });
547
548
  return { selectorUsed: this.getUsedSelector(step.selector) };
548
549
  }
@@ -1283,7 +1284,7 @@ var Page = class {
1283
1284
  * Fill an input field (clears first by default)
1284
1285
  */
1285
1286
  async fill(selector, value, options = {}) {
1286
- const { clear = true } = options;
1287
+ const { clear = true, blur = false } = options;
1287
1288
  return this.withStaleNodeRetry(async () => {
1288
1289
  const element = await this.findElement(selector, options);
1289
1290
  if (!element) {
@@ -1297,7 +1298,11 @@ var Page = class {
1297
1298
  const el = document.querySelector(${JSON.stringify(element.selector)});
1298
1299
  if (el) {
1299
1300
  el.value = '';
1300
- el.dispatchEvent(new Event('input', { bubbles: true }));
1301
+ el.dispatchEvent(new InputEvent('input', {
1302
+ bubbles: true,
1303
+ cancelable: true,
1304
+ inputType: 'deleteContent'
1305
+ }));
1301
1306
  }
1302
1307
  })()`
1303
1308
  );
@@ -1307,11 +1312,21 @@ var Page = class {
1307
1312
  `(() => {
1308
1313
  const el = document.querySelector(${JSON.stringify(element.selector)});
1309
1314
  if (el) {
1310
- el.dispatchEvent(new Event('input', { bubbles: true }));
1315
+ el.dispatchEvent(new InputEvent('input', {
1316
+ bubbles: true,
1317
+ cancelable: true,
1318
+ inputType: 'insertText',
1319
+ data: ${JSON.stringify(value)}
1320
+ }));
1311
1321
  el.dispatchEvent(new Event('change', { bubbles: true }));
1312
1322
  }
1313
1323
  })()`
1314
1324
  );
1325
+ if (blur) {
1326
+ await this.evaluateInFrame(
1327
+ `document.querySelector(${JSON.stringify(element.selector)})?.blur()`
1328
+ );
1329
+ }
1315
1330
  return true;
1316
1331
  });
1317
1332
  }
@@ -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-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';
3
+ import { P as Page } from './types-DQhA8uQZ.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-DQhA8uQZ.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-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';
3
+ import { P as Page } from './types-DKz34hii.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-DKz34hii.js';
5
5
 
6
6
  /**
7
7
  * Browser class - manages CDP connection and pages
package/dist/browser.mjs CHANGED
@@ -5,10 +5,10 @@ import {
5
5
  Page,
6
6
  TimeoutError,
7
7
  connect
8
- } from "./chunk-CWSTSVWO.mjs";
8
+ } from "./chunk-NP56KSAN.mjs";
9
9
  import "./chunk-BCOZUKWS.mjs";
10
10
  import "./chunk-R3PS4PCM.mjs";
11
- import "./chunk-YEHK2XY3.mjs";
11
+ import "./chunk-6RB3GKQP.mjs";
12
12
  export {
13
13
  Browser,
14
14
  ElementNotFoundError,
@@ -83,7 +83,8 @@ var BatchExecutor = class {
83
83
  await this.page.fill(step.selector, step.value, {
84
84
  timeout,
85
85
  optional,
86
- clear: step.clear ?? true
86
+ clear: step.clear ?? true,
87
+ blur: step.blur
87
88
  });
88
89
  return { selectorUsed: this.getUsedSelector(step.selector) };
89
90
  }
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-R3PS4PCM.mjs";
7
7
  import {
8
8
  BatchExecutor
9
- } from "./chunk-YEHK2XY3.mjs";
9
+ } from "./chunk-6RB3GKQP.mjs";
10
10
 
11
11
  // src/network/interceptor.ts
12
12
  var RequestInterceptor = class {
@@ -621,7 +621,7 @@ var Page = class {
621
621
  * Fill an input field (clears first by default)
622
622
  */
623
623
  async fill(selector, value, options = {}) {
624
- const { clear = true } = options;
624
+ const { clear = true, blur = false } = options;
625
625
  return this.withStaleNodeRetry(async () => {
626
626
  const element = await this.findElement(selector, options);
627
627
  if (!element) {
@@ -635,7 +635,11 @@ var Page = class {
635
635
  const el = document.querySelector(${JSON.stringify(element.selector)});
636
636
  if (el) {
637
637
  el.value = '';
638
- el.dispatchEvent(new Event('input', { bubbles: true }));
638
+ el.dispatchEvent(new InputEvent('input', {
639
+ bubbles: true,
640
+ cancelable: true,
641
+ inputType: 'deleteContent'
642
+ }));
639
643
  }
640
644
  })()`
641
645
  );
@@ -645,11 +649,21 @@ var Page = class {
645
649
  `(() => {
646
650
  const el = document.querySelector(${JSON.stringify(element.selector)});
647
651
  if (el) {
648
- el.dispatchEvent(new Event('input', { bubbles: true }));
652
+ el.dispatchEvent(new InputEvent('input', {
653
+ bubbles: true,
654
+ cancelable: true,
655
+ inputType: 'insertText',
656
+ data: ${JSON.stringify(value)}
657
+ }));
649
658
  el.dispatchEvent(new Event('change', { bubbles: true }));
650
659
  }
651
660
  })()`
652
661
  );
662
+ if (blur) {
663
+ await this.evaluateInFrame(
664
+ `document.querySelector(${JSON.stringify(element.selector)})?.blur()`
665
+ );
666
+ }
653
667
  return true;
654
668
  });
655
669
  }
package/dist/cli.cjs CHANGED
@@ -214,6 +214,142 @@ async function actionsCommand() {
214
214
  console.log(ACTIONS_HELP);
215
215
  }
216
216
 
217
+ // src/cli/session.ts
218
+ var import_node_os = require("os");
219
+ var import_node_path = require("path");
220
+ var SESSION_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".browser-pilot", "sessions");
221
+ async function ensureSessionDir() {
222
+ const fs = await import("fs/promises");
223
+ await fs.mkdir(SESSION_DIR, { recursive: true });
224
+ }
225
+ async function saveSession(session) {
226
+ await ensureSessionDir();
227
+ const fs = await import("fs/promises");
228
+ const filePath = (0, import_node_path.join)(SESSION_DIR, `${session.id}.json`);
229
+ await fs.writeFile(filePath, JSON.stringify(session, null, 2));
230
+ }
231
+ async function loadSession(id) {
232
+ const fs = await import("fs/promises");
233
+ const filePath = (0, import_node_path.join)(SESSION_DIR, `${id}.json`);
234
+ try {
235
+ const content = await fs.readFile(filePath, "utf-8");
236
+ return JSON.parse(content);
237
+ } catch (error) {
238
+ if (error.code === "ENOENT") {
239
+ throw new Error(`Session not found: ${id}`);
240
+ }
241
+ throw error;
242
+ }
243
+ }
244
+ async function updateSession(id, updates) {
245
+ const session = await loadSession(id);
246
+ const mergedMetadata = updates.metadata !== void 0 ? { ...session.metadata ?? {}, ...updates.metadata ?? {} } : session.metadata;
247
+ const updated = {
248
+ ...session,
249
+ ...updates,
250
+ metadata: mergedMetadata,
251
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
252
+ };
253
+ await saveSession(updated);
254
+ return updated;
255
+ }
256
+ async function deleteSession(id) {
257
+ const fs = await import("fs/promises");
258
+ const filePath = (0, import_node_path.join)(SESSION_DIR, `${id}.json`);
259
+ try {
260
+ await fs.unlink(filePath);
261
+ } catch (error) {
262
+ if (error.code !== "ENOENT") {
263
+ throw error;
264
+ }
265
+ }
266
+ }
267
+ async function listSessions() {
268
+ await ensureSessionDir();
269
+ const fs = await import("fs/promises");
270
+ try {
271
+ const files = await fs.readdir(SESSION_DIR);
272
+ const sessions = [];
273
+ for (const file of files) {
274
+ if (file.endsWith(".json")) {
275
+ try {
276
+ const content = await fs.readFile((0, import_node_path.join)(SESSION_DIR, file), "utf-8");
277
+ sessions.push(JSON.parse(content));
278
+ } catch {
279
+ }
280
+ }
281
+ }
282
+ return sessions.sort(
283
+ (a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()
284
+ );
285
+ } catch {
286
+ return [];
287
+ }
288
+ }
289
+ function generateSessionId() {
290
+ const timestamp = Date.now().toString(36);
291
+ const random = Math.random().toString(36).slice(2, 8);
292
+ return `${timestamp}-${random}`;
293
+ }
294
+ async function getDefaultSession() {
295
+ const sessions = await listSessions();
296
+ return sessions[0] ?? null;
297
+ }
298
+
299
+ // src/cli/commands/clean.ts
300
+ function parseCleanArgs(args) {
301
+ const options = {};
302
+ for (let i = 0; i < args.length; i++) {
303
+ const arg = args[i];
304
+ if (arg === "--max-age") {
305
+ const value = args[++i];
306
+ options.maxAge = parseInt(value ?? "24", 10);
307
+ } else if (arg === "--dry-run") {
308
+ options.dryRun = true;
309
+ } else if (arg === "--all") {
310
+ options.all = true;
311
+ }
312
+ }
313
+ return options;
314
+ }
315
+ async function cleanCommand(args, globalOptions) {
316
+ const options = parseCleanArgs(args);
317
+ const maxAgeMs = (options.maxAge ?? 24) * 60 * 60 * 1e3;
318
+ const now = Date.now();
319
+ const sessions = await listSessions();
320
+ const stale = sessions.filter((s) => {
321
+ if (options.all) return true;
322
+ const age = now - new Date(s.lastActivity).getTime();
323
+ return age > maxAgeMs;
324
+ });
325
+ if (stale.length === 0) {
326
+ output({ message: "No stale sessions found", cleaned: 0 }, globalOptions.output);
327
+ return;
328
+ }
329
+ if (options.dryRun) {
330
+ output(
331
+ {
332
+ message: `Would clean ${stale.length} session(s)`,
333
+ sessions: stale.map((s) => s.id),
334
+ dryRun: true
335
+ },
336
+ globalOptions.output
337
+ );
338
+ return;
339
+ }
340
+ for (const session of stale) {
341
+ await deleteSession(session.id);
342
+ }
343
+ output(
344
+ {
345
+ message: `Cleaned ${stale.length} session(s)`,
346
+ cleaned: stale.length,
347
+ sessions: stale.map((s) => s.id)
348
+ },
349
+ globalOptions.output
350
+ );
351
+ }
352
+
217
353
  // src/actions/executor.ts
218
354
  var DEFAULT_TIMEOUT = 3e4;
219
355
  var BatchExecutor = class {
@@ -299,7 +435,8 @@ var BatchExecutor = class {
299
435
  await this.page.fill(step.selector, step.value, {
300
436
  timeout,
301
437
  optional,
302
- clear: step.clear ?? true
438
+ clear: step.clear ?? true,
439
+ blur: step.blur
303
440
  });
304
441
  return { selectorUsed: this.getUsedSelector(step.selector) };
305
442
  }
@@ -1477,7 +1614,7 @@ var Page = class {
1477
1614
  * Fill an input field (clears first by default)
1478
1615
  */
1479
1616
  async fill(selector, value, options = {}) {
1480
- const { clear = true } = options;
1617
+ const { clear = true, blur = false } = options;
1481
1618
  return this.withStaleNodeRetry(async () => {
1482
1619
  const element = await this.findElement(selector, options);
1483
1620
  if (!element) {
@@ -1491,7 +1628,11 @@ var Page = class {
1491
1628
  const el = document.querySelector(${JSON.stringify(element.selector)});
1492
1629
  if (el) {
1493
1630
  el.value = '';
1494
- el.dispatchEvent(new Event('input', { bubbles: true }));
1631
+ el.dispatchEvent(new InputEvent('input', {
1632
+ bubbles: true,
1633
+ cancelable: true,
1634
+ inputType: 'deleteContent'
1635
+ }));
1495
1636
  }
1496
1637
  })()`
1497
1638
  );
@@ -1501,11 +1642,21 @@ var Page = class {
1501
1642
  `(() => {
1502
1643
  const el = document.querySelector(${JSON.stringify(element.selector)});
1503
1644
  if (el) {
1504
- el.dispatchEvent(new Event('input', { bubbles: true }));
1645
+ el.dispatchEvent(new InputEvent('input', {
1646
+ bubbles: true,
1647
+ cancelable: true,
1648
+ inputType: 'insertText',
1649
+ data: ${JSON.stringify(value)}
1650
+ }));
1505
1651
  el.dispatchEvent(new Event('change', { bubbles: true }));
1506
1652
  }
1507
1653
  })()`
1508
1654
  );
1655
+ if (blur) {
1656
+ await this.evaluateInFrame(
1657
+ `document.querySelector(${JSON.stringify(element.selector)})?.blur()`
1658
+ );
1659
+ }
1509
1660
  return true;
1510
1661
  });
1511
1662
  }
@@ -2974,88 +3125,6 @@ function connect(options) {
2974
3125
  return Browser.connect(options);
2975
3126
  }
2976
3127
 
2977
- // src/cli/session.ts
2978
- var import_node_os = require("os");
2979
- var import_node_path = require("path");
2980
- var SESSION_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".browser-pilot", "sessions");
2981
- async function ensureSessionDir() {
2982
- const fs = await import("fs/promises");
2983
- await fs.mkdir(SESSION_DIR, { recursive: true });
2984
- }
2985
- async function saveSession(session) {
2986
- await ensureSessionDir();
2987
- const fs = await import("fs/promises");
2988
- const filePath = (0, import_node_path.join)(SESSION_DIR, `${session.id}.json`);
2989
- await fs.writeFile(filePath, JSON.stringify(session, null, 2));
2990
- }
2991
- async function loadSession(id) {
2992
- const fs = await import("fs/promises");
2993
- const filePath = (0, import_node_path.join)(SESSION_DIR, `${id}.json`);
2994
- try {
2995
- const content = await fs.readFile(filePath, "utf-8");
2996
- return JSON.parse(content);
2997
- } catch (error) {
2998
- if (error.code === "ENOENT") {
2999
- throw new Error(`Session not found: ${id}`);
3000
- }
3001
- throw error;
3002
- }
3003
- }
3004
- async function updateSession(id, updates) {
3005
- const session = await loadSession(id);
3006
- const mergedMetadata = updates.metadata !== void 0 ? { ...session.metadata ?? {}, ...updates.metadata ?? {} } : session.metadata;
3007
- const updated = {
3008
- ...session,
3009
- ...updates,
3010
- metadata: mergedMetadata,
3011
- lastActivity: (/* @__PURE__ */ new Date()).toISOString()
3012
- };
3013
- await saveSession(updated);
3014
- return updated;
3015
- }
3016
- async function deleteSession(id) {
3017
- const fs = await import("fs/promises");
3018
- const filePath = (0, import_node_path.join)(SESSION_DIR, `${id}.json`);
3019
- try {
3020
- await fs.unlink(filePath);
3021
- } catch (error) {
3022
- if (error.code !== "ENOENT") {
3023
- throw error;
3024
- }
3025
- }
3026
- }
3027
- async function listSessions() {
3028
- await ensureSessionDir();
3029
- const fs = await import("fs/promises");
3030
- try {
3031
- const files = await fs.readdir(SESSION_DIR);
3032
- const sessions = [];
3033
- for (const file of files) {
3034
- if (file.endsWith(".json")) {
3035
- try {
3036
- const content = await fs.readFile((0, import_node_path.join)(SESSION_DIR, file), "utf-8");
3037
- sessions.push(JSON.parse(content));
3038
- } catch {
3039
- }
3040
- }
3041
- }
3042
- return sessions.sort(
3043
- (a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()
3044
- );
3045
- } catch {
3046
- return [];
3047
- }
3048
- }
3049
- function generateSessionId() {
3050
- const timestamp = Date.now().toString(36);
3051
- const random = Math.random().toString(36).slice(2, 8);
3052
- return `${timestamp}-${random}`;
3053
- }
3054
- async function getDefaultSession() {
3055
- const sessions = await listSessions();
3056
- return sessions[0] ?? null;
3057
- }
3058
-
3059
3128
  // src/cli/commands/close.ts
3060
3129
  async function closeCommand(args, globalOptions) {
3061
3130
  let session;
@@ -3174,6 +3243,17 @@ async function connectCommand(args, globalOptions) {
3174
3243
  }
3175
3244
 
3176
3245
  // src/cli/commands/exec.ts
3246
+ async function validateSession(session) {
3247
+ try {
3248
+ const wsUrl = new URL(session.wsUrl);
3249
+ const protocol = wsUrl.protocol === "wss:" ? "https:" : "http:";
3250
+ const httpUrl = `${protocol}//${wsUrl.host}/json/version`;
3251
+ const response = await fetch(httpUrl, { signal: AbortSignal.timeout(3e3) });
3252
+ return response.ok;
3253
+ } catch {
3254
+ return false;
3255
+ }
3256
+ }
3177
3257
  function parseExecArgs(args) {
3178
3258
  const options = {};
3179
3259
  let actionsJson;
@@ -3218,6 +3298,14 @@ Run 'bp actions' for complete action reference.`
3218
3298
  throw new Error('No session found. Run "bp connect" first.');
3219
3299
  }
3220
3300
  }
3301
+ const isValid = await validateSession(session);
3302
+ if (!isValid) {
3303
+ await deleteSession(session.id);
3304
+ throw new Error(
3305
+ `Session "${session.id}" is no longer valid (browser may have closed).
3306
+ Session file has been cleaned up. Run "bp connect" to create a new session.`
3307
+ );
3308
+ }
3221
3309
  const browser = await connect({
3222
3310
  provider: session.provider,
3223
3311
  wsUrl: session.wsUrl,
@@ -3556,6 +3644,7 @@ Commands:
3556
3644
  screenshot Take screenshot
3557
3645
  close Close session
3558
3646
  list List sessions
3647
+ clean Clean up old sessions
3559
3648
  actions Complete action reference
3560
3649
 
3561
3650
  Options:
@@ -3602,7 +3691,10 @@ function output(data, format = "pretty") {
3602
3691
  if (typeof data === "string") {
3603
3692
  console.log(data);
3604
3693
  } else if (typeof data === "object" && data !== null) {
3605
- prettyPrint(data);
3694
+ const { truncated } = prettyPrint(data);
3695
+ if (truncated) {
3696
+ console.log("\n(Output truncated. Use -o json for full data)");
3697
+ }
3606
3698
  } else {
3607
3699
  console.log(data);
3608
3700
  }
@@ -3610,16 +3702,20 @@ function output(data, format = "pretty") {
3610
3702
  }
3611
3703
  function prettyPrint(obj, indent = 0) {
3612
3704
  const prefix = " ".repeat(indent);
3705
+ let truncated = false;
3613
3706
  for (const [key, value] of Object.entries(obj)) {
3614
3707
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3615
3708
  console.log(`${prefix}${key}:`);
3616
- prettyPrint(value, indent + 1);
3709
+ const result = prettyPrint(value, indent + 1);
3710
+ if (result.truncated) truncated = true;
3617
3711
  } else if (Array.isArray(value)) {
3618
3712
  console.log(`${prefix}${key}: [${value.length} items]`);
3713
+ truncated = true;
3619
3714
  } else {
3620
3715
  console.log(`${prefix}${key}: ${value}`);
3621
3716
  }
3622
3717
  }
3718
+ return { truncated };
3623
3719
  }
3624
3720
  async function main() {
3625
3721
  const args = process.argv.slice(2);
@@ -3659,6 +3755,9 @@ async function main() {
3659
3755
  case "list":
3660
3756
  await listCommand(remaining, options);
3661
3757
  break;
3758
+ case "clean":
3759
+ await cleanCommand(remaining, options);
3760
+ break;
3662
3761
  case "actions":
3663
3762
  await actionsCommand();
3664
3763
  break;
package/dist/cli.d.cts CHANGED
@@ -15,6 +15,7 @@
15
15
  * screenshot Take screenshot
16
16
  * close Close session
17
17
  * list List sessions
18
+ * clean Clean up old sessions
18
19
  * actions Complete action reference
19
20
  *
20
21
  * Run 'bp quickstart' for getting started guide.
package/dist/cli.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * screenshot Take screenshot
16
16
  * close Close session
17
17
  * list List sessions
18
+ * clean Clean up old sessions
18
19
  * actions Complete action reference
19
20
  *
20
21
  * Run 'bp quickstart' for getting started guide.
package/dist/cli.mjs CHANGED
@@ -2,14 +2,14 @@
2
2
  import "./chunk-ZIQA4JOT.mjs";
3
3
  import {
4
4
  connect
5
- } from "./chunk-CWSTSVWO.mjs";
5
+ } from "./chunk-NP56KSAN.mjs";
6
6
  import "./chunk-BCOZUKWS.mjs";
7
7
  import {
8
8
  getBrowserWebSocketUrl
9
9
  } from "./chunk-R3PS4PCM.mjs";
10
10
  import {
11
11
  addBatchToPage
12
- } from "./chunk-YEHK2XY3.mjs";
12
+ } from "./chunk-6RB3GKQP.mjs";
13
13
 
14
14
  // src/cli/commands/actions.ts
15
15
  var ACTIONS_HELP = `
@@ -272,6 +272,60 @@ async function getDefaultSession() {
272
272
  return sessions[0] ?? null;
273
273
  }
274
274
 
275
+ // src/cli/commands/clean.ts
276
+ function parseCleanArgs(args) {
277
+ const options = {};
278
+ for (let i = 0; i < args.length; i++) {
279
+ const arg = args[i];
280
+ if (arg === "--max-age") {
281
+ const value = args[++i];
282
+ options.maxAge = parseInt(value ?? "24", 10);
283
+ } else if (arg === "--dry-run") {
284
+ options.dryRun = true;
285
+ } else if (arg === "--all") {
286
+ options.all = true;
287
+ }
288
+ }
289
+ return options;
290
+ }
291
+ async function cleanCommand(args, globalOptions) {
292
+ const options = parseCleanArgs(args);
293
+ const maxAgeMs = (options.maxAge ?? 24) * 60 * 60 * 1e3;
294
+ const now = Date.now();
295
+ const sessions = await listSessions();
296
+ const stale = sessions.filter((s) => {
297
+ if (options.all) return true;
298
+ const age = now - new Date(s.lastActivity).getTime();
299
+ return age > maxAgeMs;
300
+ });
301
+ if (stale.length === 0) {
302
+ output({ message: "No stale sessions found", cleaned: 0 }, globalOptions.output);
303
+ return;
304
+ }
305
+ if (options.dryRun) {
306
+ output(
307
+ {
308
+ message: `Would clean ${stale.length} session(s)`,
309
+ sessions: stale.map((s) => s.id),
310
+ dryRun: true
311
+ },
312
+ globalOptions.output
313
+ );
314
+ return;
315
+ }
316
+ for (const session of stale) {
317
+ await deleteSession(session.id);
318
+ }
319
+ output(
320
+ {
321
+ message: `Cleaned ${stale.length} session(s)`,
322
+ cleaned: stale.length,
323
+ sessions: stale.map((s) => s.id)
324
+ },
325
+ globalOptions.output
326
+ );
327
+ }
328
+
275
329
  // src/cli/commands/close.ts
276
330
  async function closeCommand(args, globalOptions) {
277
331
  let session;
@@ -390,6 +444,17 @@ async function connectCommand(args, globalOptions) {
390
444
  }
391
445
 
392
446
  // src/cli/commands/exec.ts
447
+ async function validateSession(session) {
448
+ try {
449
+ const wsUrl = new URL(session.wsUrl);
450
+ const protocol = wsUrl.protocol === "wss:" ? "https:" : "http:";
451
+ const httpUrl = `${protocol}//${wsUrl.host}/json/version`;
452
+ const response = await fetch(httpUrl, { signal: AbortSignal.timeout(3e3) });
453
+ return response.ok;
454
+ } catch {
455
+ return false;
456
+ }
457
+ }
393
458
  function parseExecArgs(args) {
394
459
  const options = {};
395
460
  let actionsJson;
@@ -434,6 +499,14 @@ Run 'bp actions' for complete action reference.`
434
499
  throw new Error('No session found. Run "bp connect" first.');
435
500
  }
436
501
  }
502
+ const isValid = await validateSession(session);
503
+ if (!isValid) {
504
+ await deleteSession(session.id);
505
+ throw new Error(
506
+ `Session "${session.id}" is no longer valid (browser may have closed).
507
+ Session file has been cleaned up. Run "bp connect" to create a new session.`
508
+ );
509
+ }
437
510
  const browser = await connect({
438
511
  provider: session.provider,
439
512
  wsUrl: session.wsUrl,
@@ -772,6 +845,7 @@ Commands:
772
845
  screenshot Take screenshot
773
846
  close Close session
774
847
  list List sessions
848
+ clean Clean up old sessions
775
849
  actions Complete action reference
776
850
 
777
851
  Options:
@@ -818,7 +892,10 @@ function output(data, format = "pretty") {
818
892
  if (typeof data === "string") {
819
893
  console.log(data);
820
894
  } else if (typeof data === "object" && data !== null) {
821
- prettyPrint(data);
895
+ const { truncated } = prettyPrint(data);
896
+ if (truncated) {
897
+ console.log("\n(Output truncated. Use -o json for full data)");
898
+ }
822
899
  } else {
823
900
  console.log(data);
824
901
  }
@@ -826,16 +903,20 @@ function output(data, format = "pretty") {
826
903
  }
827
904
  function prettyPrint(obj, indent = 0) {
828
905
  const prefix = " ".repeat(indent);
906
+ let truncated = false;
829
907
  for (const [key, value] of Object.entries(obj)) {
830
908
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
831
909
  console.log(`${prefix}${key}:`);
832
- prettyPrint(value, indent + 1);
910
+ const result = prettyPrint(value, indent + 1);
911
+ if (result.truncated) truncated = true;
833
912
  } else if (Array.isArray(value)) {
834
913
  console.log(`${prefix}${key}: [${value.length} items]`);
914
+ truncated = true;
835
915
  } else {
836
916
  console.log(`${prefix}${key}: ${value}`);
837
917
  }
838
918
  }
919
+ return { truncated };
839
920
  }
840
921
  async function main() {
841
922
  const args = process.argv.slice(2);
@@ -875,6 +956,9 @@ async function main() {
875
956
  case "list":
876
957
  await listCommand(remaining, options);
877
958
  break;
959
+ case "clean":
960
+ await cleanCommand(remaining, options);
961
+ break;
878
962
  case "actions":
879
963
  await actionsCommand();
880
964
  break;
package/dist/index.cjs CHANGED
@@ -134,7 +134,8 @@ var BatchExecutor = class {
134
134
  await this.page.fill(step.selector, step.value, {
135
135
  timeout,
136
136
  optional,
137
- clear: step.clear ?? true
137
+ clear: step.clear ?? true,
138
+ blur: step.blur
138
139
  });
139
140
  return { selectorUsed: this.getUsedSelector(step.selector) };
140
141
  }
@@ -1353,7 +1354,7 @@ var Page = class {
1353
1354
  * Fill an input field (clears first by default)
1354
1355
  */
1355
1356
  async fill(selector, value, options = {}) {
1356
- const { clear = true } = options;
1357
+ const { clear = true, blur = false } = options;
1357
1358
  return this.withStaleNodeRetry(async () => {
1358
1359
  const element = await this.findElement(selector, options);
1359
1360
  if (!element) {
@@ -1367,7 +1368,11 @@ var Page = class {
1367
1368
  const el = document.querySelector(${JSON.stringify(element.selector)});
1368
1369
  if (el) {
1369
1370
  el.value = '';
1370
- el.dispatchEvent(new Event('input', { bubbles: true }));
1371
+ el.dispatchEvent(new InputEvent('input', {
1372
+ bubbles: true,
1373
+ cancelable: true,
1374
+ inputType: 'deleteContent'
1375
+ }));
1371
1376
  }
1372
1377
  })()`
1373
1378
  );
@@ -1377,11 +1382,21 @@ var Page = class {
1377
1382
  `(() => {
1378
1383
  const el = document.querySelector(${JSON.stringify(element.selector)});
1379
1384
  if (el) {
1380
- el.dispatchEvent(new Event('input', { bubbles: true }));
1385
+ el.dispatchEvent(new InputEvent('input', {
1386
+ bubbles: true,
1387
+ cancelable: true,
1388
+ inputType: 'insertText',
1389
+ data: ${JSON.stringify(value)}
1390
+ }));
1381
1391
  el.dispatchEvent(new Event('change', { bubbles: true }));
1382
1392
  }
1383
1393
  })()`
1384
1394
  );
1395
+ if (blur) {
1396
+ await this.evaluateInFrame(
1397
+ `document.querySelector(${JSON.stringify(element.selector)})?.blur()`
1398
+ );
1399
+ }
1385
1400
  return true;
1386
1401
  });
1387
1402
  }
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-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';
2
+ import { R as RequestPattern, a as RequestHandler } from './types-DQhA8uQZ.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-DQhA8uQZ.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-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';
2
+ import { R as RequestPattern, a as RequestHandler } from './types-DKz34hii.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-DKz34hii.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-CWSTSVWO.mjs";
20
+ } from "./chunk-NP56KSAN.mjs";
21
21
  import {
22
22
  CDPError,
23
23
  createCDPClient
@@ -33,7 +33,7 @@ import {
33
33
  import {
34
34
  BatchExecutor,
35
35
  addBatchToPage
36
- } from "./chunk-YEHK2XY3.mjs";
36
+ } from "./chunk-6RB3GKQP.mjs";
37
37
  export {
38
38
  BatchExecutor,
39
39
  Browser,
@@ -68,6 +68,8 @@ interface ActionOptions {
68
68
  interface FillOptions extends ActionOptions {
69
69
  /** Clear existing content before filling */
70
70
  clear?: boolean;
71
+ /** Trigger blur after filling (useful for React/Vue frameworks that update on blur) */
72
+ blur?: boolean;
71
73
  }
72
74
  interface TypeOptions extends ActionOptions {
73
75
  /** Delay between keystrokes in ms */
@@ -866,6 +868,8 @@ interface Step {
866
868
  method?: 'enter' | 'click' | 'enter+click';
867
869
  /** Clear input before filling */
868
870
  clear?: boolean;
871
+ /** Trigger blur after filling (for React/Vue frameworks) */
872
+ blur?: boolean;
869
873
  /** Delay between keystrokes for type action */
870
874
  delay?: number;
871
875
  /** Wait for navigation after click action completes */
@@ -68,6 +68,8 @@ interface ActionOptions {
68
68
  interface FillOptions extends ActionOptions {
69
69
  /** Clear existing content before filling */
70
70
  clear?: boolean;
71
+ /** Trigger blur after filling (useful for React/Vue frameworks that update on blur) */
72
+ blur?: boolean;
71
73
  }
72
74
  interface TypeOptions extends ActionOptions {
73
75
  /** Delay between keystrokes in ms */
@@ -866,6 +868,8 @@ interface Step {
866
868
  method?: 'enter' | 'click' | 'enter+click';
867
869
  /** Clear input before filling */
868
870
  clear?: boolean;
871
+ /** Trigger blur after filling (for React/Vue frameworks) */
872
+ blur?: boolean;
869
873
  /** Delay between keystrokes for type action */
870
874
  delay?: number;
871
875
  /** Wait for navigation after click action completes */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-pilot",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
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",