@vosjs/cli 0.48.0 → 0.49.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/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-CYHH56KA.js");
514
+ const { run } = await import("./run-EQZXEPXZ.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-CYHH56KA.js");
554
+ const { verbHelp } = await import("./run-EQZXEPXZ.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-CYHH56KA.js");
586
+ const { HELP } = await import("./run-EQZXEPXZ.js");
587
587
  const documented = [
588
588
  ...helpFlagsFor(HELP_ENGINE, verb),
589
589
  ...helpFlagsFor(HELP, verb)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { Browser, BrowserContext, Page } from 'playwright';
2
- import { CursorTrack, RecordingMeta, ProjectDoc, CursorEvent, TemplateAnchor, Backdrop, LayoutParts, TranscriptSegment, Digest } from '@vosjs/studio-core';
2
+ import * as _vosjs_render_core_record from '@vosjs/render-core/record';
3
+ import { ActionsFile, RecordOpts as RecordOpts$1 } from '@vosjs/render-core/record';
4
+ export { ActionStep, ActionsFile, validateActions } from '@vosjs/render-core/record';
5
+ import { CursorTrack, RecordingMeta, ProjectDoc, TemplateAnchor, Backdrop, LayoutParts, TranscriptSegment, Digest } from '@vosjs/studio-core';
3
6
  export { Digest } from '@vosjs/studio-core';
4
7
 
5
8
  interface RenderCommonOptions {
@@ -94,217 +97,6 @@ interface PluginManifest {
94
97
  }
95
98
  declare const manifest: PluginManifest;
96
99
 
97
- interface PaceReport {
98
- askedMs: number;
99
- gestureMs: number;
100
- wallMs: number;
101
- /** Wall time neither asked nor a gesture: what the page and the round trips cost. */
102
- overheadMs: number;
103
- overheadPct: number;
104
- /** The steps whose wall ran past their ask plus gesture by more than a third. */
105
- slow: {
106
- step: number;
107
- do: string;
108
- askedMs: number;
109
- wallMs: number;
110
- }[];
111
- }
112
- interface DeadStep {
113
- step: number;
114
- id?: string;
115
- do: string;
116
- /** ms from the step's act (its press, else its start) to the last frame change in the step. */
117
- settledMs: number;
118
- /** ms the settled frame was held before the step ended. */
119
- heldMs: number;
120
- /** the hold past the reading beat, while the cursor was parked; 0 when the hold was read or the pointer moved. */
121
- deadMs: number;
122
- }
123
- interface DeadReport {
124
- ms: number;
125
- pct: number;
126
- /** the steps that carried any dead time, in order. */
127
- steps: DeadStep[];
128
- /** the longest single dead hold. */
129
- longestMs: number;
130
- }
131
-
132
- /**
133
- * What a signed-in take SHOWS. Getting past the login is half the problem;
134
- * the other half is that the account's own data is now in the frame: an
135
- * email address in the header, a key on a settings page, a card number.
136
- * Asking nicely does not hold this line (a person asked to sign in with a
137
- * demo account signs in as themselves, because it is the account they have),
138
- * so the recorder looks.
139
- *
140
- * Two pieces, both run IN the page:
141
- *
142
- * - the EXPOSURE scan reads the text that is visible in the viewport after
143
- * each step and reports the KIND of thing it saw and where. Never the
144
- * string itself: a report that quotes the secret is a second leak.
145
- * - a MASK hides a selector before the first frame is captured and keeps it
146
- * hidden across navigations and re-renders, so the real value is never in
147
- * a frame, never in the recording, never pushed. `as: 'text'` swaps the
148
- * words (a product reads better than a redaction); `as: 'blur'` blurs.
149
- *
150
- * The matchers are ONE plain-JavaScript source string, evaluated in the page
151
- * and, by the tests, in Node: a function serialized out of a bundle picks up
152
- * the bundler's `__name` helper and dies in the page, and two copies of a
153
- * regex drift.
154
- */
155
-
156
- interface MaskRule {
157
- selector: string;
158
- /** `blur` (default) blurs the element; `text` swaps its words for `text`. */
159
- as?: 'blur' | 'text';
160
- text?: string;
161
- }
162
-
163
- /**
164
- * `setup`: the steps that run BEFORE the camera rolls. A sign-in form, a
165
- * cookie banner, the "choose your editor" modal, an onboarding tour: things
166
- * a take must get past and must not show. They run after the first
167
- * navigation and before `Page.startScreencast`, with no cursor synthesis,
168
- * no pace accounting and nothing written to `meta.steps`. Then the recorder
169
- * navigates to `url` again and the take begins where the setup left it.
170
- *
171
- * A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`: resolved at run
172
- * time, never logged, never stored. The guarantee is narrow and real: the
173
- * secret is never in a file that travels (`actions.json` is committed and
174
- * pushed with the take; `meta.json` is read by the digest) and never in the
175
- * footage. An agent with a shell can still read the variable; that is the
176
- * maker's decision to make, not this file's to hide.
177
- */
178
-
179
- type SetupText = string | {
180
- env: string;
181
- };
182
- type SetupStep = ({
183
- do: 'wait';
184
- ms: number;
185
- } | {
186
- do: 'click';
187
- selector: string;
188
- ms?: number;
189
- } | {
190
- do: 'type';
191
- selector: string;
192
- text: SetupText;
193
- ms?: number;
194
- } | {
195
- do: 'press';
196
- key: string;
197
- ms?: number;
198
- } | {
199
- do: 'goto';
200
- url: string;
201
- }) & {
202
- id?: string;
203
- };
204
-
205
- /**
206
- * The action script — the declarative recipe an agent (or human) writes to
207
- * drive a take. Small on purpose: selectors + a handful of verbs. The recorder
208
- * executes it with humanized cursor motion and synthesizes the CursorTrack
209
- * from its own dispatches.
210
- */
211
- interface ActionsFile {
212
- /** Page to record. `--url` overrides. */
213
- url?: string;
214
- /** Recording viewport in CSS px (default 1280x720). */
215
- viewport?: {
216
- width: number;
217
- height: number;
218
- };
219
- /**
220
- * Hidden BEFORE the first frame is captured and kept hidden across
221
- * navigations: what a signed-in account shows that must not ship. `blur`
222
- * (default) blurs the element; `text` swaps its words for `text`, which is
223
- * for IDENTIFIERS (an email, a name, an account id), never for product
224
- * copy or numbers: the video must stay true to the product.
225
- */
226
- mask?: MaskRule[];
227
- /**
228
- * Run BEFORE the camera rolls, after the first navigation: a sign-in form,
229
- * a cookie banner, an onboarding tour. Plain actions with no cursor, no
230
- * frames, no pace and nothing in `meta.steps`; then the recorder opens
231
- * `url` again and the take begins where the setup left it. A `type`
232
- * step's text may be `{ env: 'NAME' }`, read at run time and never logged
233
- * or stored.
234
- */
235
- setup?: SetupStep[];
236
- steps: ActionStep[];
237
- }
238
- type ActionStep = ({
239
- do: 'wait';
240
- ms: number;
241
- } | {
242
- do: 'hover';
243
- selector: string;
244
- ms?: number;
245
- }
246
- /** `ms` is the settle after the press (default 150). */
247
- | {
248
- do: 'click';
249
- selector: string;
250
- ms?: number;
251
- }
252
- /**
253
- * Type into `selector`. The recorder clicks the field first, which is what
254
- * opens the typing zoom on it; `focus: false` types into the field as it is
255
- * already focused, for a keystroke that follows earlier typing (a submitting
256
- * Enter) rather than starting it — a second click there rings a click effect
257
- * on empty space beside the text.
258
- */
259
- | {
260
- do: 'type';
261
- selector: string;
262
- text: string;
263
- delayMs?: number;
264
- focus?: boolean;
265
- /** the settle after the last character (default 150) */
266
- ms?: number;
267
- }
268
- /** `ms` is the settle after the scroll lands (default 200). */
269
- | {
270
- do: 'scroll';
271
- dy: number;
272
- ms?: number;
273
- } | {
274
- do: 'move';
275
- x: number;
276
- y: number;
277
- }
278
- /**
279
- * Press-move-release — real edits (drag an element on the stage canvas,
280
- * slide a range input, move a timeline clip). Start = the selector's center
281
- * when given, else (x, y); end = (tx, ty); eased over ms (default 700).
282
- */
283
- | {
284
- do: 'drag';
285
- selector?: string;
286
- x?: number;
287
- y?: number;
288
- tx: number;
289
- ty: number;
290
- ms?: number;
291
- }) & {
292
- /**
293
- * Optional stable identity: anchors in doc.json name a step by this
294
- * id (else by index), so a step can move or gain neighbours across script
295
- * edits without breaking the cut anchored to it. Unique when present.
296
- */
297
- id?: string;
298
- /**
299
- * A beat's caption, two to eight words: `vos deliver` lands it as a
300
- * lower-third at this step's moment on the cuts that take words, and
301
- * `vos actions script` leads the beat with it.
302
- */
303
- caption?: string;
304
- };
305
- /** Structural validation with actionable messages. Returns [] when valid. */
306
- declare function validateActions(value: unknown): string[];
307
-
308
100
  /** The name `record` writes its footage under — its encoder emits WebM. */
309
101
  declare const RECORDING_NAME = "recording.webm";
310
102
  interface TakePaths {
@@ -330,143 +122,14 @@ interface TakeData {
330
122
  /** Load a take directory; cursor+meta are required, doc/actions optional. */
331
123
  declare function loadTake(dir: string): Promise<TakeData>;
332
124
 
333
- /**
334
- * The wall check: did the recorder land where it was asked, or in front of
335
- * a sign-in? A take of a product behind a login, recorded without a session,
336
- * used to succeed: the footage was the login page (or wherever the site
337
- * sends a stranger), and the only symptom was a skipped selector. This says
338
- * it in words, before a frame is captured and before a re-record clears the
339
- * footage it would have replaced.
340
- *
341
- * Pure: the recorder gathers the evidence, this decides. A heuristic is
342
- * acceptable because both failure directions are cheap: a false refusal
343
- * costs one flag (`--allow-wall`), a false pass costs what every such take
344
- * cost before.
345
- */
346
- /** What the recorder saw once the first navigation settled. */
347
- interface Arrival {
348
- askedUrl: string;
349
- landedUrl: string;
350
- /** HTTP status of the main document, when the navigation produced one. */
351
- status?: number;
352
- /** `input[type=password]` fields that are visible on the page. */
353
- passwordFields: number;
354
- /** A password field marked `autocomplete="new-password"` (a settings or sign-up form). */
355
- newPasswordField: boolean;
356
- /** A visible one-time-code field (`autocomplete="one-time-code"`). */
357
- oneTimeCodeField: boolean;
358
- }
359
- type WallKind =
360
- /** the asked URL answered 401 or 403 */
361
- 'status'
362
- /** landed on an identity provider's host */
363
- | 'idp'
364
- /** landed on a sign-in form or a sign-in path */
365
- | 'signin'
366
- /** sent somewhere else, with no sign-in in sight (a site that shows strangers a public page) */
367
- | 'redirect';
368
- interface WallVerdict {
369
- kind: WallKind;
370
- /**
371
- * `hard` is refused always; `soft` is refused under `--strict` and said as
372
- * a warning otherwise, because a redirect alone is not proof of a wall.
373
- */
374
- level: 'hard' | 'soft';
375
- /** origin + path, never the query or hash (they can carry tokens). */
376
- asked: string;
377
- landed: string;
378
- message: string;
379
- }
380
-
381
- interface FrameRec {
382
- file: string;
383
- /** ms since t0 */
384
- tMs: number;
385
- }
386
- interface SkippedStep {
387
- /** index into actions.steps */
388
- step: number;
389
- do: string;
390
- selector: string;
391
- }
392
- /** A stretch with no screencast frames — the page pixels did not change. */
393
- interface FreezeSpan {
394
- /** seconds into the take */
395
- from: number;
396
- to: number;
397
- ms: number;
398
- }
399
- interface RecordResult {
400
- events: CursorEvent[];
401
- frames: FrameRec[];
402
- meta: RecordingMeta;
403
- /** The take's pace: what the script asked, what the gestures added, what the page cost. */
404
- pace: PaceReport;
405
- /** steps whose selector never became visible — the take continued without them. */
406
- skipped: SkippedStep[];
407
- /** the initial goto never reached networkidle (recording proceeded anyway). */
408
- navTimeout: boolean;
409
- /** A wall the caller let through; null when the recorder landed where it was asked. */
410
- wall: WallVerdict | null;
411
- /** Sensitive-looking text seen in the frame (kind + place, never the string). */
412
- exposures: NonNullable<RecordingMeta['exposures']>;
413
- /** The script's masks with how many elements each reached; hits 0 hid nothing. */
414
- masks: NonNullable<RecordingMeta['masks']>;
415
- /**
416
- * Smoothness telemetry: stretches ≥400ms with no visual change. Frozen
417
- * footage is the #1 enemy of a smooth product video — either the flow should
418
- * keep motion in frame (animate, hover a preview, scroll) or the doc should
419
- * trim/speed through these. freezePct = share of the take that is frozen.
420
- */
421
- freezes: FreezeSpan[];
422
- freezePct: number;
423
- /**
424
- * The smoothness WARNING: still footage under a parked cursor past the
425
- * beat it takes to read what changed, per step (`deadTime`). A still page
426
- * being read is content; this is the part of a hold nobody is reading.
427
- */
428
- dead: DeadReport;
429
- /** The take reached --max-duration and stopped there; later steps did not run. */
430
- capped: boolean;
431
- }
432
- interface RecordOpts {
433
- /** Stop the capture at this many seconds (the hosted cap). */
434
- maxDurationSeconds?: number;
435
- /**
436
- * Path to a Playwright storage state (cookies + origin storage), so the
437
- * recorder drives a SIGNED-IN product. A demo of anything behind a login
438
- * needs it, and a sign-in form cannot always be scripted (an emailed code,
439
- * an SSO hop). Export one from a real browser, or with
440
- * `context.storageState({ path })`.
441
- */
442
- storageState?: string;
443
- /**
444
- * REHEARSE the flow: every step runs against the real page, in order,
445
- * because a later selector usually exists only after an earlier click. But
446
- * nothing is captured (no screencast, no frames), nothing is written, the
447
- * pointer lands instead of travelling and every pause is cut to a beat, so
448
- * a script that misses a selector says so in seconds instead of after a
449
- * full real-time take and its encode. Selector lookups keep their whole
450
- * timeout, so a miss here is a miss in the take.
451
- */
452
- dryRun?: boolean;
453
- /**
454
- * Called once the first navigation has settled, BEFORE a frame is captured.
455
- * The caller judges the wall here and only then prepares the take
456
- * directory, so a take refused at a sign-in never clears the footage a
457
- * re-record would have replaced. A throw closes the context and propagates.
458
- */
459
- onArrival?: (arrival: Arrival) => Promise<WallVerdict | null | void> | WallVerdict | null | void;
460
- /** Extra request headers on every request (`--header name=value`). */
461
- headers?: Record<string, string>;
125
+ type RecordOpts = Omit<RecordOpts$1, 'context' | 'env' | 'platform'> & {
462
126
  /**
463
127
  * A ready-made context to record IN, instead of one made from `browser`:
464
- * `--session <name>`, a persistent profile the person signed in to. The
465
- * recorder still sizes the viewport and closes it at the end.
128
+ * `--session <name>`, a persistent profile the person signed in to.
466
129
  */
467
130
  context?: BrowserContext;
468
- }
469
- declare function recordTake(browser: Browser, url: string, actions: ActionsFile, paths: TakePaths, log: (msg: string) => void, opts?: RecordOpts): Promise<RecordResult>;
131
+ };
132
+ declare function recordTake(browser: Browser, url: string, actions: ActionsFile, paths: TakePaths, log: (msg: string) => void, opts?: RecordOpts): Promise<_vosjs_render_core_record.RecordResult>;
470
133
 
471
134
  declare function encodeRecording(browser: Browser, takeDir: string, onProgress: (fraction: number) => void): Promise<{
472
135
  bytes: number;
@@ -869,4 +532,4 @@ interface VersionChange {
869
532
  summary?: string;
870
533
  }
871
534
 
872
- export { type ActionStep, type ActionsFile, type AgentBrowserRecord, BrowserUnavailableError, type ConvertOptions, type ConvertResult, type DigestOptions, type DigestResult, type LoadedConfig, RECORDING_NAME, type RenderAnimationOptions, type RenderAnimationResult, type RenderResult, type RenderStillOptions, type RenderTakeOptions, type RenderTakeResult, type RenderVideoOptions, type SyncState, type TakeData, type TakePaths, type TakeServer, type VersionChange, apiError, apiJson, configDuration, convertAgentBrowser, digestTake, encodeRecording, launchBrowser, loadTake, loadVosConfig, manifest, parseAgentBrowserLog, parseTranscript, parseVosId, planTake, platformOrigin, previewPages, pullMedia, readSyncState, recordTake, renderAnimation, renderStill, renderTake, renderVideo, resolveCredential, run, splitCommand, startTakeServer, takePaths, validateActions, waitForPageDone, writeSyncState };
535
+ export { type AgentBrowserRecord, BrowserUnavailableError, type ConvertOptions, type ConvertResult, type DigestOptions, type DigestResult, type LoadedConfig, RECORDING_NAME, type RenderAnimationOptions, type RenderAnimationResult, type RenderResult, type RenderStillOptions, type RenderTakeOptions, type RenderTakeResult, type RenderVideoOptions, type SyncState, type TakeData, type TakePaths, type TakeServer, type VersionChange, apiError, apiJson, configDuration, convertAgentBrowser, digestTake, encodeRecording, launchBrowser, loadTake, loadVosConfig, manifest, parseAgentBrowserLog, parseTranscript, parseVosId, planTake, platformOrigin, previewPages, pullMedia, readSyncState, recordTake, renderAnimation, renderStill, renderTake, renderVideo, resolveCredential, run, splitCommand, startTakeServer, takePaths, waitForPageDone, writeSyncState };
package/dist/index.js CHANGED
@@ -30,14 +30,14 @@ import {
30
30
  validateActions,
31
31
  waitForPageDone,
32
32
  writeSyncState
33
- } from "./chunk-ANKECV7F.js";
33
+ } from "./chunk-TAWRT7UM.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-AUPGJJTL.js";
40
+ import "./chunk-72ADTMWW.js";
41
41
  import {
42
42
  manifest
43
43
  } from "./chunk-P3X5KVA6.js";
@@ -5,9 +5,9 @@ import {
5
5
  run,
6
6
  takeBrowserArgs,
7
7
  verbHelp
8
- } from "./chunk-ANKECV7F.js";
8
+ } from "./chunk-TAWRT7UM.js";
9
9
  import "./chunk-FACYY7VV.js";
10
- import "./chunk-AUPGJJTL.js";
10
+ import "./chunk-72ADTMWW.js";
11
11
  export {
12
12
  HELP,
13
13
  MULTI_FLAGS,
@@ -15,4 +15,4 @@ export {
15
15
  takeBrowserArgs,
16
16
  verbHelp
17
17
  };
18
- //# sourceMappingURL=run-CYHH56KA.js.map
18
+ //# sourceMappingURL=run-EQZXEPXZ.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  askedMs
4
- } from "./chunk-AUPGJJTL.js";
4
+ } from "./chunk-72ADTMWW.js";
5
5
 
6
6
  // src/plugin/shotList.ts
7
7
  function sayTarget(selector) {
@@ -108,4 +108,4 @@ export {
108
108
  sayTarget,
109
109
  shotList
110
110
  };
111
- //# sourceMappingURL=shotList-7F2FE3RU.js.map
111
+ //# sourceMappingURL=shotList-IYFDHSIK.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vosjs/cli",
3
- "version": "0.48.0",
3
+ "version": "0.49.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",
@@ -49,14 +49,14 @@
49
49
  "dependencies": {
50
50
  "mediabunny": "^1.55.7",
51
51
  "playwright": "^1.49.0",
52
- "@vosjs/core": "^0.25.0",
53
52
  "@vosjs/editor": "^1.3.1",
53
+ "@vosjs/core": "^0.25.0",
54
+ "@vosjs/render-core": "^0.3.0",
54
55
  "@vosjs/elements": "^0.8.1",
55
- "@vosjs/render-core": "^0.2.9",
56
56
  "@vosjs/shared": "^0.4.1",
57
57
  "@vosjs/studio-core": "^0.30.3",
58
- "@vosjs/tween": "^0.8.2",
59
- "@vosjs/timeline": "^0.4.1"
58
+ "@vosjs/timeline": "^0.4.1",
59
+ "@vosjs/tween": "^0.8.2"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^22",