@vosjs/cli 0.43.0 → 0.45.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 +18 -1
- package/dist/{chunk-6PJPGBKH.js → chunk-JRRM7P5G.js} +437 -183
- package/dist/chunk-JRRM7P5G.js.map +1 -0
- package/dist/chunk-RDQYYRWN.js +107 -0
- package/dist/chunk-RDQYYRWN.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +13 -1
- package/dist/index.js +2 -1
- package/dist/{run-ZYROWQ3T.js → run-3TY74GWE.js} +3 -2
- package/dist/shotList-FYDHO5AI.js +111 -0
- package/dist/shotList-FYDHO5AI.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-6PJPGBKH.js.map +0 -1
- /package/dist/{run-ZYROWQ3T.js.map → run-3TY74GWE.js.map} +0 -0
|
@@ -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"]}
|
package/dist/cli.js
CHANGED
|
@@ -511,7 +511,7 @@ async function cmdCheck(argv) {
|
|
|
511
511
|
return result.ok ? EXIT_OK : EXIT_ERROR;
|
|
512
512
|
}
|
|
513
513
|
async function delegate(argv, viaAlias = false) {
|
|
514
|
-
const { run } = await import("./run-
|
|
514
|
+
const { run } = await import("./run-3TY74GWE.js");
|
|
515
515
|
if (viaAlias && argv[0]) {
|
|
516
516
|
process.stderr.write(
|
|
517
517
|
`note: "vos voila ${argv[0]}" is now "vos ${argv[0]}".
|
|
@@ -551,7 +551,7 @@ async function main() {
|
|
|
551
551
|
const engine = HELP_ENGINE.split("\n").filter(
|
|
552
552
|
(l) => l.startsWith(` vos ${cmd} `)
|
|
553
553
|
);
|
|
554
|
-
const { verbHelp } = await import("./run-
|
|
554
|
+
const { verbHelp } = await import("./run-3TY74GWE.js");
|
|
555
555
|
const take = verbHelp(cmd);
|
|
556
556
|
process.stdout.write(
|
|
557
557
|
`${engine.join("\n")}
|
|
@@ -583,7 +583,7 @@ ${take.includes("no such verb") ? "" : take}`
|
|
|
583
583
|
main().then(async (code) => {
|
|
584
584
|
if (code === EXIT_OK) {
|
|
585
585
|
const verb = process.argv[2] ?? "";
|
|
586
|
-
const { HELP } = await import("./run-
|
|
586
|
+
const { HELP } = await import("./run-3TY74GWE.js");
|
|
587
587
|
const documented = [
|
|
588
588
|
...helpFlagsFor(HELP_ENGINE, verb),
|
|
589
589
|
...helpFlagsFor(HELP, verb)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Browser, Page } from 'playwright';
|
|
1
|
+
import { Browser, BrowserContext, Page } from 'playwright';
|
|
2
2
|
import { CursorTrack, RecordingMeta, ProjectDoc, CursorEvent, TemplateAnchor, Backdrop, LayoutParts, TranscriptSegment, Digest } from '@vosjs/studio-core';
|
|
3
3
|
export { Digest } from '@vosjs/studio-core';
|
|
4
4
|
|
|
@@ -276,6 +276,12 @@ type ActionStep = ({
|
|
|
276
276
|
* edits without breaking the cut anchored to it. Unique when present.
|
|
277
277
|
*/
|
|
278
278
|
id?: string;
|
|
279
|
+
/**
|
|
280
|
+
* A beat's caption, two to eight words: `vos deliver` lands it as a
|
|
281
|
+
* lower-third at this step's moment on the cuts that take words, and
|
|
282
|
+
* `vos actions script` leads the beat with it.
|
|
283
|
+
*/
|
|
284
|
+
caption?: string;
|
|
279
285
|
};
|
|
280
286
|
/** Structural validation with actionable messages. Returns [] when valid. */
|
|
281
287
|
declare function validateActions(value: unknown): string[];
|
|
@@ -428,6 +434,12 @@ interface RecordOpts {
|
|
|
428
434
|
onArrival?: (arrival: Arrival) => Promise<WallVerdict | null | void> | WallVerdict | null | void;
|
|
429
435
|
/** Extra request headers on every request (`--header name=value`). */
|
|
430
436
|
headers?: Record<string, string>;
|
|
437
|
+
/**
|
|
438
|
+
* A ready-made context to record IN, instead of one made from `browser`:
|
|
439
|
+
* `--session <name>`, a persistent profile the person signed in to. The
|
|
440
|
+
* recorder still sizes the viewport and closes it at the end.
|
|
441
|
+
*/
|
|
442
|
+
context?: BrowserContext;
|
|
431
443
|
}
|
|
432
444
|
declare function recordTake(browser: Browser, url: string, actions: ActionsFile, paths: TakePaths, log: (msg: string) => void, opts?: RecordOpts): Promise<RecordResult>;
|
|
433
445
|
|
package/dist/index.js
CHANGED
|
@@ -30,13 +30,14 @@ import {
|
|
|
30
30
|
validateActions,
|
|
31
31
|
waitForPageDone,
|
|
32
32
|
writeSyncState
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-JRRM7P5G.js";
|
|
34
34
|
import {
|
|
35
35
|
BrowserUnavailableError,
|
|
36
36
|
configDuration,
|
|
37
37
|
launchBrowser,
|
|
38
38
|
loadVosConfig
|
|
39
39
|
} from "./chunk-FACYY7VV.js";
|
|
40
|
+
import "./chunk-RDQYYRWN.js";
|
|
40
41
|
import {
|
|
41
42
|
manifest
|
|
42
43
|
} from "./chunk-EFI3WUQB.js";
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
run,
|
|
6
6
|
takeBrowserArgs,
|
|
7
7
|
verbHelp
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-JRRM7P5G.js";
|
|
9
9
|
import "./chunk-FACYY7VV.js";
|
|
10
|
+
import "./chunk-RDQYYRWN.js";
|
|
10
11
|
export {
|
|
11
12
|
HELP,
|
|
12
13
|
MULTI_FLAGS,
|
|
@@ -14,4 +15,4 @@ export {
|
|
|
14
15
|
takeBrowserArgs,
|
|
15
16
|
verbHelp
|
|
16
17
|
};
|
|
17
|
-
//# sourceMappingURL=run-
|
|
18
|
+
//# sourceMappingURL=run-3TY74GWE.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
askedMs
|
|
4
|
+
} from "./chunk-RDQYYRWN.js";
|
|
5
|
+
|
|
6
|
+
// src/plugin/shotList.ts
|
|
7
|
+
function sayTarget(selector) {
|
|
8
|
+
const text = /^text=(['"]?)(.+?)\1$/.exec(selector);
|
|
9
|
+
if (text) return `"${text[2]}"`;
|
|
10
|
+
const has = /:has-text\((['"])(.+?)\1\)/.exec(selector);
|
|
11
|
+
if (has) return `"${has[2]}"`;
|
|
12
|
+
const role = /getByRole\(['"](\w+)['"],\s*\{\s*name:\s*(['"])(.+?)\2/.exec(
|
|
13
|
+
selector
|
|
14
|
+
);
|
|
15
|
+
if (role) return `the ${role[1]} "${role[3]}"`;
|
|
16
|
+
const label = /\[aria-label=(['"])(.+?)\1\]/.exec(selector);
|
|
17
|
+
if (label) return `"${label[2]}"`;
|
|
18
|
+
const ph = /\[placeholder=(['"])(.+?)\1\]/.exec(selector);
|
|
19
|
+
if (ph) return `the "${ph[2]}" field`;
|
|
20
|
+
return `\`${selector}\``;
|
|
21
|
+
}
|
|
22
|
+
var s1 = (ms) => `${Math.round(ms / 100) / 10}`.replace(/\.0$/, "");
|
|
23
|
+
function sayId(id) {
|
|
24
|
+
if (!id || /^\d+$/.test(id) || id.length < 3) return null;
|
|
25
|
+
const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").toLowerCase().trim();
|
|
26
|
+
return words.length >= 3 ? words : null;
|
|
27
|
+
}
|
|
28
|
+
function sayStepTarget(step) {
|
|
29
|
+
const bySelector = sayTarget(step.selector);
|
|
30
|
+
if (!bySelector.startsWith("`")) return bySelector;
|
|
31
|
+
const byId = sayId(step.id);
|
|
32
|
+
return byId ? `${byId} (${bySelector})` : bySelector;
|
|
33
|
+
}
|
|
34
|
+
function beatOf(step) {
|
|
35
|
+
const cap = step.caption;
|
|
36
|
+
const named = cap ? `${cap}: ` : "";
|
|
37
|
+
switch (step.do) {
|
|
38
|
+
case "wait":
|
|
39
|
+
return null;
|
|
40
|
+
case "hover":
|
|
41
|
+
return `${named}Rest the pointer on ${sayStepTarget(step)}`;
|
|
42
|
+
case "click":
|
|
43
|
+
return `${named}Click ${sayStepTarget(step)}`;
|
|
44
|
+
case "type":
|
|
45
|
+
return `${named}Type "${step.text}" into ${sayStepTarget(step)}`;
|
|
46
|
+
case "scroll":
|
|
47
|
+
return `${named}Scroll ${step.dy < 0 ? "up" : "down"} ${step.dy < 0 ? "a little" : "a little"}`;
|
|
48
|
+
case "move":
|
|
49
|
+
return `${named}Move the pointer to the ${step.y < 360 ? "upper" : "lower"} ${step.x < 640 ? "left" : "right"}`;
|
|
50
|
+
case "drag":
|
|
51
|
+
return `${named}Drag ${step.selector ? sayStepTarget(step) : "from the start point"} across`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function shotList(actions) {
|
|
55
|
+
const beats = [];
|
|
56
|
+
let pendingHold = 0;
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (const step of actions.steps) {
|
|
59
|
+
total += askedMs(step);
|
|
60
|
+
if (step.do === "wait") {
|
|
61
|
+
if (beats.length) {
|
|
62
|
+
const last = beats[beats.length - 1];
|
|
63
|
+
last.holdS = (last.holdS ?? 0) + step.ms / 1e3;
|
|
64
|
+
} else pendingHold += step.ms;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const line = beatOf(step);
|
|
68
|
+
if (!line) continue;
|
|
69
|
+
const beat = { n: beats.length + 1, line };
|
|
70
|
+
if (step.do === "hover") beat.holdS = askedMs(step) / 1e3;
|
|
71
|
+
beats.push(beat);
|
|
72
|
+
}
|
|
73
|
+
void pendingHold;
|
|
74
|
+
return {
|
|
75
|
+
url: actions.url,
|
|
76
|
+
viewport: actions.viewport,
|
|
77
|
+
beats,
|
|
78
|
+
// gestures the recorder adds around each ask: about a second a beat
|
|
79
|
+
aboutS: Math.round((total + beats.length * 1e3) / 1e3),
|
|
80
|
+
setup: actions.setup?.length ?? 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function formatShotList(list) {
|
|
84
|
+
const out = [];
|
|
85
|
+
out.push(
|
|
86
|
+
`Record this with the vosso extension in your own signed-in browser, about ${list.aboutS} seconds${list.viewport ? `, window about ${list.viewport.width}\xD7${list.viewport.height}` : ""}.`
|
|
87
|
+
);
|
|
88
|
+
if (list.url)
|
|
89
|
+
out.push(`Start on ${list.url}, signed in, with the page settled.`);
|
|
90
|
+
if (list.setup)
|
|
91
|
+
out.push(
|
|
92
|
+
`Before you press record: the script's ${list.setup} setup step${list.setup === 1 ? "" : "s"} (sign in, dismiss banners) are yours to do first; they are not part of the take.`
|
|
93
|
+
);
|
|
94
|
+
out.push("");
|
|
95
|
+
for (const b of list.beats) {
|
|
96
|
+
const hold = b.holdS && b.holdS >= 0.4 ? ` Hold ${s1(b.holdS * 1e3)} s.` : "";
|
|
97
|
+
out.push(`${b.n}. ${b.line}.${hold}`);
|
|
98
|
+
}
|
|
99
|
+
out.push("");
|
|
100
|
+
out.push(
|
|
101
|
+
"Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf."
|
|
102
|
+
);
|
|
103
|
+
return out.join("\n");
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
formatShotList,
|
|
107
|
+
sayId,
|
|
108
|
+
sayTarget,
|
|
109
|
+
shotList
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=shotList-FYDHO5AI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin/shotList.ts"],"sourcesContent":["/**\n * The shot list: an `actions.json` said in words, for a person who will\n * record it by hand. The last rung of the session ladder is \"the human\n * records, the agent cuts\", and an agent that falls to it should hand over\n * more than an apology: the flow it worked out, as numbered beats with the\n * pace it wanted, so its knowledge survives the hop to a human. The take\n * comes back through the ordinary handoff and the agent cuts it.\n *\n * Pure: an ActionsFile in, lines out. Selectors are said as what they name\n * where the script says (an id, a caption, a text= locator), and as the\n * selector itself where it does not: a person reading `#filter-late` can\n * find it; a person reading nothing cannot.\n */\nimport type { ActionStep, ActionsFile } from './actions'\nimport { askedMs } from './pace'\n\nexport interface ShotBeat {\n n: number\n /** what to do, in words */\n line: string\n /** how long to hold after, in seconds, when the script asks for a hold */\n holdS?: number\n}\n\nexport interface ShotList {\n url?: string\n viewport?: { width: number; height: number }\n beats: ShotBeat[]\n /** the take's asked length, from the script's own pacing */\n aboutS: number\n setup: number\n}\n\n/** A selector as a person reads it: the text it names, or the selector itself. */\nexport function sayTarget(selector: string): string {\n const text = /^text=(['\"]?)(.+?)\\1$/.exec(selector)\n if (text) return `\"${text[2]}\"`\n const has = /:has-text\\((['\"])(.+?)\\1\\)/.exec(selector)\n if (has) return `\"${has[2]}\"`\n const role = /getByRole\\(['\"](\\w+)['\"],\\s*\\{\\s*name:\\s*(['\"])(.+?)\\2/.exec(\n selector,\n )\n if (role) return `the ${role[1]} \"${role[3]}\"`\n const label = /\\[aria-label=(['\"])(.+?)\\1\\]/.exec(selector)\n if (label) return `\"${label[2]}\"`\n const ph = /\\[placeholder=(['\"])(.+?)\\1\\]/.exec(selector)\n if (ph) return `the \"${ph[2]}\" field`\n return `\\`${selector}\\``\n}\n\nconst s1 = (ms: number) => `${Math.round(ms / 100) / 10}`.replace(/\\.0$/, '')\n\n/** A step's id, said as a name when it reads as one (`late`, `new-order`, `openBilling`). */\nexport function sayId(id: string | undefined): string | null {\n if (!id || /^\\d+$/.test(id) || id.length < 3) return null\n const words = id\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n return words.length >= 3 ? words : null\n}\n\n/** What a step points at, for a person: the words the selector names, else the id's, else the selector. */\nfunction sayStepTarget(step: { selector: string; id?: string }): string {\n const bySelector = sayTarget(step.selector)\n if (!bySelector.startsWith('`')) return bySelector\n const byId = sayId(step.id)\n return byId ? `${byId} (${bySelector})` : bySelector\n}\n\nfunction beatOf(step: ActionStep): string | null {\n const cap = step.caption\n const named = cap ? `${cap}: ` : ''\n switch (step.do) {\n case 'wait':\n return null\n case 'hover':\n return `${named}Rest the pointer on ${sayStepTarget(step)}`\n case 'click':\n return `${named}Click ${sayStepTarget(step)}`\n case 'type':\n return `${named}Type \"${step.text}\" into ${sayStepTarget(step)}`\n case 'scroll':\n return `${named}Scroll ${step.dy < 0 ? 'up' : 'down'} ${step.dy < 0 ? 'a little' : 'a little'}`\n case 'move':\n return `${named}Move the pointer to the ${step.y < 360 ? 'upper' : 'lower'} ${step.x < 640 ? 'left' : 'right'}`\n case 'drag':\n return `${named}Drag ${step.selector ? sayStepTarget(step as { selector: string; id?: string }) : 'from the start point'} across`\n }\n}\n\nexport function shotList(actions: ActionsFile): ShotList {\n const beats: ShotBeat[] = []\n let pendingHold = 0\n let total = 0\n for (const step of actions.steps) {\n total += askedMs(step)\n if (step.do === 'wait') {\n // A wait after a beat is that beat's hold; a leading wait is the\n // settle before the first, which a person does without being told.\n if (beats.length) {\n const last = beats[beats.length - 1]\n last.holdS = (last.holdS ?? 0) + step.ms / 1000\n } else pendingHold += step.ms\n continue\n }\n const line = beatOf(step)\n if (!line) continue\n const beat: ShotBeat = { n: beats.length + 1, line }\n if (step.do === 'hover') beat.holdS = askedMs(step) / 1000\n beats.push(beat)\n }\n void pendingHold\n return {\n url: actions.url,\n viewport: actions.viewport,\n beats,\n // gestures the recorder adds around each ask: about a second a beat\n aboutS: Math.round((total + beats.length * 1000) / 1000),\n setup: actions.setup?.length ?? 0,\n }\n}\n\n/** The list as the person reads it. */\nexport function formatShotList(list: ShotList): string {\n const out: string[] = []\n out.push(\n `Record this with the vosso extension in your own signed-in browser, about ${list.aboutS} seconds${list.viewport ? `, window about ${list.viewport.width}×${list.viewport.height}` : ''}.`,\n )\n if (list.url)\n out.push(`Start on ${list.url}, signed in, with the page settled.`)\n if (list.setup)\n out.push(\n `Before you press record: the script's ${list.setup} setup step${list.setup === 1 ? '' : 's'} (sign in, dismiss banners) are yours to do first; they are not part of the take.`,\n )\n out.push('')\n for (const b of list.beats) {\n const hold =\n b.holdS && b.holdS >= 0.4 ? ` Hold ${s1(b.holdS * 1000)} s.` : ''\n out.push(`${b.n}. ${b.line}.${hold}`)\n }\n out.push('')\n out.push(\n 'Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf.',\n )\n return out.join('\\n')\n}\n"],"mappings":";;;;;;AAkCO,SAAS,UAAU,UAA0B;AAClD,QAAM,OAAO,wBAAwB,KAAK,QAAQ;AAClD,MAAI,KAAM,QAAO,IAAI,KAAK,CAAC,CAAC;AAC5B,QAAM,MAAM,6BAA6B,KAAK,QAAQ;AACtD,MAAI,IAAK,QAAO,IAAI,IAAI,CAAC,CAAC;AAC1B,QAAM,OAAO,yDAAyD;AAAA,IACpE;AAAA,EACF;AACA,MAAI,KAAM,QAAO,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAC3C,QAAM,QAAQ,+BAA+B,KAAK,QAAQ;AAC1D,MAAI,MAAO,QAAO,IAAI,MAAM,CAAC,CAAC;AAC9B,QAAM,KAAK,gCAAgC,KAAK,QAAQ;AACxD,MAAI,GAAI,QAAO,QAAQ,GAAG,CAAC,CAAC;AAC5B,SAAO,KAAK,QAAQ;AACtB;AAEA,IAAM,KAAK,CAAC,OAAe,GAAG,KAAK,MAAM,KAAK,GAAG,IAAI,EAAE,GAAG,QAAQ,QAAQ,EAAE;AAGrE,SAAS,MAAM,IAAuC;AAC3D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,KAAK,GAAG,SAAS,EAAG,QAAO;AACrD,QAAM,QAAQ,GACX,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,UAAU,GAAG,EACrB,YAAY,EACZ,KAAK;AACR,SAAO,MAAM,UAAU,IAAI,QAAQ;AACrC;AAGA,SAAS,cAAc,MAAiD;AACtE,QAAM,aAAa,UAAU,KAAK,QAAQ;AAC1C,MAAI,CAAC,WAAW,WAAW,GAAG,EAAG,QAAO;AACxC,QAAM,OAAO,MAAM,KAAK,EAAE;AAC1B,SAAO,OAAO,GAAG,IAAI,KAAK,UAAU,MAAM;AAC5C;AAEA,SAAS,OAAO,MAAiC;AAC/C,QAAM,MAAM,KAAK;AACjB,QAAM,QAAQ,MAAM,GAAG,GAAG,OAAO;AACjC,UAAQ,KAAK,IAAI;AAAA,IACf,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,GAAG,KAAK,uBAAuB,cAAc,IAAI,CAAC;AAAA,IAC3D,KAAK;AACH,aAAO,GAAG,KAAK,SAAS,cAAc,IAAI,CAAC;AAAA,IAC7C,KAAK;AACH,aAAO,GAAG,KAAK,SAAS,KAAK,IAAI,UAAU,cAAc,IAAI,CAAC;AAAA,IAChE,KAAK;AACH,aAAO,GAAG,KAAK,UAAU,KAAK,KAAK,IAAI,OAAO,MAAM,IAAI,KAAK,KAAK,IAAI,aAAa,UAAU;AAAA,IAC/F,KAAK;AACH,aAAO,GAAG,KAAK,2BAA2B,KAAK,IAAI,MAAM,UAAU,OAAO,IAAI,KAAK,IAAI,MAAM,SAAS,OAAO;AAAA,IAC/G,KAAK;AACH,aAAO,GAAG,KAAK,QAAQ,KAAK,WAAW,cAAc,IAAyC,IAAI,sBAAsB;AAAA,EAC5H;AACF;AAEO,SAAS,SAAS,SAAgC;AACvD,QAAM,QAAoB,CAAC;AAC3B,MAAI,cAAc;AAClB,MAAI,QAAQ;AACZ,aAAW,QAAQ,QAAQ,OAAO;AAChC,aAAS,QAAQ,IAAI;AACrB,QAAI,KAAK,OAAO,QAAQ;AAGtB,UAAI,MAAM,QAAQ;AAChB,cAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,aAAK,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK;AAAA,MAC7C,MAAO,gBAAe,KAAK;AAC3B;AAAA,IACF;AACA,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,CAAC,KAAM;AACX,UAAM,OAAiB,EAAE,GAAG,MAAM,SAAS,GAAG,KAAK;AACnD,QAAI,KAAK,OAAO,QAAS,MAAK,QAAQ,QAAQ,IAAI,IAAI;AACtD,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,OAAK;AACL,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB;AAAA;AAAA,IAEA,QAAQ,KAAK,OAAO,QAAQ,MAAM,SAAS,OAAQ,GAAI;AAAA,IACvD,OAAO,QAAQ,OAAO,UAAU;AAAA,EAClC;AACF;AAGO,SAAS,eAAe,MAAwB;AACrD,QAAM,MAAgB,CAAC;AACvB,MAAI;AAAA,IACF,6EAA6E,KAAK,MAAM,WAAW,KAAK,WAAW,kBAAkB,KAAK,SAAS,KAAK,OAAI,KAAK,SAAS,MAAM,KAAK,EAAE;AAAA,EACzL;AACA,MAAI,KAAK;AACP,QAAI,KAAK,YAAY,KAAK,GAAG,qCAAqC;AACpE,MAAI,KAAK;AACP,QAAI;AAAA,MACF,yCAAyC,KAAK,KAAK,cAAc,KAAK,UAAU,IAAI,KAAK,GAAG;AAAA,IAC9F;AACF,MAAI,KAAK,EAAE;AACX,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,OACJ,EAAE,SAAS,EAAE,SAAS,MAAM,SAAS,GAAG,EAAE,QAAQ,GAAI,CAAC,QAAQ;AACjE,QAAI,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,EAAE;AAAA,EACtC;AACA,MAAI,KAAK,EAAE;AACX,MAAI;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,KAAK,IAAI;AACtB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "The vos CLI: record the real product from a scripted browser flow, auto-zoom from the cursor track, cut as data in doc.json, render deterministic video and stills, deliver a release's media per destination spec, and sync with vos.so. One binary, every verb, MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@vosjs/render-core": "^0.2.8",
|
|
55
55
|
"@vosjs/shared": "^0.4.1",
|
|
56
56
|
"@vosjs/studio-core": "^0.29.0",
|
|
57
|
-
"@vosjs/
|
|
58
|
-
"@vosjs/
|
|
57
|
+
"@vosjs/tween": "^0.8.2",
|
|
58
|
+
"@vosjs/timeline": "^0.4.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^22",
|