browser-pilot 0.0.8 → 0.0.10

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
@@ -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
@@ -355,6 +376,7 @@ bp exec --dialog accept '{"action":"click","selector":"#delete-btn"}'
355
376
  # Other commands
356
377
  bp text -s my-session --selector ".main-content"
357
378
  bp screenshot -s my-session --output page.png
379
+ bp listen ws -m "*voice*" # monitor WebSocket traffic
358
380
  bp list # list all sessions
359
381
  bp close -s my-session # close session
360
382
  bp actions # show complete action reference
@@ -378,7 +400,7 @@ bp exec '[
378
400
  {"action":"fill","selector":"ref:e5","value":"laptop"},
379
401
  {"action":"click","selector":"ref:e12"},
380
402
  {"action":"snapshot"}
381
- ]' --output json
403
+ ]' --format json
382
404
  ```
383
405
 
384
406
  Multi-selector fallbacks for robustness:
@@ -403,6 +425,44 @@ Output:
403
425
 
404
426
  Run `bp actions` for complete action reference.
405
427
 
428
+ ### Voice Agent Testing
429
+
430
+ Test audio-based AI apps (voice assistants, phone agents) by injecting microphone input and capturing spoken responses.
431
+
432
+ > **Full guide:** [Voice Agent Testing Guide](./docs/guides/voice-agent-testing.md)
433
+
434
+ ```bash
435
+ export OPENAI_API_KEY=sk-... # Required for --transcribe
436
+
437
+ # Validate audio pipeline
438
+ bp audio check -s my-session
439
+ # Output: "READY for roundtrip" with agent AudioContext detected
440
+
441
+ # Full round-trip: send audio prompt → wait for response → transcribe
442
+ bp audio roundtrip -i prompt.wav --transcribe --silence-timeout 1500
443
+ # Output: { "transcript": "Welcome! I'd be happy to help...", "latencyMs": 5200, ... }
444
+
445
+ # Save response audio for manual review
446
+ bp audio roundtrip -i prompt.wav -o response.wav --transcribe
447
+ ```
448
+
449
+ **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.
450
+
451
+ Programmatic API:
452
+
453
+ ```typescript
454
+ await page.setupAudio();
455
+
456
+ const result = await page.audioRoundTrip({
457
+ input: audioBytes,
458
+ silenceTimeout: 1500,
459
+ });
460
+
461
+ import { transcribe } from 'browser-pilot';
462
+ const { text } = await transcribe(result.audio);
463
+ console.log(text); // "Welcome! I'd be happy to help..."
464
+ ```
465
+
406
466
  ### Recording Browser Actions
407
467
 
408
468
  Record human interactions to create automation recipes:
@@ -573,6 +633,7 @@ See the [docs](./docs) folder for detailed documentation:
573
633
  - [Multi-Selector Guide](./docs/guides/multi-selector.md)
574
634
  - [Batch Actions](./docs/guides/batch-actions.md)
575
635
  - [Snapshots](./docs/guides/snapshots.md)
636
+ - [Voice Agent Testing](./docs/guides/voice-agent-testing.md)
576
637
  - [CLI Reference](./docs/cli.md)
577
638
  - [API Reference](./docs/api/page.md)
578
639
 
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
- throw new Error(
271
- `Unknown action: ${step.action}. Run 'bp actions' for available actions.`
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
  });
@@ -1,5 +1,5 @@
1
- import { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-DklIxnbO.cjs';
2
- export { A as ActionType, c as StepResult } from './types-DklIxnbO.cjs';
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
- export { BatchExecutor, BatchOptions, BatchResult, Step, addBatchToPage };
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 { P as Page, S as Step, B as BatchOptions, b as BatchResult } from './types-Pv8KzZ6l.js';
2
- export { A as ActionType, c as StepResult } from './types-Pv8KzZ6l.js';
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
- export { BatchExecutor, BatchOptions, BatchResult, Step, addBatchToPage };
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
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  BatchExecutor,
3
- addBatchToPage
4
- } from "./chunk-ZTQ37YQT.mjs";
3
+ addBatchToPage,
4
+ validateSteps
5
+ } from "./chunk-KKW2SZLV.mjs";
5
6
  export {
6
7
  BatchExecutor,
7
- addBatchToPage
8
+ addBatchToPage,
9
+ validateSteps
8
10
  };