@vosjs/cli 0.43.0 → 0.44.0

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
@@ -101,6 +101,8 @@ vos plan take --reuse # re-time that cut onto
101
101
  ]
102
102
  ```
103
103
 
104
+ **`vos actions script <actions.json>`: the flow as a shot list.** When no rung of the session ladder holds and a person has to record the take themselves (in their own signed-in browser, with the vosso extension), the agent hands over the flow it worked out rather than an apology: numbered beats in plain words with the holds the script asked for, the page to start on and about how long. A step's `caption` leads its beat, a `text=` or `:has-text()` selector is said as its words, and a step's `id` is said as a name (`new-order` reads as "new order"), so give steps ids and captions that a person could follow. `--json` carries the beats beside the text. The take comes back through the ordinary handoff, and the agent cuts it (`vos pull --media`, then the `vos-cut` skill).
105
+
104
106
  **`--header name=value`**, repeatable: a request header on every request the recording browser makes, the way past a preview deployment protected by a bypass header alone (`--header x-vercel-protection-bypass=$TOKEN`). The rehearsal's `Next:` line carries it.
105
107
 
106
108
  **What the frame shows.** Getting past a login puts the account's own data in the picture, and asking for a demo account does not hold that line: a person asked to sign in signs in as themselves. So the recorder looks. After the page opens and after every step it reads the text visible in the viewport and reports the KIND of thing it saw and where, never the string: an email address (one on `example.com` or a `.test`, `.example`, `.invalid` or `.localhost` domain is demo data and is not reported), something shaped like an API key or a JWT, a card number that passes the Luhn check, a masked card's visible tail. They land in `meta.exposures[]` (`step`, `kind`, `selector`, `rect`, `seen`), in the `record` and `create` done events, at the end of a rehearsal (before anything is recorded), as warnings in `vos validate <take>`, and in the digest's `take.exposures`. They warn; they do not fail a take, because a product may legitimately show addresses. The `selector` reaches that element and no other, so it can be pasted into a mask.
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/plugin/pace.ts
4
+ var POINTER_MS_PER_PX = 0.7;
5
+ var POINTER_MIN_MS = 250;
6
+ var POINTER_MAX_MS = 800;
7
+ var MOTION_TICK_MS = 16;
8
+ var SETTLE_MS = {
9
+ click: 150,
10
+ type: 150,
11
+ scroll: 200,
12
+ drag: 80
13
+ };
14
+ var PRESS_LEAD_MS = 80;
15
+ var PRESS_HOLD_MS = 70;
16
+ var SCROLL_SETTLE_MS = 120;
17
+ var TRAILING_HOLD_MS = 400;
18
+ function pointerTravelMs(dist) {
19
+ return Math.min(
20
+ POINTER_MAX_MS,
21
+ Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX))
22
+ );
23
+ }
24
+ function settleMs(step) {
25
+ if (typeof step.ms === "number" && step.ms >= 0) return step.ms;
26
+ const d = step.do;
27
+ return SETTLE_MS[d] ?? 0;
28
+ }
29
+ var easeInOutCubic = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
30
+ async function clockMotion(dur, at, clock) {
31
+ const start = clock.now();
32
+ let samples = 0;
33
+ for (; ; ) {
34
+ const elapsed = clock.now() - start;
35
+ if (elapsed >= dur) break;
36
+ await at(easeInOutCubic(Math.min(1, elapsed / dur)));
37
+ samples++;
38
+ const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
39
+ const wait = Math.min(next, start + dur) - clock.now();
40
+ if (wait > 0) await clock.sleep(wait);
41
+ }
42
+ await at(1);
43
+ return samples + 1;
44
+ }
45
+ async function clockTyping(chars, delay, type, clock) {
46
+ const start = clock.now();
47
+ for (let i = 0; i < chars.length; i++) {
48
+ await type(chars[i], i);
49
+ const due = start + (i + 1) * delay;
50
+ const wait = due - clock.now();
51
+ if (wait > 0 && i < chars.length - 1) await clock.sleep(wait);
52
+ }
53
+ }
54
+ function askedMs(step) {
55
+ switch (step.do) {
56
+ case "wait":
57
+ return step.ms ?? 0;
58
+ case "hover":
59
+ return step.ms ?? 700;
60
+ case "drag":
61
+ return step.ms ?? 700;
62
+ case "type":
63
+ return (step.text?.length ?? 0) * (step.delayMs ?? 40);
64
+ default:
65
+ return 0;
66
+ }
67
+ }
68
+ function paceReport(steps) {
69
+ const askedMs2 = steps.reduce((a, s) => a + s.askedMs, 0);
70
+ const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0);
71
+ const wallMs = steps.reduce((a, s) => a + s.wallMs, 0);
72
+ const overheadMs = Math.max(0, wallMs - askedMs2 - gestureMs);
73
+ const slow = steps.filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60).map((s) => ({
74
+ step: s.step,
75
+ do: s.do,
76
+ askedMs: s.askedMs,
77
+ wallMs: s.wallMs
78
+ }));
79
+ return {
80
+ askedMs: askedMs2,
81
+ gestureMs,
82
+ wallMs,
83
+ overheadMs,
84
+ overheadPct: wallMs > 0 ? Math.round(overheadMs / wallMs * 100) : 0,
85
+ slow
86
+ };
87
+ }
88
+ function paceLine(r) {
89
+ const s = (ms) => `${(ms / 1e3).toFixed(1)} s`;
90
+ const slow = r.slow.length ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(", ")}` : "";
91
+ return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`;
92
+ }
93
+
94
+ export {
95
+ PRESS_LEAD_MS,
96
+ PRESS_HOLD_MS,
97
+ SCROLL_SETTLE_MS,
98
+ TRAILING_HOLD_MS,
99
+ pointerTravelMs,
100
+ settleMs,
101
+ clockMotion,
102
+ clockTyping,
103
+ askedMs,
104
+ paceReport,
105
+ paceLine
106
+ };
107
+ //# sourceMappingURL=chunk-RDQYYRWN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/pace.ts"],"sourcesContent":["/**\n * The recorder's pace — the pure parts.\n *\n * A take of a scripted flow should run as long as the script asks, plus the\n * gestures a person makes between the asks (the pointer's travel, the press,\n * the settle after it) and no more. Measured before this module (§4.5 of\n * the utility-clip retrospective), hovers ran 2× their ask, drags 4.8× and\n * a 25 s script recorded 42 s, for two reasons the numbers here answer:\n *\n * - The motion loops counted STEPS (one per 16 ms of the asked duration)\n * and awaited a `mouse.move` round trip per step, so a page that\n * re-renders per move (a slider) stretched every gesture by the round\n * trip. A gesture is now driven by the CLOCK: its position is a function\n * of the elapsed time, and it ends when the asked duration has elapsed,\n * however many samples the page allowed.\n * - The settles after a gesture were fixed sleeps (500 ms after a click,\n * 250 after every selector lookup). A settle is DATA: `ms` on the step\n * overrides a small default, and the script's own `wait` steps carry\n * the intended pauses.\n */\n\n/** Pointer travel: ms per CSS px, floor and ceiling — a person's hand. */\nexport const POINTER_MS_PER_PX = 0.7\nexport const POINTER_MIN_MS = 250\nexport const POINTER_MAX_MS = 800\n/** The sample cadence a motion loop aims for. */\nexport const MOTION_TICK_MS = 16\n\n/** The settle after a gesture when the step names none, by verb. */\nexport const SETTLE_MS = {\n click: 150,\n type: 150,\n scroll: 200,\n drag: 80,\n} as const\n\n/** The press: the pause before the button goes down, and the hold. */\nexport const PRESS_LEAD_MS = 80\nexport const PRESS_HOLD_MS = 70\n/** The pause between a selector lookup that scrolled the page and the move. */\nexport const SCROLL_SETTLE_MS = 120\n/** The hold after the last step, so the take does not cut on a press. */\nexport const TRAILING_HOLD_MS = 400\n\n/** How long the pointer takes to travel `dist` CSS px. */\nexport function pointerTravelMs(dist: number): number {\n return Math.min(\n POINTER_MAX_MS,\n Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX)),\n )\n}\n\n/** The settle after a step: its own `ms` when it names one, else the verb's. */\nexport function settleMs(step: { do: string; ms?: number }): number {\n if (typeof step.ms === 'number' && step.ms >= 0) return step.ms\n const d = step.do as keyof typeof SETTLE_MS\n return SETTLE_MS[d] ?? 0\n}\n\nexport const easeInOutCubic = (u: number): number =>\n u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2\n\n/**\n * Drive a motion by the clock: `at(u)` is called with the eased progress\n * for each sample, and the loop ends when `dur` ms have elapsed, with the\n * last call at u = 1 exactly. Between samples it sleeps to the next tick\n * only when the sample came back early; a slow sample (a page busy\n * re-rendering) is followed at once, so the gesture keeps its length and\n * loses samples, never the other way round.\n */\nexport async function clockMotion(\n dur: number,\n at: (u: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<number> {\n const start = clock.now()\n let samples = 0\n for (;;) {\n const elapsed = clock.now() - start\n if (elapsed >= dur) break\n await at(easeInOutCubic(Math.min(1, elapsed / dur)))\n samples++\n const next =\n start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS\n const wait = Math.min(next, start + dur) - clock.now()\n if (wait > 0) await clock.sleep(wait)\n }\n await at(1)\n return samples + 1\n}\n\n/**\n * Typing paced by the clock: the i-th character is due at `start + i·delay`;\n * after each keystroke the loop sleeps to the next due time only if the\n * keystroke came back early. A slow field (an editor re-rendering per key)\n * types as fast as it can and says so in the pace report.\n */\nexport async function clockTyping(\n chars: readonly string[],\n delay: number,\n type: (ch: string, index: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<void> {\n const start = clock.now()\n for (let i = 0; i < chars.length; i++) {\n await type(chars[i], i)\n const due = start + (i + 1) * delay\n const wait = due - clock.now()\n if (wait > 0 && i < chars.length - 1) await clock.sleep(wait)\n }\n}\n\n/** One step's pace: what the script asked for it and what it took. */\nexport interface StepPace {\n step: number\n do: string\n /** The script's own ask: a wait's ms, a hover's dwell, a drag's ms, typing's chars × delay; 0 for a click or a scroll. */\n askedMs: number\n /** The gesture the recorder adds by design: pointer travel, the press, the settle. */\n gestureMs: number\n wallMs: number\n}\n\nexport interface PaceReport {\n askedMs: number\n gestureMs: number\n wallMs: number\n /** Wall time neither asked nor a gesture: what the page and the round trips cost. */\n overheadMs: number\n overheadPct: number\n /** The steps whose wall ran past their ask plus gesture by more than a third. */\n slow: { step: number; do: string; askedMs: number; wallMs: number }[]\n}\n\n/** What a script asks of a step, in ms: the part of its wall time that is the author's. */\nexport function askedMs(step: {\n do: string\n ms?: number\n text?: string\n delayMs?: number\n}): number {\n switch (step.do) {\n case 'wait':\n return step.ms ?? 0\n case 'hover':\n return step.ms ?? 700\n case 'drag':\n return step.ms ?? 700\n case 'type':\n return (step.text?.length ?? 0) * (step.delayMs ?? 40)\n default:\n return 0\n }\n}\n\nexport function paceReport(steps: readonly StepPace[]): PaceReport {\n const askedMs = steps.reduce((a, s) => a + s.askedMs, 0)\n const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0)\n const wallMs = steps.reduce((a, s) => a + s.wallMs, 0)\n const overheadMs = Math.max(0, wallMs - askedMs - gestureMs)\n const slow = steps\n .filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60)\n .map((s) => ({\n step: s.step,\n do: s.do,\n askedMs: s.askedMs,\n wallMs: s.wallMs,\n }))\n return {\n askedMs,\n gestureMs,\n wallMs,\n overheadMs,\n overheadPct: wallMs > 0 ? Math.round((overheadMs / wallMs) * 100) : 0,\n slow,\n }\n}\n\n/** The pace in one line for the log. */\nexport function paceLine(r: PaceReport): string {\n const s = (ms: number) => `${(ms / 1000).toFixed(1)} s`\n const slow = r.slow.length\n ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(', ')}`\n : ''\n return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`\n}\n"],"mappings":"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","names":["askedMs"]}
@@ -6,6 +6,19 @@ import {
6
6
  noteGivenFlag,
7
7
  trackFlags
8
8
  } from "./chunk-FACYY7VV.js";
9
+ import {
10
+ PRESS_HOLD_MS,
11
+ PRESS_LEAD_MS,
12
+ SCROLL_SETTLE_MS,
13
+ TRAILING_HOLD_MS,
14
+ askedMs,
15
+ clockMotion,
16
+ clockTyping,
17
+ paceLine,
18
+ paceReport,
19
+ pointerTravelMs,
20
+ settleMs
21
+ } from "./chunk-RDQYYRWN.js";
9
22
 
10
23
  // src/plugin/run.ts
11
24
  import { mkdir as mkdir10, readFile as readFile15, rm as rm5 } from "fs/promises";
@@ -6686,97 +6699,6 @@ function cappedLine(capSeconds) {
6686
6699
  return `stopped at ${formatDurationCap(capSeconds)} (--max-duration ${capSeconds}); the remaining steps did not run`;
6687
6700
  }
6688
6701
 
6689
- // src/plugin/pace.ts
6690
- var POINTER_MS_PER_PX = 0.7;
6691
- var POINTER_MIN_MS = 250;
6692
- var POINTER_MAX_MS = 800;
6693
- var MOTION_TICK_MS = 16;
6694
- var SETTLE_MS = {
6695
- click: 150,
6696
- type: 150,
6697
- scroll: 200,
6698
- drag: 80
6699
- };
6700
- var PRESS_LEAD_MS = 80;
6701
- var PRESS_HOLD_MS = 70;
6702
- var SCROLL_SETTLE_MS = 120;
6703
- var TRAILING_HOLD_MS = 400;
6704
- function pointerTravelMs(dist) {
6705
- return Math.min(
6706
- POINTER_MAX_MS,
6707
- Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX))
6708
- );
6709
- }
6710
- function settleMs(step) {
6711
- if (typeof step.ms === "number" && step.ms >= 0) return step.ms;
6712
- const d = step.do;
6713
- return SETTLE_MS[d] ?? 0;
6714
- }
6715
- var easeInOutCubic = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
6716
- async function clockMotion(dur, at2, clock) {
6717
- const start = clock.now();
6718
- let samples = 0;
6719
- for (; ; ) {
6720
- const elapsed = clock.now() - start;
6721
- if (elapsed >= dur) break;
6722
- await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
6723
- samples++;
6724
- const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
6725
- const wait2 = Math.min(next, start + dur) - clock.now();
6726
- if (wait2 > 0) await clock.sleep(wait2);
6727
- }
6728
- await at2(1);
6729
- return samples + 1;
6730
- }
6731
- async function clockTyping(chars, delay, type, clock) {
6732
- const start = clock.now();
6733
- for (let i = 0; i < chars.length; i++) {
6734
- await type(chars[i], i);
6735
- const due = start + (i + 1) * delay;
6736
- const wait2 = due - clock.now();
6737
- if (wait2 > 0 && i < chars.length - 1) await clock.sleep(wait2);
6738
- }
6739
- }
6740
- function askedMs(step) {
6741
- switch (step.do) {
6742
- case "wait":
6743
- return step.ms ?? 0;
6744
- case "hover":
6745
- return step.ms ?? 700;
6746
- case "drag":
6747
- return step.ms ?? 700;
6748
- case "type":
6749
- return (step.text?.length ?? 0) * (step.delayMs ?? 40);
6750
- default:
6751
- return 0;
6752
- }
6753
- }
6754
- function paceReport(steps) {
6755
- const askedMs2 = steps.reduce((a, s) => a + s.askedMs, 0);
6756
- const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0);
6757
- const wallMs = steps.reduce((a, s) => a + s.wallMs, 0);
6758
- const overheadMs = Math.max(0, wallMs - askedMs2 - gestureMs);
6759
- const slow = steps.filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60).map((s) => ({
6760
- step: s.step,
6761
- do: s.do,
6762
- askedMs: s.askedMs,
6763
- wallMs: s.wallMs
6764
- }));
6765
- return {
6766
- askedMs: askedMs2,
6767
- gestureMs,
6768
- wallMs,
6769
- overheadMs,
6770
- overheadPct: wallMs > 0 ? Math.round(overheadMs / wallMs * 100) : 0,
6771
- slow
6772
- };
6773
- }
6774
- function paceLine(r) {
6775
- const s = (ms) => `${(ms / 1e3).toFixed(1)} s`;
6776
- const slow = r.slow.length ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(", ")}` : "";
6777
- return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`;
6778
- }
6779
-
6780
6702
  // src/plugin/wall.ts
6781
6703
  var IDP_HOSTS = [
6782
6704
  "accounts.google.com",
@@ -10588,9 +10510,10 @@ function convertAgentBrowser(records, opts = {}) {
10588
10510
  async function cmdActions(argv) {
10589
10511
  const { positionals, flags } = parseArgs(argv, /* @__PURE__ */ new Set(["json"]));
10590
10512
  const [sub, input] = positionals;
10513
+ if (sub === "script" && input) return cmdActionsScript(input, flags);
10591
10514
  if (sub !== "from-agent-browser" || !input) {
10592
10515
  throw new UsageError(
10593
- "vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]"
10516
+ "vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]\n vos actions script <actions.json> [--json]"
10594
10517
  );
10595
10518
  }
10596
10519
  const r = createReporter(flags.json === true);
@@ -10638,6 +10561,18 @@ function summary(result) {
10638
10561
  notes: result.notes
10639
10562
  };
10640
10563
  }
10564
+ async function cmdActionsScript(input, flags) {
10565
+ const r = createReporter(flags.json === true);
10566
+ const { readFile: readFile16 } = await import("fs/promises");
10567
+ const { shotList, formatShotList } = await import("./shotList-FYDHO5AI.js");
10568
+ const raw = JSON.parse(await readFile16(resolve8(input), "utf8"));
10569
+ const errors = validateActions(raw);
10570
+ if (errors.length) throw new UsageError(`${input}:
10571
+ ${errors.join("\n ")}`);
10572
+ const list = shotList(raw);
10573
+ r.done({ ...list, text: formatShotList(list) }, formatShotList(list));
10574
+ return EXIT_OK;
10575
+ }
10641
10576
 
10642
10577
  // src/plugin/run.ts
10643
10578
  var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
@@ -10680,6 +10615,8 @@ Take pipeline
10680
10615
  vos validate <actions.json|take|kit.json> [--picture] [--json]
10681
10616
  vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
10682
10617
  vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
10618
+ vos actions script <actions.json> [--json]
10619
+ the flow as numbered beats in words, for a person recording it by hand
10683
10620
 
10684
10621
  Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10685
10622
  vos login [--key <vos_sk_\u2026>] [--label <name>] [--no-browser]
@@ -12411,4 +12348,4 @@ export {
12411
12348
  verbHelp,
12412
12349
  run
12413
12350
  };
12414
- //# sourceMappingURL=chunk-6PJPGBKH.js.map
12351
+ //# sourceMappingURL=chunk-WCSAECM4.js.map