browser-pilot 0.0.8 → 0.0.9
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 +61 -1
- package/dist/actions.cjs +465 -6
- package/dist/actions.d.cts +22 -3
- package/dist/actions.d.ts +22 -3
- package/dist/actions.mjs +5 -3
- package/dist/browser.cjs +1350 -14
- package/dist/browser.d.cts +3 -3
- package/dist/browser.d.ts +3 -3
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-JN44FHTK.mjs → chunk-7OSR2CAE.mjs} +1429 -14
- package/dist/chunk-KKW2SZLV.mjs +741 -0
- package/dist/cli.mjs +6150 -103
- package/dist/index.cjs +2026 -23
- package/dist/index.d.cts +142 -6
- package/dist/index.d.ts +142 -6
- package/dist/index.mjs +357 -10
- package/dist/providers.d.cts +2 -2
- package/dist/providers.d.ts +2 -2
- package/dist/{types-D_uDqh0Z.d.cts → types--wXNHUwt.d.cts} +1 -1
- package/dist/{types-D_uDqh0Z.d.ts → types--wXNHUwt.d.ts} +1 -1
- package/dist/{types-DklIxnbO.d.cts → types-CYw-7vx1.d.cts} +244 -1
- package/dist/{types-Pv8KzZ6l.d.ts → types-DOGsEYQa.d.ts} +244 -1
- package/package.json +3 -3
- package/dist/chunk-ZIQA4JOT.mjs +0 -226
- package/dist/chunk-ZTQ37YQT.mjs +0 -283
- package/dist/cli.cjs +0 -6377
- package/dist/cli.d.cts +0 -25
- package/dist/cli.d.ts +0 -25
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ await browser.close();
|
|
|
34
34
|
| Single-selector API (fragile) | Multi-selector by default: `['#primary', '.fallback']` |
|
|
35
35
|
| No action batching (high latency) | Batch DSL: one call for entire sequences |
|
|
36
36
|
| No AI-optimized snapshots | Built-in accessibility tree extraction |
|
|
37
|
+
| No audio I/O for voice agents | Mic injection + output capture + Whisper transcription |
|
|
37
38
|
|
|
38
39
|
## Installation
|
|
39
40
|
|
|
@@ -246,6 +247,26 @@ await page.setLocale('fr-FR');
|
|
|
246
247
|
|
|
247
248
|
Devices: `iPhone 14`, `iPhone 14 Pro Max`, `Pixel 7`, `iPad Pro 11`, `Desktop Chrome`, `Desktop Firefox`
|
|
248
249
|
|
|
250
|
+
### Audio I/O
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Set up audio input/output interception
|
|
254
|
+
await page.setupAudio();
|
|
255
|
+
|
|
256
|
+
// Play audio into the page's fake microphone
|
|
257
|
+
await page.audioInput.play(wavBytes, { waitForEnd: true });
|
|
258
|
+
|
|
259
|
+
// Capture audio output until silence
|
|
260
|
+
const capture = await page.audioOutput.captureUntilSilence({ silenceTimeout: 5000 });
|
|
261
|
+
|
|
262
|
+
// Full round-trip: play input → capture response
|
|
263
|
+
const result = await page.audioRoundTrip({ input: wavBytes, silenceTimeout: 5000 });
|
|
264
|
+
|
|
265
|
+
// Transcribe captured audio (requires OPENAI_API_KEY)
|
|
266
|
+
import { transcribe } from 'browser-pilot';
|
|
267
|
+
const { text } = await transcribe(capture);
|
|
268
|
+
```
|
|
269
|
+
|
|
249
270
|
### Request Interception
|
|
250
271
|
|
|
251
272
|
```typescript
|
|
@@ -378,7 +399,7 @@ bp exec '[
|
|
|
378
399
|
{"action":"fill","selector":"ref:e5","value":"laptop"},
|
|
379
400
|
{"action":"click","selector":"ref:e12"},
|
|
380
401
|
{"action":"snapshot"}
|
|
381
|
-
]' --
|
|
402
|
+
]' --format json
|
|
382
403
|
```
|
|
383
404
|
|
|
384
405
|
Multi-selector fallbacks for robustness:
|
|
@@ -403,6 +424,44 @@ Output:
|
|
|
403
424
|
|
|
404
425
|
Run `bp actions` for complete action reference.
|
|
405
426
|
|
|
427
|
+
### Voice Agent Testing
|
|
428
|
+
|
|
429
|
+
Test audio-based AI apps (voice assistants, phone agents) by injecting microphone input and capturing spoken responses.
|
|
430
|
+
|
|
431
|
+
> **Full guide:** [Voice Agent Testing Guide](./docs/guides/voice-agent-testing.md)
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
export OPENAI_API_KEY=sk-... # Required for --transcribe
|
|
435
|
+
|
|
436
|
+
# Validate audio pipeline
|
|
437
|
+
bp audio check -s my-session
|
|
438
|
+
# Output: "READY for roundtrip" with agent AudioContext detected
|
|
439
|
+
|
|
440
|
+
# Full round-trip: send audio prompt → wait for response → transcribe
|
|
441
|
+
bp audio roundtrip -i prompt.wav --transcribe --silence-timeout 1500
|
|
442
|
+
# Output: { "transcript": "Welcome! I'd be happy to help...", "latencyMs": 5200, ... }
|
|
443
|
+
|
|
444
|
+
# Save response audio for manual review
|
|
445
|
+
bp audio roundtrip -i prompt.wav -o response.wav --transcribe
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Important:** Audio overrides must be injected before the voice agent initializes. Use `bp audio check` to validate the pipeline. See the [full guide](./docs/guides/voice-agent-testing.md) for setup order and troubleshooting.
|
|
449
|
+
|
|
450
|
+
Programmatic API:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
await page.setupAudio();
|
|
454
|
+
|
|
455
|
+
const result = await page.audioRoundTrip({
|
|
456
|
+
input: audioBytes,
|
|
457
|
+
silenceTimeout: 1500,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
import { transcribe } from 'browser-pilot';
|
|
461
|
+
const { text } = await transcribe(result.audio);
|
|
462
|
+
console.log(text); // "Welcome! I'd be happy to help..."
|
|
463
|
+
```
|
|
464
|
+
|
|
406
465
|
### Recording Browser Actions
|
|
407
466
|
|
|
408
467
|
Record human interactions to create automation recipes:
|
|
@@ -573,6 +632,7 @@ See the [docs](./docs) folder for detailed documentation:
|
|
|
573
632
|
- [Multi-Selector Guide](./docs/guides/multi-selector.md)
|
|
574
633
|
- [Batch Actions](./docs/guides/batch-actions.md)
|
|
575
634
|
- [Snapshots](./docs/guides/snapshots.md)
|
|
635
|
+
- [Voice Agent Testing](./docs/guides/voice-agent-testing.md)
|
|
576
636
|
- [CLI Reference](./docs/cli.md)
|
|
577
637
|
- [API Reference](./docs/api/page.md)
|
|
578
638
|
|
package/dist/actions.cjs
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var actions_exports = {};
|
|
22
22
|
__export(actions_exports, {
|
|
23
23
|
BatchExecutor: () => BatchExecutor,
|
|
24
|
-
addBatchToPage: () => addBatchToPage
|
|
24
|
+
addBatchToPage: () => addBatchToPage,
|
|
25
|
+
validateSteps: () => validateSteps
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(actions_exports);
|
|
27
28
|
|
|
@@ -266,10 +267,32 @@ var BatchExecutor = class {
|
|
|
266
267
|
await this.page.switchToMain();
|
|
267
268
|
return {};
|
|
268
269
|
}
|
|
269
|
-
default:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
default: {
|
|
271
|
+
const action = step.action;
|
|
272
|
+
const aliases = {
|
|
273
|
+
execute: "evaluate",
|
|
274
|
+
navigate: "goto",
|
|
275
|
+
input: "fill",
|
|
276
|
+
tap: "click",
|
|
277
|
+
go: "goto",
|
|
278
|
+
run: "evaluate",
|
|
279
|
+
capture: "screenshot",
|
|
280
|
+
inspect: "snapshot",
|
|
281
|
+
enter: "press",
|
|
282
|
+
open: "goto",
|
|
283
|
+
visit: "goto",
|
|
284
|
+
eval: "evaluate",
|
|
285
|
+
js: "evaluate",
|
|
286
|
+
snap: "snapshot",
|
|
287
|
+
frame: "switchFrame"
|
|
288
|
+
};
|
|
289
|
+
const suggestion = aliases[action.toLowerCase()];
|
|
290
|
+
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
291
|
+
const valid = "goto, click, fill, type, select, check, uncheck, submit, press, focus, hover, scroll, wait, snapshot, screenshot, evaluate, text, switchFrame, switchToMain";
|
|
292
|
+
throw new Error(`Unknown action "${action}".${hint}
|
|
293
|
+
|
|
294
|
+
Valid actions: ${valid}`);
|
|
295
|
+
}
|
|
273
296
|
}
|
|
274
297
|
}
|
|
275
298
|
/**
|
|
@@ -288,8 +311,444 @@ function addBatchToPage(page) {
|
|
|
288
311
|
batch: (steps, options) => executor.execute(steps, options)
|
|
289
312
|
});
|
|
290
313
|
}
|
|
314
|
+
|
|
315
|
+
// src/actions/validate.ts
|
|
316
|
+
function levenshtein(a, b) {
|
|
317
|
+
const m = a.length;
|
|
318
|
+
const n = b.length;
|
|
319
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
320
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
321
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
322
|
+
for (let i = 1; i <= m; i++) {
|
|
323
|
+
for (let j = 1; j <= n; j++) {
|
|
324
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return dp[m][n];
|
|
328
|
+
}
|
|
329
|
+
var ACTION_ALIASES = {
|
|
330
|
+
execute: "evaluate",
|
|
331
|
+
navigate: "goto",
|
|
332
|
+
input: "fill",
|
|
333
|
+
tap: "click",
|
|
334
|
+
go: "goto",
|
|
335
|
+
run: "evaluate",
|
|
336
|
+
capture: "screenshot",
|
|
337
|
+
inspect: "snapshot",
|
|
338
|
+
enter: "press",
|
|
339
|
+
keypress: "press",
|
|
340
|
+
nav: "goto",
|
|
341
|
+
open: "goto",
|
|
342
|
+
visit: "goto",
|
|
343
|
+
browse: "goto",
|
|
344
|
+
load: "goto",
|
|
345
|
+
write: "fill",
|
|
346
|
+
set: "fill",
|
|
347
|
+
pick: "select",
|
|
348
|
+
choose: "select",
|
|
349
|
+
send: "press",
|
|
350
|
+
eval: "evaluate",
|
|
351
|
+
js: "evaluate",
|
|
352
|
+
script: "evaluate",
|
|
353
|
+
snap: "snapshot",
|
|
354
|
+
accessibility: "snapshot",
|
|
355
|
+
a11y: "snapshot",
|
|
356
|
+
image: "screenshot",
|
|
357
|
+
pic: "screenshot",
|
|
358
|
+
frame: "switchFrame",
|
|
359
|
+
iframe: "switchFrame"
|
|
360
|
+
};
|
|
361
|
+
var PROPERTY_ALIASES = {
|
|
362
|
+
expression: "value",
|
|
363
|
+
href: "url",
|
|
364
|
+
target: "selector",
|
|
365
|
+
element: "selector",
|
|
366
|
+
code: "value",
|
|
367
|
+
script: "value",
|
|
368
|
+
src: "url",
|
|
369
|
+
link: "url",
|
|
370
|
+
char: "key",
|
|
371
|
+
text: "value",
|
|
372
|
+
query: "selector",
|
|
373
|
+
el: "selector",
|
|
374
|
+
elem: "selector",
|
|
375
|
+
css: "selector",
|
|
376
|
+
xpath: "selector",
|
|
377
|
+
input: "value",
|
|
378
|
+
content: "value",
|
|
379
|
+
keys: "key",
|
|
380
|
+
button: "key",
|
|
381
|
+
address: "url",
|
|
382
|
+
page: "url",
|
|
383
|
+
path: "url"
|
|
384
|
+
};
|
|
385
|
+
var ACTION_RULES = {
|
|
386
|
+
goto: {
|
|
387
|
+
required: { url: { type: "string" } },
|
|
388
|
+
optional: {}
|
|
389
|
+
},
|
|
390
|
+
click: {
|
|
391
|
+
required: { selector: { type: "string|string[]" } },
|
|
392
|
+
optional: {
|
|
393
|
+
waitForNavigation: { type: "boolean" }
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
fill: {
|
|
397
|
+
required: { selector: { type: "string|string[]" }, value: { type: "string" } },
|
|
398
|
+
optional: {
|
|
399
|
+
clear: { type: "boolean" },
|
|
400
|
+
blur: { type: "boolean" }
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
type: {
|
|
404
|
+
required: { selector: { type: "string|string[]" }, value: { type: "string" } },
|
|
405
|
+
optional: {
|
|
406
|
+
delay: { type: "number" }
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
select: {
|
|
410
|
+
required: {},
|
|
411
|
+
optional: {
|
|
412
|
+
selector: { type: "string|string[]" },
|
|
413
|
+
value: { type: "string|string[]" },
|
|
414
|
+
trigger: { type: "string|string[]" },
|
|
415
|
+
option: { type: "string|string[]" },
|
|
416
|
+
match: { type: "string", enum: ["text", "value", "contains"] }
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
check: {
|
|
420
|
+
required: { selector: { type: "string|string[]" } },
|
|
421
|
+
optional: {}
|
|
422
|
+
},
|
|
423
|
+
uncheck: {
|
|
424
|
+
required: { selector: { type: "string|string[]" } },
|
|
425
|
+
optional: {}
|
|
426
|
+
},
|
|
427
|
+
submit: {
|
|
428
|
+
required: { selector: { type: "string|string[]" } },
|
|
429
|
+
optional: {
|
|
430
|
+
method: { type: "string", enum: ["enter", "click", "enter+click"] }
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
press: {
|
|
434
|
+
required: { key: { type: "string" } },
|
|
435
|
+
optional: {}
|
|
436
|
+
},
|
|
437
|
+
focus: {
|
|
438
|
+
required: { selector: { type: "string|string[]" } },
|
|
439
|
+
optional: {}
|
|
440
|
+
},
|
|
441
|
+
hover: {
|
|
442
|
+
required: { selector: { type: "string|string[]" } },
|
|
443
|
+
optional: {}
|
|
444
|
+
},
|
|
445
|
+
scroll: {
|
|
446
|
+
required: {},
|
|
447
|
+
optional: {
|
|
448
|
+
selector: { type: "string|string[]" },
|
|
449
|
+
x: { type: "number" },
|
|
450
|
+
y: { type: "number" },
|
|
451
|
+
direction: { type: "string", enum: ["up", "down", "left", "right"] },
|
|
452
|
+
amount: { type: "number" }
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
wait: {
|
|
456
|
+
required: {},
|
|
457
|
+
optional: {
|
|
458
|
+
selector: { type: "string|string[]" },
|
|
459
|
+
waitFor: {
|
|
460
|
+
type: "string",
|
|
461
|
+
enum: ["visible", "hidden", "attached", "detached", "navigation", "networkIdle"]
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
snapshot: {
|
|
466
|
+
required: {},
|
|
467
|
+
optional: {}
|
|
468
|
+
},
|
|
469
|
+
screenshot: {
|
|
470
|
+
required: {},
|
|
471
|
+
optional: {
|
|
472
|
+
format: { type: "string", enum: ["png", "jpeg", "webp"] },
|
|
473
|
+
quality: { type: "number" },
|
|
474
|
+
fullPage: { type: "boolean" }
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
evaluate: {
|
|
478
|
+
required: { value: { type: "string" } },
|
|
479
|
+
optional: {}
|
|
480
|
+
},
|
|
481
|
+
text: {
|
|
482
|
+
required: {},
|
|
483
|
+
optional: {
|
|
484
|
+
selector: { type: "string|string[]" }
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
switchFrame: {
|
|
488
|
+
required: { selector: { type: "string|string[]" } },
|
|
489
|
+
optional: {}
|
|
490
|
+
},
|
|
491
|
+
switchToMain: {
|
|
492
|
+
required: {},
|
|
493
|
+
optional: {}
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var VALID_ACTIONS = Object.keys(ACTION_RULES);
|
|
497
|
+
var VALID_ACTIONS_LIST = VALID_ACTIONS.join(", ");
|
|
498
|
+
var KNOWN_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
499
|
+
"action",
|
|
500
|
+
"selector",
|
|
501
|
+
"url",
|
|
502
|
+
"value",
|
|
503
|
+
"key",
|
|
504
|
+
"waitFor",
|
|
505
|
+
"timeout",
|
|
506
|
+
"optional",
|
|
507
|
+
"method",
|
|
508
|
+
"clear",
|
|
509
|
+
"blur",
|
|
510
|
+
"delay",
|
|
511
|
+
"waitForNavigation",
|
|
512
|
+
"trigger",
|
|
513
|
+
"option",
|
|
514
|
+
"match",
|
|
515
|
+
"x",
|
|
516
|
+
"y",
|
|
517
|
+
"direction",
|
|
518
|
+
"amount",
|
|
519
|
+
"format",
|
|
520
|
+
"quality",
|
|
521
|
+
"fullPage"
|
|
522
|
+
]);
|
|
523
|
+
function resolveAction(name) {
|
|
524
|
+
if (VALID_ACTIONS.includes(name)) {
|
|
525
|
+
return { action: name };
|
|
526
|
+
}
|
|
527
|
+
const lower = name.toLowerCase();
|
|
528
|
+
if (ACTION_ALIASES[lower]) {
|
|
529
|
+
return {
|
|
530
|
+
action: ACTION_ALIASES[lower],
|
|
531
|
+
suggestion: `Did you mean "${ACTION_ALIASES[lower]}"?`
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
let best = null;
|
|
535
|
+
let bestDist = Infinity;
|
|
536
|
+
for (const valid of VALID_ACTIONS) {
|
|
537
|
+
const dist = levenshtein(lower, valid);
|
|
538
|
+
if (dist < bestDist) {
|
|
539
|
+
bestDist = dist;
|
|
540
|
+
best = valid;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (best && bestDist <= 2) {
|
|
544
|
+
return { action: best, suggestion: `Did you mean "${best}"?` };
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
function suggestProperty(name) {
|
|
549
|
+
if (PROPERTY_ALIASES[name]) {
|
|
550
|
+
return PROPERTY_ALIASES[name];
|
|
551
|
+
}
|
|
552
|
+
let best = null;
|
|
553
|
+
let bestDist = Infinity;
|
|
554
|
+
for (const known of KNOWN_STEP_FIELDS) {
|
|
555
|
+
if (known === "action") continue;
|
|
556
|
+
const dist = levenshtein(name, known);
|
|
557
|
+
if (dist < bestDist) {
|
|
558
|
+
bestDist = dist;
|
|
559
|
+
best = known;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (best && bestDist <= 2) {
|
|
563
|
+
return best;
|
|
564
|
+
}
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
function checkFieldType(value, rule) {
|
|
568
|
+
switch (rule.type) {
|
|
569
|
+
case "string":
|
|
570
|
+
if (typeof value !== "string") return `expected string, got ${typeof value}`;
|
|
571
|
+
if (rule.enum && !rule.enum.includes(value)) {
|
|
572
|
+
return `must be one of: ${rule.enum.join(", ")}`;
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
case "string|string[]":
|
|
576
|
+
if (typeof value !== "string" && !Array.isArray(value)) {
|
|
577
|
+
return `expected string or string[], got ${typeof value}`;
|
|
578
|
+
}
|
|
579
|
+
if (Array.isArray(value) && value.some((v) => typeof v !== "string")) {
|
|
580
|
+
return "array elements must be strings";
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
case "number":
|
|
584
|
+
if (typeof value !== "number") return `expected number, got ${typeof value}`;
|
|
585
|
+
return null;
|
|
586
|
+
case "boolean":
|
|
587
|
+
if (typeof value !== "boolean") return `expected boolean, got ${typeof value}`;
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function validateSteps(steps) {
|
|
592
|
+
const errors = [];
|
|
593
|
+
for (let i = 0; i < steps.length; i++) {
|
|
594
|
+
const step = steps[i];
|
|
595
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
596
|
+
errors.push({
|
|
597
|
+
stepIndex: i,
|
|
598
|
+
field: "step",
|
|
599
|
+
message: "step must be a JSON object."
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const obj = step;
|
|
604
|
+
if (!("action" in obj)) {
|
|
605
|
+
errors.push({
|
|
606
|
+
stepIndex: i,
|
|
607
|
+
field: "action",
|
|
608
|
+
message: 'missing required "action" field.'
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const actionName = obj["action"];
|
|
613
|
+
if (typeof actionName !== "string") {
|
|
614
|
+
errors.push({
|
|
615
|
+
stepIndex: i,
|
|
616
|
+
field: "action",
|
|
617
|
+
message: `"action" must be a string, got ${typeof actionName}.`
|
|
618
|
+
});
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const resolved = resolveAction(actionName);
|
|
622
|
+
if (!resolved) {
|
|
623
|
+
errors.push({
|
|
624
|
+
stepIndex: i,
|
|
625
|
+
field: "action",
|
|
626
|
+
message: `unknown action "${actionName}".`,
|
|
627
|
+
suggestion: `Valid actions: ${VALID_ACTIONS_LIST}`
|
|
628
|
+
});
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (resolved.suggestion) {
|
|
632
|
+
errors.push({
|
|
633
|
+
stepIndex: i,
|
|
634
|
+
field: "action",
|
|
635
|
+
message: `unknown action "${actionName}". ${resolved.suggestion}`,
|
|
636
|
+
suggestion: resolved.suggestion
|
|
637
|
+
});
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const action = resolved.action;
|
|
641
|
+
const rule = ACTION_RULES[action];
|
|
642
|
+
for (const key of Object.keys(obj)) {
|
|
643
|
+
if (key === "action") continue;
|
|
644
|
+
if (!KNOWN_STEP_FIELDS.has(key)) {
|
|
645
|
+
const suggestion = suggestProperty(key);
|
|
646
|
+
errors.push({
|
|
647
|
+
stepIndex: i,
|
|
648
|
+
field: key,
|
|
649
|
+
message: suggestion ? `unknown property "${key}". Did you mean "${suggestion}"?` : `unknown property "${key}".`,
|
|
650
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
for (const [field, fieldRule] of Object.entries(rule.required)) {
|
|
655
|
+
if (!(field in obj) || obj[field] === void 0) {
|
|
656
|
+
errors.push({
|
|
657
|
+
stepIndex: i,
|
|
658
|
+
field,
|
|
659
|
+
message: `missing required "${field}" (${fieldRule.type}).`
|
|
660
|
+
});
|
|
661
|
+
} else {
|
|
662
|
+
const typeErr = checkFieldType(obj[field], fieldRule);
|
|
663
|
+
if (typeErr) {
|
|
664
|
+
errors.push({
|
|
665
|
+
stepIndex: i,
|
|
666
|
+
field,
|
|
667
|
+
message: `"${field}" ${typeErr}.`
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
for (const [field, fieldRule] of Object.entries(rule.optional)) {
|
|
673
|
+
if (field in obj && obj[field] !== void 0) {
|
|
674
|
+
const typeErr = checkFieldType(obj[field], fieldRule);
|
|
675
|
+
if (typeErr) {
|
|
676
|
+
errors.push({
|
|
677
|
+
stepIndex: i,
|
|
678
|
+
field,
|
|
679
|
+
message: `"${field}" ${typeErr}.`
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if ("timeout" in obj && obj["timeout"] !== void 0) {
|
|
685
|
+
if (typeof obj["timeout"] !== "number") {
|
|
686
|
+
errors.push({
|
|
687
|
+
stepIndex: i,
|
|
688
|
+
field: "timeout",
|
|
689
|
+
message: `"timeout" expected number, got ${typeof obj["timeout"]}.`
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if ("optional" in obj && obj["optional"] !== void 0) {
|
|
694
|
+
if (typeof obj["optional"] !== "boolean") {
|
|
695
|
+
errors.push({
|
|
696
|
+
stepIndex: i,
|
|
697
|
+
field: "optional",
|
|
698
|
+
message: `"optional" expected boolean, got ${typeof obj["optional"]}.`
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (action === "select") {
|
|
703
|
+
const hasNative = "selector" in obj && "value" in obj;
|
|
704
|
+
const hasCustom = "trigger" in obj && "option" in obj && "value" in obj;
|
|
705
|
+
if (!hasNative && !hasCustom) {
|
|
706
|
+
errors.push({
|
|
707
|
+
stepIndex: i,
|
|
708
|
+
field: "selector",
|
|
709
|
+
message: "select requires either (selector + value) for native select, or (trigger + option + value) for custom select."
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
valid: errors.length === 0,
|
|
716
|
+
errors,
|
|
717
|
+
formatted() {
|
|
718
|
+
if (errors.length === 0) return "";
|
|
719
|
+
const lines = [`Validation failed (${errors.length} error${errors.length > 1 ? "s" : ""}):`];
|
|
720
|
+
for (const err of errors) {
|
|
721
|
+
const stepLabel = err.field === "action" || err.field === "step" ? `Step ${err.stepIndex}` : `Step ${err.stepIndex}`;
|
|
722
|
+
lines.push("");
|
|
723
|
+
lines.push(` ${stepLabel}: ${err.message}`);
|
|
724
|
+
if (err.suggestion && !err.message.includes(err.suggestion)) {
|
|
725
|
+
lines.push(` ${err.suggestion}`);
|
|
726
|
+
}
|
|
727
|
+
const step = steps[err.stepIndex];
|
|
728
|
+
if (step && typeof step === "object") {
|
|
729
|
+
lines.push(` Got: ${JSON.stringify(step)}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const hasEvaluateError = errors.some((err) => {
|
|
733
|
+
const step = steps[err.stepIndex];
|
|
734
|
+
return step && typeof step === "object" && step["action"] === "evaluate";
|
|
735
|
+
});
|
|
736
|
+
if (hasEvaluateError) {
|
|
737
|
+
lines.push("");
|
|
738
|
+
lines.push(
|
|
739
|
+
"Tip: For JavaScript evaluation, use 'bp eval' instead \u2014 no JSON wrapping needed:"
|
|
740
|
+
);
|
|
741
|
+
lines.push(" bp eval 'your.expression.here'");
|
|
742
|
+
}
|
|
743
|
+
lines.push("");
|
|
744
|
+
lines.push(`Valid actions: ${VALID_ACTIONS_LIST}`);
|
|
745
|
+
return lines.join("\n");
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
291
749
|
// Annotate the CommonJS export names for ESM import in node:
|
|
292
750
|
0 && (module.exports = {
|
|
293
751
|
BatchExecutor,
|
|
294
|
-
addBatchToPage
|
|
752
|
+
addBatchToPage,
|
|
753
|
+
validateSteps
|
|
295
754
|
});
|
package/dist/actions.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ActionType, c as StepResult } from './types-
|
|
1
|
+
import { y as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-CYw-7vx1.cjs';
|
|
2
|
+
export { A as ActionType, c as StepResult } from './types-CYw-7vx1.cjs';
|
|
3
3
|
import './client-7Nqka5MV.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -30,4 +30,23 @@ declare function addBatchToPage(page: Page): Page & {
|
|
|
30
30
|
batch: (steps: Step[], options?: BatchOptions) => Promise<BatchResult>;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Step validation for batch executor
|
|
35
|
+
*
|
|
36
|
+
* Validates steps before browser connection, catching malformed JSON
|
|
37
|
+
* from AI agents with actionable, specific feedback.
|
|
38
|
+
*/
|
|
39
|
+
interface ValidationError {
|
|
40
|
+
stepIndex: number;
|
|
41
|
+
field: string;
|
|
42
|
+
message: string;
|
|
43
|
+
suggestion?: string;
|
|
44
|
+
}
|
|
45
|
+
interface ValidationResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
errors: ValidationError[];
|
|
48
|
+
formatted(): string;
|
|
49
|
+
}
|
|
50
|
+
declare function validateSteps(steps: unknown[]): ValidationResult;
|
|
51
|
+
|
|
52
|
+
export { BatchExecutor, BatchOptions, BatchResult, Step, type ValidationError, type ValidationResult, addBatchToPage, validateSteps };
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ActionType, c as StepResult } from './types-
|
|
1
|
+
import { y as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-DOGsEYQa.js';
|
|
2
|
+
export { A as ActionType, c as StepResult } from './types-DOGsEYQa.js';
|
|
3
3
|
import './client-7Nqka5MV.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -30,4 +30,23 @@ declare function addBatchToPage(page: Page): Page & {
|
|
|
30
30
|
batch: (steps: Step[], options?: BatchOptions) => Promise<BatchResult>;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Step validation for batch executor
|
|
35
|
+
*
|
|
36
|
+
* Validates steps before browser connection, catching malformed JSON
|
|
37
|
+
* from AI agents with actionable, specific feedback.
|
|
38
|
+
*/
|
|
39
|
+
interface ValidationError {
|
|
40
|
+
stepIndex: number;
|
|
41
|
+
field: string;
|
|
42
|
+
message: string;
|
|
43
|
+
suggestion?: string;
|
|
44
|
+
}
|
|
45
|
+
interface ValidationResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
errors: ValidationError[];
|
|
48
|
+
formatted(): string;
|
|
49
|
+
}
|
|
50
|
+
declare function validateSteps(steps: unknown[]): ValidationResult;
|
|
51
|
+
|
|
52
|
+
export { BatchExecutor, BatchOptions, BatchResult, Step, type ValidationError, type ValidationResult, addBatchToPage, validateSteps };
|
package/dist/actions.mjs
CHANGED