browser-pilot 0.0.3 → 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 +2 -1
- package/dist/actions.d.cts +2 -2
- package/dist/actions.d.ts +2 -2
- package/dist/actions.mjs +1 -1
- package/dist/browser.cjs +19 -4
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-YEHK2XY3.mjs → chunk-6RB3GKQP.mjs} +2 -1
- package/dist/{chunk-CWSTSVWO.mjs → chunk-NP56KSAN.mjs} +18 -4
- package/dist/cli.cjs +267 -199
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +168 -115
- package/dist/index.cjs +19 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{types-BJv2dzu0.d.ts → types-DKz34hii.d.ts} +4 -0
- package/dist/{types-C6m0bT04.d.cts → types-DQhA8uQZ.d.cts} +4 -0
- package/package.json +1 -1
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
|
|
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
|
|
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;
|
|
@@ -3194,15 +3274,6 @@ function parseExecArgs(args) {
|
|
|
3194
3274
|
}
|
|
3195
3275
|
async function execCommand(args, globalOptions) {
|
|
3196
3276
|
const { actionsJson, options: execOptions } = parseExecArgs(args);
|
|
3197
|
-
let session;
|
|
3198
|
-
if (globalOptions.session) {
|
|
3199
|
-
session = await loadSession(globalOptions.session);
|
|
3200
|
-
} else {
|
|
3201
|
-
session = await getDefaultSession();
|
|
3202
|
-
if (!session) {
|
|
3203
|
-
throw new Error('No session found. Run "bp connect" first.');
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
3277
|
if (!actionsJson) {
|
|
3207
3278
|
throw new Error(
|
|
3208
3279
|
`No actions provided. Usage: bp exec '{"action":"goto","url":"..."}'
|
|
@@ -3218,6 +3289,23 @@ Run 'bp actions' for complete action reference.`
|
|
|
3218
3289
|
"Invalid JSON. Actions must be valid JSON.\n\nRun 'bp actions' for complete action reference."
|
|
3219
3290
|
);
|
|
3220
3291
|
}
|
|
3292
|
+
let session;
|
|
3293
|
+
if (globalOptions.session) {
|
|
3294
|
+
session = await loadSession(globalOptions.session);
|
|
3295
|
+
} else {
|
|
3296
|
+
session = await getDefaultSession();
|
|
3297
|
+
if (!session) {
|
|
3298
|
+
throw new Error('No session found. Run "bp connect" first.');
|
|
3299
|
+
}
|
|
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,
|
|
@@ -3317,67 +3405,62 @@ function getAge(date) {
|
|
|
3317
3405
|
|
|
3318
3406
|
// src/cli/commands/quickstart.ts
|
|
3319
3407
|
var QUICKSTART = `
|
|
3320
|
-
browser-pilot
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3408
|
+
browser-pilot CLI - Quick Start Guide
|
|
3409
|
+
|
|
3410
|
+
STEP 1: CONNECT TO A BROWSER
|
|
3411
|
+
bp connect --provider generic --name mysite
|
|
3412
|
+
|
|
3413
|
+
This creates a session. The CLI remembers it for subsequent commands.
|
|
3414
|
+
|
|
3415
|
+
STEP 2: NAVIGATE
|
|
3416
|
+
bp exec '{"action":"goto","url":"https://example.com"}'
|
|
3417
|
+
|
|
3418
|
+
STEP 3: GET PAGE SNAPSHOT
|
|
3419
|
+
bp snapshot --format text
|
|
3420
|
+
|
|
3421
|
+
Output shows the page as an accessibility tree with element refs:
|
|
3422
|
+
- heading "Welcome" [ref=e1]
|
|
3423
|
+
- button "Sign In" [ref=e2]
|
|
3424
|
+
- textbox "Email" [ref=e3]
|
|
3425
|
+
|
|
3426
|
+
STEP 4: INTERACT USING REFS
|
|
3427
|
+
bp exec '{"action":"fill","selector":"ref:e3","value":"test@example.com"}'
|
|
3428
|
+
bp exec '{"action":"click","selector":"ref:e2"}'
|
|
3429
|
+
|
|
3430
|
+
STEP 5: BATCH MULTIPLE ACTIONS
|
|
3431
|
+
bp exec '[
|
|
3432
|
+
{"action":"fill","selector":"ref:e3","value":"user@test.com"},
|
|
3433
|
+
{"action":"click","selector":"ref:e2"},
|
|
3434
|
+
{"action":"snapshot"}
|
|
3435
|
+
]'
|
|
3436
|
+
|
|
3437
|
+
FOR AI AGENTS
|
|
3438
|
+
Use -o json for machine-readable output:
|
|
3439
|
+
bp snapshot --format text -o json
|
|
3440
|
+
bp exec '{"action":"click","selector":"ref:e3"}' -o json
|
|
3441
|
+
|
|
3442
|
+
TIPS
|
|
3443
|
+
\u2022 Refs (e1, e2...) are stable within a page - prefer them over CSS selectors
|
|
3444
|
+
\u2022 After navigation, take a new snapshot to get updated refs
|
|
3445
|
+
\u2022 Use multi-selectors for resilience: ["ref:e3", "#email", "input[type=email]"]
|
|
3446
|
+
\u2022 Add "optional":true to skip elements that may not exist
|
|
3447
|
+
|
|
3448
|
+
SELECTOR PRIORITY
|
|
3449
|
+
1. ref:e5 From snapshot - most reliable
|
|
3450
|
+
2. #id CSS ID selector
|
|
3451
|
+
3. [data-testid] Test attributes
|
|
3452
|
+
4. .class CSS class (less stable)
|
|
3363
3453
|
|
|
3364
3454
|
COMMON ACTIONS
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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>
|
|
3455
|
+
goto {"action":"goto","url":"https://..."}
|
|
3456
|
+
click {"action":"click","selector":"ref:e3"}
|
|
3457
|
+
fill {"action":"fill","selector":"ref:e3","value":"text"}
|
|
3458
|
+
submit {"action":"submit","selector":"form"}
|
|
3459
|
+
select {"action":"select","selector":"ref:e5","value":"option"}
|
|
3460
|
+
snapshot {"action":"snapshot"}
|
|
3461
|
+
screenshot {"action":"screenshot"}
|
|
3462
|
+
|
|
3463
|
+
Run 'bp actions' for the complete action reference.
|
|
3381
3464
|
`;
|
|
3382
3465
|
async function quickstartCommand() {
|
|
3383
3466
|
console.log(QUICKSTART);
|
|
@@ -3553,56 +3636,31 @@ Usage:
|
|
|
3553
3636
|
bp <command> [options]
|
|
3554
3637
|
|
|
3555
3638
|
Commands:
|
|
3556
|
-
quickstart
|
|
3557
|
-
connect Create
|
|
3558
|
-
exec Execute actions
|
|
3559
|
-
snapshot Get page
|
|
3560
|
-
text Extract text content
|
|
3639
|
+
quickstart Getting started guide (start here!)
|
|
3640
|
+
connect Create browser session
|
|
3641
|
+
exec Execute actions
|
|
3642
|
+
snapshot Get page with element refs
|
|
3643
|
+
text Extract text content
|
|
3561
3644
|
screenshot Take screenshot
|
|
3562
3645
|
close Close session
|
|
3563
|
-
list List
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
Global Options:
|
|
3567
|
-
-s, --session <id> Session ID to use
|
|
3568
|
-
-o, --output <fmt> Output format: json | pretty (default: pretty)
|
|
3569
|
-
--trace Enable execution tracing
|
|
3570
|
-
-h, --help Show this help message
|
|
3646
|
+
list List sessions
|
|
3647
|
+
clean Clean up old sessions
|
|
3648
|
+
actions Complete action reference
|
|
3571
3649
|
|
|
3572
|
-
|
|
3573
|
-
--
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
2. Use refs (snapshot cached for same session+URL):
|
|
3579
|
-
bp exec '[{"action":"click","selector":"ref:e4"}]'
|
|
3580
|
-
|
|
3581
|
-
Refs are cached per session+URL after snapshot. Use new snapshot after navigation.
|
|
3582
|
-
Combine with CSS fallbacks:
|
|
3583
|
-
{"selector": ["ref:e4", "#submit", "button[type=submit]"]}
|
|
3650
|
+
Options:
|
|
3651
|
+
-s, --session <id> Session ID
|
|
3652
|
+
-o, --output <fmt> json | pretty (default: pretty)
|
|
3653
|
+
--trace Enable debug tracing
|
|
3654
|
+
--dialog <mode> Handle dialogs: accept | dismiss
|
|
3655
|
+
-h, --help Show help
|
|
3584
3656
|
|
|
3585
3657
|
Examples:
|
|
3586
|
-
# Connect to browser
|
|
3587
3658
|
bp connect --provider generic --name dev
|
|
3659
|
+
bp exec '{"action":"goto","url":"https://example.com"}'
|
|
3660
|
+
bp snapshot --format text
|
|
3661
|
+
bp exec '{"action":"click","selector":"ref:e3"}'
|
|
3588
3662
|
|
|
3589
|
-
|
|
3590
|
-
bp exec '[{"action":"goto","url":"https://example.com"},{"action":"snapshot"}]'
|
|
3591
|
-
|
|
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"}]'
|
|
3594
|
-
|
|
3595
|
-
# Handle native dialogs (alert/confirm/prompt)
|
|
3596
|
-
bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
|
|
3597
|
-
|
|
3598
|
-
# Batch multiple actions (snapshot optional if already cached)
|
|
3599
|
-
bp exec '[
|
|
3600
|
-
{"action":"snapshot"},
|
|
3601
|
-
{"action":"fill","selector":"ref:e5","value":"user@example.com"},
|
|
3602
|
-
{"action":"click","selector":"ref:e4"},
|
|
3603
|
-
{"action":"snapshot"}
|
|
3604
|
-
]'
|
|
3605
|
-
|
|
3663
|
+
Run 'bp quickstart' for CLI workflow guide.
|
|
3606
3664
|
Run 'bp actions' for complete action reference.
|
|
3607
3665
|
`;
|
|
3608
3666
|
function parseGlobalOptions(args) {
|
|
@@ -3633,7 +3691,10 @@ function output(data, format = "pretty") {
|
|
|
3633
3691
|
if (typeof data === "string") {
|
|
3634
3692
|
console.log(data);
|
|
3635
3693
|
} else if (typeof data === "object" && data !== null) {
|
|
3636
|
-
prettyPrint(data);
|
|
3694
|
+
const { truncated } = prettyPrint(data);
|
|
3695
|
+
if (truncated) {
|
|
3696
|
+
console.log("\n(Output truncated. Use -o json for full data)");
|
|
3697
|
+
}
|
|
3637
3698
|
} else {
|
|
3638
3699
|
console.log(data);
|
|
3639
3700
|
}
|
|
@@ -3641,16 +3702,20 @@ function output(data, format = "pretty") {
|
|
|
3641
3702
|
}
|
|
3642
3703
|
function prettyPrint(obj, indent = 0) {
|
|
3643
3704
|
const prefix = " ".repeat(indent);
|
|
3705
|
+
let truncated = false;
|
|
3644
3706
|
for (const [key, value] of Object.entries(obj)) {
|
|
3645
3707
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3646
3708
|
console.log(`${prefix}${key}:`);
|
|
3647
|
-
prettyPrint(value, indent + 1);
|
|
3709
|
+
const result = prettyPrint(value, indent + 1);
|
|
3710
|
+
if (result.truncated) truncated = true;
|
|
3648
3711
|
} else if (Array.isArray(value)) {
|
|
3649
3712
|
console.log(`${prefix}${key}: [${value.length} items]`);
|
|
3713
|
+
truncated = true;
|
|
3650
3714
|
} else {
|
|
3651
3715
|
console.log(`${prefix}${key}: ${value}`);
|
|
3652
3716
|
}
|
|
3653
3717
|
}
|
|
3718
|
+
return { truncated };
|
|
3654
3719
|
}
|
|
3655
3720
|
async function main() {
|
|
3656
3721
|
const args = process.argv.slice(2);
|
|
@@ -3690,6 +3755,9 @@ async function main() {
|
|
|
3690
3755
|
case "list":
|
|
3691
3756
|
await listCommand(remaining, options);
|
|
3692
3757
|
break;
|
|
3758
|
+
case "clean":
|
|
3759
|
+
await cleanCommand(remaining, options);
|
|
3760
|
+
break;
|
|
3693
3761
|
case "actions":
|
|
3694
3762
|
await actionsCommand();
|
|
3695
3763
|
break;
|
package/dist/cli.d.cts
CHANGED