@vosjs/cli 0.32.0 → 0.33.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 +2 -0
- package/dist/{chunk-QJORXTJM.js → chunk-6O55OD6D.js} +446 -105
- package/dist/chunk-6O55OD6D.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-PYK3XJNH.js → run-X6XJCMIZ.js} +2 -2
- package/package.json +3 -3
- package/dist/chunk-QJORXTJM.js.map +0 -1
- /package/dist/{run-PYK3XJNH.js.map → run-X6XJCMIZ.js.map} +0 -0
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
} from "./chunk-NHKHTIDR.js";
|
|
7
7
|
|
|
8
8
|
// src/plugin/run.ts
|
|
9
|
-
import { mkdir as mkdir9, readFile as
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
9
|
+
import { mkdir as mkdir9, readFile as readFile15, rm as rm4 } from "fs/promises";
|
|
10
|
+
import { existsSync as existsSync15 } from "fs";
|
|
11
|
+
import { join as join18, resolve as resolve9 } from "path";
|
|
12
12
|
import {
|
|
13
13
|
END_CARD_FROM as END_CARD_FROM2,
|
|
14
14
|
docOutputDuration as docOutputDuration3,
|
|
@@ -214,6 +214,7 @@ import {
|
|
|
214
214
|
findStep,
|
|
215
215
|
outputEnd,
|
|
216
216
|
pinBox,
|
|
217
|
+
pinCandidates,
|
|
217
218
|
pinRectOnScreen,
|
|
218
219
|
pinReferent,
|
|
219
220
|
ratedSegments,
|
|
@@ -1611,6 +1612,17 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1611
1612
|
const tf = isObj(o.transform) ? o.transform : null;
|
|
1612
1613
|
if (!tf || !isNum(tf.x) || !isNum(tf.y)) return;
|
|
1613
1614
|
const pinned = pins.get(String(o.id));
|
|
1615
|
+
if (!pinned && o.pin === void 0 && (o.kind === "html" || o.kind === "image" || o.kind === "video")) {
|
|
1616
|
+
const cands = pinCandidates(docIn, {
|
|
1617
|
+
start: o.start,
|
|
1618
|
+
duration: o.duration
|
|
1619
|
+
});
|
|
1620
|
+
if (cands.length) {
|
|
1621
|
+
warnings.push(
|
|
1622
|
+
`overlays[${i}] is not pinned, and ${cands.length === 1 ? "a step with an element sits" : "steps with an element sit"} inside its window: ${cands.map((c) => `${String(c.step)} (${c.do}${c.selector ? ` ${c.selector}` : ""})`).join(", ")} \u2014 a callout about one of them says so: pin: { step: "${String(cands[0].step)}" }`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1614
1626
|
if (pinned) {
|
|
1615
1627
|
if (pinned.clamped > 4) {
|
|
1616
1628
|
warnings.push(
|
|
@@ -5294,10 +5306,208 @@ function winRate(verdicts) {
|
|
|
5294
5306
|
return { wins, ties, judged, rate: judged ? (wins + ties / 2) / judged : null };
|
|
5295
5307
|
}
|
|
5296
5308
|
|
|
5309
|
+
// src/plugin/callout.ts
|
|
5310
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5311
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5312
|
+
import { join as join10 } from "path";
|
|
5313
|
+
import { lerpArray, sample } from "@vosjs/timeline";
|
|
5314
|
+
import {
|
|
5315
|
+
CALLOUT_DEFAULT_BODY_PX,
|
|
5316
|
+
CALLOUT_SHAPES,
|
|
5317
|
+
calloutClip,
|
|
5318
|
+
calloutGroundOf,
|
|
5319
|
+
cameraModel as cameraModel2,
|
|
5320
|
+
deltaE,
|
|
5321
|
+
docCardLayout as docCardLayout2,
|
|
5322
|
+
docZoomTrack as docZoomTrack2,
|
|
5323
|
+
hexToRgb,
|
|
5324
|
+
parseCssColour,
|
|
5325
|
+
pinBox as pinBox2,
|
|
5326
|
+
ratedSegments as ratedSegments5,
|
|
5327
|
+
resolvePins as resolvePins2,
|
|
5328
|
+
spanOutputExtent as spanOutputExtent5
|
|
5329
|
+
} from "@vosjs/studio-core";
|
|
5330
|
+
var CALLOUT_DEFAULT_SECONDS = 3;
|
|
5331
|
+
var CALLOUT_LEAD_SECONDS = 0.2;
|
|
5332
|
+
var CALLOUT_DELTA_E_PROBLEM = 8;
|
|
5333
|
+
var CALLOUT_DELTA_E_WARNING = 16;
|
|
5334
|
+
function registerFrom(roles, flags) {
|
|
5335
|
+
const ground = flags.ground ?? roles?.bgA;
|
|
5336
|
+
const accent = flags.accent ?? roles?.accent;
|
|
5337
|
+
if (!ground || !accent) return null;
|
|
5338
|
+
if (!hexToRgb(ground) || !hexToRgb(accent)) return null;
|
|
5339
|
+
const face = flags.face ?? roles?.fontBody;
|
|
5340
|
+
return {
|
|
5341
|
+
ground,
|
|
5342
|
+
accent,
|
|
5343
|
+
...face && !/^ui-sans-serif$|^system-ui$/.test(face) ? { face } : {},
|
|
5344
|
+
...flags.mono ? { mono: flags.mono } : {},
|
|
5345
|
+
body: flags.body ?? CALLOUT_DEFAULT_BODY_PX
|
|
5346
|
+
};
|
|
5347
|
+
}
|
|
5348
|
+
function appScaleAt(doc, t) {
|
|
5349
|
+
const layout = docCardLayout2(doc);
|
|
5350
|
+
const meta = doc.source.meta;
|
|
5351
|
+
const base = layout.dw / Math.max(1, meta.width);
|
|
5352
|
+
const track = docZoomTrack2(doc);
|
|
5353
|
+
if (!track || !track.keyframes.length) return base;
|
|
5354
|
+
const z = sample(track, t, lerpArray);
|
|
5355
|
+
return base * Math.max(1, z[0]);
|
|
5356
|
+
}
|
|
5357
|
+
function windowForStep(doc, step, seconds = CALLOUT_DEFAULT_SECONDS) {
|
|
5358
|
+
const steps = doc.source.meta.steps ?? [];
|
|
5359
|
+
const s = steps.find((x) => x.id !== void 0 && x.id === step) ?? steps.find((x) => String(x.step) === String(step));
|
|
5360
|
+
if (!s || s.skipped) return null;
|
|
5361
|
+
const rated = ratedSegments5(doc);
|
|
5362
|
+
const ext = spanOutputExtent5(rated, s.tStart, s.tEnd);
|
|
5363
|
+
if (!ext) return null;
|
|
5364
|
+
const at2 = s.do === "click" || s.do === "drag" ? ext.start : ext.end;
|
|
5365
|
+
const start = +(at2 + CALLOUT_LEAD_SECONDS).toFixed(3);
|
|
5366
|
+
let end = start + seconds;
|
|
5367
|
+
for (const n of steps) {
|
|
5368
|
+
if (n.tStart <= s.tStart) continue;
|
|
5369
|
+
if (n.do !== "scroll" && !n.navigated) continue;
|
|
5370
|
+
const ne = spanOutputExtent5(rated, n.tStart, n.tEnd);
|
|
5371
|
+
if (ne && ne.start > start && ne.start < end) end = ne.start - 0.05;
|
|
5372
|
+
}
|
|
5373
|
+
return { start, duration: +Math.max(0.5, end - start).toFixed(3) };
|
|
5374
|
+
}
|
|
5375
|
+
function composeCallout(doc, register, ask) {
|
|
5376
|
+
let window2 = null;
|
|
5377
|
+
if (ask.step !== void 0) {
|
|
5378
|
+
window2 = windowForStep(doc, ask.step, ask.seconds);
|
|
5379
|
+
if (!window2)
|
|
5380
|
+
throw new Error(
|
|
5381
|
+
`step "${String(ask.step)}" is not in the take's step timeline, was skipped, or falls outside the cut`
|
|
5382
|
+
);
|
|
5383
|
+
} else if (ask.at !== void 0) {
|
|
5384
|
+
window2 = {
|
|
5385
|
+
start: ask.at,
|
|
5386
|
+
duration: ask.seconds ?? CALLOUT_DEFAULT_SECONDS
|
|
5387
|
+
};
|
|
5388
|
+
} else {
|
|
5389
|
+
throw new Error(
|
|
5390
|
+
"name the step the callout is about (--step <id>), or --at <output seconds>"
|
|
5391
|
+
);
|
|
5392
|
+
}
|
|
5393
|
+
const pin = ask.step !== void 0 ? { step: ask.step } : ask.press !== void 0 ? { press: ask.press } : void 0;
|
|
5394
|
+
if (pin) {
|
|
5395
|
+
if (ask.side && ask.side !== "auto") pin.side = ask.side;
|
|
5396
|
+
if (ask.mark && ask.mark !== "none") pin.mark = ask.mark;
|
|
5397
|
+
if (ask.leader) pin.leader = true;
|
|
5398
|
+
if (ask.color) pin.color = ask.color;
|
|
5399
|
+
}
|
|
5400
|
+
const taken = new Set((doc.overlays ?? []).map((o) => o.id));
|
|
5401
|
+
let id = ask.id ?? `${ask.shape}-${ask.step ?? Math.round(window2.start * 10)}`;
|
|
5402
|
+
let n = 2;
|
|
5403
|
+
while (taken.has(id) && !ask.id)
|
|
5404
|
+
id = `${ask.shape}-${ask.step ?? Math.round(window2.start * 10)}-${n++}`;
|
|
5405
|
+
const clip = calloutClip(ask.shape, register, ask.words, {
|
|
5406
|
+
id,
|
|
5407
|
+
start: window2.start,
|
|
5408
|
+
duration: window2.duration,
|
|
5409
|
+
scale: appScaleAt(doc, window2.start + 0.35),
|
|
5410
|
+
pin,
|
|
5411
|
+
// The fallback place, in the margin: the lower third.
|
|
5412
|
+
at: { x: 0.5, y: 0.82 }
|
|
5413
|
+
});
|
|
5414
|
+
return { clip, window: window2 };
|
|
5415
|
+
}
|
|
5416
|
+
function isCalloutShape(s) {
|
|
5417
|
+
return CALLOUT_SHAPES.includes(s);
|
|
5418
|
+
}
|
|
5419
|
+
async function groundFindings(doc, frameAt) {
|
|
5420
|
+
const out = [];
|
|
5421
|
+
const overlays = doc.overlays ?? [];
|
|
5422
|
+
const layout = docCardLayout2(doc);
|
|
5423
|
+
const camera = cameraModel2(doc.frame);
|
|
5424
|
+
const track = docZoomTrack2(doc);
|
|
5425
|
+
const pins = resolvePins2(doc, layout, camera, track);
|
|
5426
|
+
for (let i = 0; i < overlays.length; i++) {
|
|
5427
|
+
const o = overlays[i];
|
|
5428
|
+
if (o.kind !== "html") continue;
|
|
5429
|
+
const card = calloutGroundOf(o.css ?? "");
|
|
5430
|
+
if (!card) {
|
|
5431
|
+
out.push({
|
|
5432
|
+
index: i,
|
|
5433
|
+
id: o.id,
|
|
5434
|
+
level: "note",
|
|
5435
|
+
deltaE: null,
|
|
5436
|
+
card: null,
|
|
5437
|
+
footage: null,
|
|
5438
|
+
message: `overlays[${i}] declares no readable background on its root rule \u2014 the ground check cannot read it`
|
|
5439
|
+
});
|
|
5440
|
+
continue;
|
|
5441
|
+
}
|
|
5442
|
+
const t = o.start + Math.min(0.35, o.duration / 2);
|
|
5443
|
+
const img = await frameAt(t);
|
|
5444
|
+
if (!img) {
|
|
5445
|
+
out.push({
|
|
5446
|
+
index: i,
|
|
5447
|
+
id: o.id,
|
|
5448
|
+
level: "note",
|
|
5449
|
+
deltaE: null,
|
|
5450
|
+
card,
|
|
5451
|
+
footage: null,
|
|
5452
|
+
message: `overlays[${i}]: the frame at ${t.toFixed(2)}s could not be read`
|
|
5453
|
+
});
|
|
5454
|
+
continue;
|
|
5455
|
+
}
|
|
5456
|
+
const box = pinBox2(o, layout);
|
|
5457
|
+
const pinned = pins.get(o.id);
|
|
5458
|
+
let cx = o.transform.x * layout.W;
|
|
5459
|
+
let cy = o.transform.y * layout.H;
|
|
5460
|
+
if (pinned) {
|
|
5461
|
+
const v = sample(pinned.track, t - o.start, lerpArray);
|
|
5462
|
+
cx = v[0] * layout.W;
|
|
5463
|
+
cy = v[1] * layout.H;
|
|
5464
|
+
}
|
|
5465
|
+
const sx = img.w / layout.W;
|
|
5466
|
+
const sy = img.h / layout.H;
|
|
5467
|
+
const rect = {
|
|
5468
|
+
x: (cx - box.w / 2) * sx,
|
|
5469
|
+
y: (cy - box.h / 2) * sy,
|
|
5470
|
+
w: box.w * sx,
|
|
5471
|
+
h: box.h * sy
|
|
5472
|
+
};
|
|
5473
|
+
const footage = medianColour(img, rect);
|
|
5474
|
+
const cardRgb = parseCssColour(card);
|
|
5475
|
+
if (!cardRgb) continue;
|
|
5476
|
+
const d = deltaE(cardRgb, footage);
|
|
5477
|
+
const fh = `#${footage.map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
5478
|
+
const level = d < CALLOUT_DELTA_E_PROBLEM ? "problem" : d < CALLOUT_DELTA_E_WARNING ? "warning" : "note";
|
|
5479
|
+
out.push({
|
|
5480
|
+
index: i,
|
|
5481
|
+
id: o.id,
|
|
5482
|
+
level,
|
|
5483
|
+
deltaE: +d.toFixed(1),
|
|
5484
|
+
card,
|
|
5485
|
+
footage: fh,
|
|
5486
|
+
message: level === "note" ? `overlays[${i}] ground ${card} over footage ${fh}: \u0394E ${d.toFixed(1)}, reads as lifted` : `overlays[${i}] ground ${card} is within \u0394E ${d.toFixed(1)} of the footage it covers (${fh}) at ${t.toFixed(2)}s \u2014 it reads as one more panel of the app; invert its value (vos callout does) or move it to the margin`
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
return out;
|
|
5490
|
+
}
|
|
5491
|
+
async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
5492
|
+
const out = /* @__PURE__ */ new Map();
|
|
5493
|
+
if (!times.length) return out;
|
|
5494
|
+
const bare = { ...doc, overlays: [] };
|
|
5495
|
+
const outDir = join10(dir, ".ground-check");
|
|
5496
|
+
const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
|
|
5497
|
+
for (const f of res.frames) {
|
|
5498
|
+
if (!existsSync9(f.file)) continue;
|
|
5499
|
+
out.set(
|
|
5500
|
+
+f.time.toFixed(3),
|
|
5501
|
+
decodePng(new Uint8Array(await readFile9(f.file)))
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
5504
|
+
return out;
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5297
5507
|
// src/plugin/kitPicture.ts
|
|
5298
5508
|
import { execFile } from "child_process";
|
|
5299
5509
|
import { promisify } from "util";
|
|
5300
|
-
import { readFile as
|
|
5510
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5301
5511
|
var execFileP = promisify(execFile);
|
|
5302
5512
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
5303
5513
|
var BLANK_INK = 0.12;
|
|
@@ -5325,7 +5535,7 @@ function apcaContrast(text, bg) {
|
|
|
5325
5535
|
return sapc > -0.1 ? 0 : -(sapc + 0.027) * 100;
|
|
5326
5536
|
}
|
|
5327
5537
|
var pct = (v) => `${Math.round(v * 100)}%`;
|
|
5328
|
-
var
|
|
5538
|
+
var hexToRgb2 = (h) => {
|
|
5329
5539
|
const m = /^#([0-9a-f]{6})$/i.exec(h.trim());
|
|
5330
5540
|
if (!m) return null;
|
|
5331
5541
|
const n = parseInt(m[1], 16);
|
|
@@ -5474,7 +5684,7 @@ function textFindings(a, img) {
|
|
|
5474
5684
|
});
|
|
5475
5685
|
}
|
|
5476
5686
|
}
|
|
5477
|
-
const rgb = t.color ?
|
|
5687
|
+
const rgb = t.color ? hexToRgb2(t.color) : null;
|
|
5478
5688
|
if (rgb) {
|
|
5479
5689
|
const ground = medianColour(img, box);
|
|
5480
5690
|
const lc = apcaContrast(rgb, ground);
|
|
@@ -5573,7 +5783,7 @@ async function pictureChecks(assets) {
|
|
|
5573
5783
|
let ffmpeg = null;
|
|
5574
5784
|
for (const a of assets) {
|
|
5575
5785
|
if (/\.png$/i.test(a.file)) {
|
|
5576
|
-
const img = decodePng(new Uint8Array(await
|
|
5786
|
+
const img = decodePng(new Uint8Array(await readFile10(a.file)));
|
|
5577
5787
|
if (!img) {
|
|
5578
5788
|
findings.push({
|
|
5579
5789
|
code: "unreadable",
|
|
@@ -5666,9 +5876,9 @@ function formatFinding(f) {
|
|
|
5666
5876
|
}
|
|
5667
5877
|
|
|
5668
5878
|
// src/plugin/validateKit.ts
|
|
5669
|
-
import { existsSync as
|
|
5670
|
-
import { readFile as
|
|
5671
|
-
import { dirname as dirname2, isAbsolute as isAbsolute4, join as
|
|
5879
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5880
|
+
import { readFile as readFile11, stat as stat2 } from "fs/promises";
|
|
5881
|
+
import { dirname as dirname2, isAbsolute as isAbsolute4, join as join11 } from "path";
|
|
5672
5882
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
5673
5883
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
5674
5884
|
function pngDimensions(bytes) {
|
|
@@ -5687,7 +5897,7 @@ async function probeVideo(path) {
|
|
|
5687
5897
|
const MB = await import("mediabunny");
|
|
5688
5898
|
const input = new MB.Input({
|
|
5689
5899
|
formats: MB.ALL_FORMATS,
|
|
5690
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
5900
|
+
source: new MB.BufferSource(new Uint8Array(await readFile11(path)))
|
|
5691
5901
|
});
|
|
5692
5902
|
try {
|
|
5693
5903
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -5708,7 +5918,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5708
5918
|
const measured = [];
|
|
5709
5919
|
let kit;
|
|
5710
5920
|
try {
|
|
5711
|
-
kit = JSON.parse(await
|
|
5921
|
+
kit = JSON.parse(await readFile11(kitPath, "utf8"));
|
|
5712
5922
|
} catch (e) {
|
|
5713
5923
|
return {
|
|
5714
5924
|
valid: false,
|
|
@@ -5727,8 +5937,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5727
5937
|
}
|
|
5728
5938
|
const base = dirname2(kitPath);
|
|
5729
5939
|
const resolvePath = (p) => {
|
|
5730
|
-
if (isAbsolute4(p) &&
|
|
5731
|
-
const beside =
|
|
5940
|
+
if (isAbsolute4(p) && existsSync10(p)) return p;
|
|
5941
|
+
const beside = join11(base, p.split("/").pop() ?? p);
|
|
5732
5942
|
return beside;
|
|
5733
5943
|
};
|
|
5734
5944
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -5754,7 +5964,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5754
5964
|
let h = null;
|
|
5755
5965
|
let seconds = null;
|
|
5756
5966
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
5757
|
-
const head = Buffer.from(await
|
|
5967
|
+
const head = Buffer.from(await readFile11(file));
|
|
5758
5968
|
const kind = sniffImage(head);
|
|
5759
5969
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
5760
5970
|
problems.push(
|
|
@@ -5864,7 +6074,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5864
6074
|
|
|
5865
6075
|
// src/plugin/recorder.ts
|
|
5866
6076
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
5867
|
-
import { join as
|
|
6077
|
+
import { join as join12 } from "path";
|
|
5868
6078
|
|
|
5869
6079
|
// src/plugin/recordingCap.ts
|
|
5870
6080
|
var HOSTED_RECORDING_CAP_SECONDS = 30 * 60;
|
|
@@ -5952,7 +6162,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
5952
6162
|
cdp.on("Page.screencastFrame", (ev) => {
|
|
5953
6163
|
const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
|
|
5954
6164
|
const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
|
|
5955
|
-
writeFileSync2(
|
|
6165
|
+
writeFileSync2(join12(paths.framesDir, file), Buffer.from(ev.data, "base64"));
|
|
5956
6166
|
frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
|
|
5957
6167
|
cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
|
|
5958
6168
|
});
|
|
@@ -6190,7 +6400,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6190
6400
|
await sleep(200);
|
|
6191
6401
|
const pageTitle = await page.title().catch(() => "");
|
|
6192
6402
|
await context.close();
|
|
6193
|
-
const firstFrame = frames[0] ? jpegDims(readFileSync3(
|
|
6403
|
+
const firstFrame = frames[0] ? jpegDims(readFileSync3(join12(paths.framesDir, frames[0].file))) : null;
|
|
6194
6404
|
const meta = {
|
|
6195
6405
|
dpr: 1,
|
|
6196
6406
|
zoom: 1,
|
|
@@ -6298,9 +6508,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6298
6508
|
}
|
|
6299
6509
|
|
|
6300
6510
|
// src/plugin/plan.ts
|
|
6301
|
-
import { existsSync as
|
|
6302
|
-
import { readFile as
|
|
6303
|
-
import { join as
|
|
6511
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6512
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6513
|
+
import { join as join13 } from "path";
|
|
6304
6514
|
import {
|
|
6305
6515
|
DEFAULT_FRAME_STYLE,
|
|
6306
6516
|
STYLE_FIELDS,
|
|
@@ -6522,10 +6732,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
6522
6732
|
// src/plugin/plan.ts
|
|
6523
6733
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
6524
6734
|
async function readDigestActivity(dir) {
|
|
6525
|
-
const file =
|
|
6526
|
-
if (!
|
|
6735
|
+
const file = join13(dir, "digest", "digest.json");
|
|
6736
|
+
if (!existsSync11(file)) return null;
|
|
6527
6737
|
try {
|
|
6528
|
-
const d = JSON.parse(await
|
|
6738
|
+
const d = JSON.parse(await readFile12(file, "utf8"));
|
|
6529
6739
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
6530
6740
|
} catch {
|
|
6531
6741
|
return null;
|
|
@@ -6735,17 +6945,17 @@ async function planTake(dir, opts = {}) {
|
|
|
6735
6945
|
|
|
6736
6946
|
// src/plugin/sync.ts
|
|
6737
6947
|
import { createHash } from "crypto";
|
|
6738
|
-
import { existsSync as
|
|
6739
|
-
import { readFile as
|
|
6948
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6949
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6740
6950
|
import { createInterface } from "readline/promises";
|
|
6741
|
-
import { basename, join as
|
|
6951
|
+
import { basename, join as join16 } from "path";
|
|
6742
6952
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
6743
6953
|
|
|
6744
6954
|
// src/plugin/media.ts
|
|
6745
6955
|
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
6746
|
-
import { createWriteStream, existsSync as
|
|
6956
|
+
import { createWriteStream, existsSync as existsSync12 } from "fs";
|
|
6747
6957
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
6748
|
-
import { join as
|
|
6958
|
+
import { join as join14 } from "path";
|
|
6749
6959
|
import { Readable } from "stream";
|
|
6750
6960
|
import { pipeline } from "stream/promises";
|
|
6751
6961
|
var MIC_NAME = "mic.webm";
|
|
@@ -6869,8 +7079,8 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6869
7079
|
const url = doc.source[key];
|
|
6870
7080
|
const assetId = assetIdOf(url);
|
|
6871
7081
|
if (!assetId) continue;
|
|
6872
|
-
const target =
|
|
6873
|
-
if (
|
|
7082
|
+
const target = join14(dir, file);
|
|
7083
|
+
if (existsSync12(target)) {
|
|
6874
7084
|
result.kept.push(file);
|
|
6875
7085
|
} else {
|
|
6876
7086
|
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
@@ -6895,8 +7105,8 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6895
7105
|
for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
|
|
6896
7106
|
const assetId = assetIdOf(ref.key);
|
|
6897
7107
|
const abs = /^https?:/.test(ref.key) ? ref.key : `${ctx.origin}${ref.key}`;
|
|
6898
|
-
const existing =
|
|
6899
|
-
(fs) => fs.readdir(
|
|
7108
|
+
const existing = existsSync12(join14(dir, "media")) ? (await import("fs/promises").then(
|
|
7109
|
+
(fs) => fs.readdir(join14(dir, "media"))
|
|
6900
7110
|
)).find((f) => f.startsWith(`${assetId}.`)) : void 0;
|
|
6901
7111
|
let file;
|
|
6902
7112
|
if (existing) {
|
|
@@ -6913,9 +7123,9 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6913
7123
|
}
|
|
6914
7124
|
const ext = extensionFor(res.headers.get("content-type") ?? "");
|
|
6915
7125
|
file = `media/${assetId}${ext}`;
|
|
6916
|
-
const target =
|
|
7126
|
+
const target = join14(dir, file);
|
|
6917
7127
|
await import("fs/promises").then(
|
|
6918
|
-
(fs) => fs.mkdir(
|
|
7128
|
+
(fs) => fs.mkdir(join14(dir, "media"), { recursive: true })
|
|
6919
7129
|
);
|
|
6920
7130
|
await pipeline(
|
|
6921
7131
|
Readable.fromWeb(res.body),
|
|
@@ -6929,17 +7139,17 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6929
7139
|
}
|
|
6930
7140
|
ref.set(file);
|
|
6931
7141
|
}
|
|
6932
|
-
const metaPath =
|
|
6933
|
-
if (!
|
|
6934
|
-
const cursorPath =
|
|
6935
|
-
if (!
|
|
6936
|
-
await writeFile9(
|
|
7142
|
+
const metaPath = join14(dir, "meta.json");
|
|
7143
|
+
if (!existsSync12(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
7144
|
+
const cursorPath = join14(dir, "cursor.json");
|
|
7145
|
+
if (!existsSync12(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
7146
|
+
await writeFile9(join14(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
6937
7147
|
return result;
|
|
6938
7148
|
}
|
|
6939
7149
|
|
|
6940
7150
|
// src/plugin/folder.ts
|
|
6941
7151
|
import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
|
|
6942
|
-
import { join as
|
|
7152
|
+
import { join as join15 } from "path";
|
|
6943
7153
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
6944
7154
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
6945
7155
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -7115,18 +7325,18 @@ async function cmdPull(argv) {
|
|
|
7115
7325
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
7116
7326
|
const payload = res.body;
|
|
7117
7327
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
7118
|
-
await mkdir7(
|
|
7328
|
+
await mkdir7(join15(out, "recipes"), { recursive: true });
|
|
7119
7329
|
const lines = [];
|
|
7120
7330
|
const recipes = [];
|
|
7121
7331
|
for (const rec of payload.recipes) {
|
|
7122
|
-
const path =
|
|
7332
|
+
const path = join15(out, "recipes", safeName(rec.filename));
|
|
7123
7333
|
await writeFile10(path, rec.body ?? "");
|
|
7124
7334
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
7125
7335
|
}
|
|
7126
7336
|
for (const rec of payload.inheritedRecipes) {
|
|
7127
7337
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
7128
|
-
await mkdir7(
|
|
7129
|
-
const path =
|
|
7338
|
+
await mkdir7(join15(out, "recipes", "_inherited", from), { recursive: true });
|
|
7339
|
+
const path = join15(
|
|
7130
7340
|
out,
|
|
7131
7341
|
"recipes",
|
|
7132
7342
|
"_inherited",
|
|
@@ -7141,7 +7351,7 @@ async function cmdPull(argv) {
|
|
|
7141
7351
|
}
|
|
7142
7352
|
const members = [];
|
|
7143
7353
|
for (const v of payload.voses) {
|
|
7144
|
-
const dir =
|
|
7354
|
+
const dir = join15(out, "members", safeName(v.slug || v.title || v.id));
|
|
7145
7355
|
await mkdir7(dir, { recursive: true });
|
|
7146
7356
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
7147
7357
|
if (cfg.status !== 200) {
|
|
@@ -7149,7 +7359,7 @@ async function cmdPull(argv) {
|
|
|
7149
7359
|
continue;
|
|
7150
7360
|
}
|
|
7151
7361
|
await writeFile10(
|
|
7152
|
-
|
|
7362
|
+
join15(dir, "config.json"),
|
|
7153
7363
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7154
7364
|
);
|
|
7155
7365
|
let take = false;
|
|
@@ -7161,7 +7371,7 @@ async function cmdPull(argv) {
|
|
|
7161
7371
|
);
|
|
7162
7372
|
if (doc.status === 200) {
|
|
7163
7373
|
await writeFile10(
|
|
7164
|
-
|
|
7374
|
+
join15(dir, "doc.json"),
|
|
7165
7375
|
JSON.stringify(doc.body, null, 2)
|
|
7166
7376
|
);
|
|
7167
7377
|
take = true;
|
|
@@ -7278,7 +7488,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7278
7488
|
throw new Error(`doc.json fails lint:
|
|
7279
7489
|
${lint.problems.join("\n ")}`);
|
|
7280
7490
|
}
|
|
7281
|
-
if (!
|
|
7491
|
+
if (!existsSync13(take.paths.recording)) {
|
|
7282
7492
|
throw new Error("no recording.webm in this take");
|
|
7283
7493
|
}
|
|
7284
7494
|
const ctx = apiContext(flags);
|
|
@@ -7310,7 +7520,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7310
7520
|
throw new Error("push cancelled");
|
|
7311
7521
|
}
|
|
7312
7522
|
}
|
|
7313
|
-
const bytes = await
|
|
7523
|
+
const bytes = await readFile13(take.paths.recording);
|
|
7314
7524
|
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7315
7525
|
r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
|
|
7316
7526
|
const upload = await api(ctx, "/assets/recording", {
|
|
@@ -7349,12 +7559,12 @@ async function pushTake(dir, flags, r) {
|
|
|
7349
7559
|
delete docForPush.source.micKey;
|
|
7350
7560
|
}
|
|
7351
7561
|
for (const ref of docMediaRefs(docForPush)) {
|
|
7352
|
-
const file =
|
|
7353
|
-
if (!
|
|
7562
|
+
const file = join16(dir, takeRelativeFile(ref.key));
|
|
7563
|
+
if (!existsSync13(file)) {
|
|
7354
7564
|
r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
|
|
7355
7565
|
continue;
|
|
7356
7566
|
}
|
|
7357
|
-
const media = await
|
|
7567
|
+
const media = await readFile13(file);
|
|
7358
7568
|
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
7359
7569
|
const type = mediaContentType(file);
|
|
7360
7570
|
const put = await api(ctx, "/assets/recording", {
|
|
@@ -7490,9 +7700,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7490
7700
|
};
|
|
7491
7701
|
}
|
|
7492
7702
|
let media2;
|
|
7493
|
-
if (flags.media &&
|
|
7703
|
+
if (flags.media && existsSync13(join16(dir, "doc.json"))) {
|
|
7494
7704
|
const local = JSON.parse(
|
|
7495
|
-
await
|
|
7705
|
+
await readFile13(join16(dir, "doc.json"), "utf8")
|
|
7496
7706
|
);
|
|
7497
7707
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7498
7708
|
}
|
|
@@ -7552,10 +7762,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7552
7762
|
}
|
|
7553
7763
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
7554
7764
|
const doc = hostedDoc;
|
|
7555
|
-
if (
|
|
7765
|
+
if (existsSync13(join16(dir, RECORDING_NAME))) {
|
|
7556
7766
|
doc.source.videoKey = RECORDING_NAME;
|
|
7557
7767
|
}
|
|
7558
|
-
await writeJson(
|
|
7768
|
+
await writeJson(join16(dir, "doc.json"), doc, true);
|
|
7559
7769
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
7560
7770
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
7561
7771
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -7574,10 +7784,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7574
7784
|
}
|
|
7575
7785
|
|
|
7576
7786
|
// src/plugin/program.ts
|
|
7577
|
-
import { existsSync as
|
|
7578
|
-
import { mkdir as mkdir8, readFile as
|
|
7787
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7788
|
+
import { mkdir as mkdir8, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
7579
7789
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7580
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
7790
|
+
import { basename as basename2, dirname as dirname3, join as join17 } from "path";
|
|
7581
7791
|
import {
|
|
7582
7792
|
CURRENT_CONFIG_VERSION,
|
|
7583
7793
|
migrateConfig,
|
|
@@ -7747,8 +7957,8 @@ function preflightConfig(parsed) {
|
|
|
7747
7957
|
}
|
|
7748
7958
|
function resolveConfigPath(target) {
|
|
7749
7959
|
if (target.endsWith(".json")) return target;
|
|
7750
|
-
const inDir =
|
|
7751
|
-
if (
|
|
7960
|
+
const inDir = join17(target, "config.json");
|
|
7961
|
+
if (existsSync14(inDir)) return inDir;
|
|
7752
7962
|
throw new UsageError(
|
|
7753
7963
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
7754
7964
|
);
|
|
@@ -7776,7 +7986,7 @@ async function cmdFetch(argv) {
|
|
|
7776
7986
|
const out = strFlag(flags, "out") ?? slug;
|
|
7777
7987
|
await mkdir8(out, { recursive: true });
|
|
7778
7988
|
await writeFile11(
|
|
7779
|
-
|
|
7989
|
+
join17(out, "config.json"),
|
|
7780
7990
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7781
7991
|
);
|
|
7782
7992
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -7801,12 +8011,12 @@ async function cmdFetch(argv) {
|
|
|
7801
8011
|
const hosted = migrateHostedDoc4(doc.body);
|
|
7802
8012
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
7803
8013
|
if (own) {
|
|
7804
|
-
await writeFile11(
|
|
8014
|
+
await writeFile11(join17(out, "config.json"), JSON.stringify(own, null, 2));
|
|
7805
8015
|
}
|
|
7806
8016
|
} else if (doc.status === 200) {
|
|
7807
8017
|
take = true;
|
|
7808
8018
|
const hosted = migrateHostedDoc4(doc.body);
|
|
7809
|
-
await writeFile11(
|
|
8019
|
+
await writeFile11(join17(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
7810
8020
|
if (flags.media === true) {
|
|
7811
8021
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
7812
8022
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -7919,7 +8129,7 @@ async function cmdPushProgram(argv) {
|
|
|
7919
8129
|
api: strFlag(flags, "api")
|
|
7920
8130
|
});
|
|
7921
8131
|
const dir = dirname3(source);
|
|
7922
|
-
const parsed = JSON.parse(await
|
|
8132
|
+
const parsed = JSON.parse(await readFile14(source, "utf8"));
|
|
7923
8133
|
const pre = preflightConfig(parsed);
|
|
7924
8134
|
if (!pre.ok || !pre.config) {
|
|
7925
8135
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -8160,10 +8370,10 @@ async function cmdPullProgram(argv) {
|
|
|
8160
8370
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
8161
8371
|
if (cfg.status !== 200)
|
|
8162
8372
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
8163
|
-
const configPath =
|
|
8373
|
+
const configPath = join17(dir, "config.json");
|
|
8164
8374
|
let backedUp = false;
|
|
8165
|
-
if (
|
|
8166
|
-
await writeFile11(
|
|
8375
|
+
if (existsSync14(configPath)) {
|
|
8376
|
+
await writeFile11(join17(dir, "config.backup.json"), await readFile14(configPath));
|
|
8167
8377
|
backedUp = true;
|
|
8168
8378
|
}
|
|
8169
8379
|
let headConfig = cfg.body.config;
|
|
@@ -8196,7 +8406,7 @@ async function cmdPullProgram(argv) {
|
|
|
8196
8406
|
protected: protectedIds,
|
|
8197
8407
|
changes,
|
|
8198
8408
|
out: configPath,
|
|
8199
|
-
backup: backedUp ?
|
|
8409
|
+
backup: backedUp ? join17(dir, "config.backup.json") : null
|
|
8200
8410
|
},
|
|
8201
8411
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
8202
8412
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -8258,9 +8468,9 @@ function storedProgramConfig(config, programDoc) {
|
|
|
8258
8468
|
return lowerProgramDoc(doc).config;
|
|
8259
8469
|
}
|
|
8260
8470
|
async function readProgramDoc(dir, config) {
|
|
8261
|
-
const docPath =
|
|
8262
|
-
if (!
|
|
8263
|
-
const parsed = JSON.parse(await
|
|
8471
|
+
const docPath = join17(dir, "doc.json");
|
|
8472
|
+
if (!existsSync14(docPath)) return null;
|
|
8473
|
+
const parsed = JSON.parse(await readFile14(docPath, "utf8"));
|
|
8264
8474
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
8265
8475
|
return null;
|
|
8266
8476
|
const raw = parsed;
|
|
@@ -8271,7 +8481,7 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
8271
8481
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
8272
8482
|
const { config, ...rest } = program;
|
|
8273
8483
|
const onDisk = { ...hosted, program: rest };
|
|
8274
|
-
await writeFile11(
|
|
8484
|
+
await writeFile11(join17(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
8275
8485
|
return {
|
|
8276
8486
|
config: config && typeof config === "object" ? config : null
|
|
8277
8487
|
};
|
|
@@ -9327,8 +9537,8 @@ async function cmdActions(argv) {
|
|
|
9327
9537
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
9328
9538
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
9329
9539
|
}
|
|
9330
|
-
const { readFile:
|
|
9331
|
-
const text = await
|
|
9540
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
9541
|
+
const text = await readFile16(resolve8(input), "utf8");
|
|
9332
9542
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
9333
9543
|
for (const p of problems) r.log(` ${p}`);
|
|
9334
9544
|
const result = convertAgentBrowser(records, {
|
|
@@ -9382,7 +9592,8 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
|
|
|
9382
9592
|
"yes",
|
|
9383
9593
|
"composed",
|
|
9384
9594
|
"check",
|
|
9385
|
-
"motion"
|
|
9595
|
+
"motion",
|
|
9596
|
+
"leader"
|
|
9386
9597
|
]);
|
|
9387
9598
|
var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override"]);
|
|
9388
9599
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
@@ -9397,6 +9608,7 @@ Take pipeline
|
|
|
9397
9608
|
vos digest <take> [--out dir] [--full 960] [--crop 640] [--no-frames] [--transcript <file.json>] [--style <doc.json|vosId>] [--json]
|
|
9398
9609
|
vos brand <url> [--out BRAND.md] [--json]
|
|
9399
9610
|
vos open <take> [--studio <url>] [--print]
|
|
9611
|
+
vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side auto|right|left|below|above] [--mark ring|underline|none] [--leader] [--color #hex] [--brand BRAND.md] [--ground #hex] [--accent #hex] [--font "\u2026"] [--body-px 14] [--id x] [--print] [--json]
|
|
9400
9612
|
vos validate <actions.json|take|kit.json> [--picture] [--json]
|
|
9401
9613
|
vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
|
|
9402
9614
|
vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
|
|
@@ -9630,7 +9842,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
9630
9842
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
9631
9843
|
`;
|
|
9632
9844
|
async function loadActions(file) {
|
|
9633
|
-
const raw = JSON.parse(await
|
|
9845
|
+
const raw = JSON.parse(await readFile15(file, "utf8"));
|
|
9634
9846
|
const errors = validateActions(raw);
|
|
9635
9847
|
if (errors.length)
|
|
9636
9848
|
throw new UsageError(`invalid actions file:
|
|
@@ -9689,7 +9901,7 @@ async function cmdRecord(argv) {
|
|
|
9689
9901
|
const outDir = resolve9(strFlag(flags, "out") ?? "take");
|
|
9690
9902
|
const backdrop = await takeBackdrop(flags, r);
|
|
9691
9903
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9692
|
-
if (
|
|
9904
|
+
if (existsSync15(join18(outDir, "meta.json"))) {
|
|
9693
9905
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9694
9906
|
r.log(
|
|
9695
9907
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
@@ -9736,7 +9948,7 @@ async function cmdRecord(argv) {
|
|
|
9736
9948
|
},
|
|
9737
9949
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
9738
9950
|
${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen${rec.freezePct >= 25 ? " \u26A0 keep motion in frame or trim" : ""}${skippedNote}
|
|
9739
|
-
Next: edit ${
|
|
9951
|
+
Next: edit ${join18(outDir, "doc.json")} (optional), then: vos render ${outDir}`
|
|
9740
9952
|
);
|
|
9741
9953
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
9742
9954
|
} finally {
|
|
@@ -9765,14 +9977,14 @@ async function cmdCreate2(argv) {
|
|
|
9765
9977
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
9766
9978
|
throw new UsageError("--format must be webm or mp4");
|
|
9767
9979
|
const format = fmtRaw;
|
|
9768
|
-
const out = positionals[0] ??
|
|
9980
|
+
const out = positionals[0] ?? join18(outDir, `out.${format}`);
|
|
9769
9981
|
const parallel = numFlag(flags, "parallel", 1);
|
|
9770
9982
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
9771
9983
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
9772
9984
|
}
|
|
9773
9985
|
const backdrop = await takeBackdrop(flags, r);
|
|
9774
9986
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9775
|
-
if (
|
|
9987
|
+
if (existsSync15(join18(outDir, "meta.json"))) {
|
|
9776
9988
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9777
9989
|
r.log(
|
|
9778
9990
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
@@ -9863,24 +10075,24 @@ async function cmdPlan(argv) {
|
|
|
9863
10075
|
);
|
|
9864
10076
|
const r = createReporter(flags.json === true);
|
|
9865
10077
|
if (flags.fresh === true) {
|
|
9866
|
-
await rm4(
|
|
10078
|
+
await rm4(join18(dir, "doc.json"), { force: true });
|
|
9867
10079
|
r.log("note: --fresh discarded the existing doc.json");
|
|
9868
10080
|
}
|
|
9869
10081
|
let reuse;
|
|
9870
10082
|
if (flags.reuse === true) {
|
|
9871
|
-
const from = resolve9(strFlag(flags, "from") ??
|
|
9872
|
-
if (!
|
|
10083
|
+
const from = resolve9(strFlag(flags, "from") ?? join18(dir, PREV_DOC_NAME));
|
|
10084
|
+
if (!existsSync15(from)) {
|
|
9873
10085
|
throw new UsageError(
|
|
9874
10086
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
9875
10087
|
);
|
|
9876
10088
|
}
|
|
9877
10089
|
reuse = {
|
|
9878
10090
|
from,
|
|
9879
|
-
doc: JSON.parse(await
|
|
10091
|
+
doc: JSON.parse(await readFile15(from, "utf8"))
|
|
9880
10092
|
};
|
|
9881
10093
|
}
|
|
9882
10094
|
const style = await resolveStyleRef(flags);
|
|
9883
|
-
const hasDoc =
|
|
10095
|
+
const hasDoc = existsSync15(join18(dir, "doc.json"));
|
|
9884
10096
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
9885
10097
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
9886
10098
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -9938,7 +10150,7 @@ async function cmdPlan(argv) {
|
|
|
9938
10150
|
...s.motion ? { motion: s.motion } : {},
|
|
9939
10151
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
9940
10152
|
},
|
|
9941
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
10153
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join18(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
|
|
9942
10154
|
);
|
|
9943
10155
|
return EXIT_OK;
|
|
9944
10156
|
}
|
|
@@ -10064,7 +10276,7 @@ async function cmdRender(argv) {
|
|
|
10064
10276
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
10065
10277
|
throw new UsageError("--format must be webm or mp4");
|
|
10066
10278
|
const format = fmtRaw;
|
|
10067
|
-
const out = positionals[1] ??
|
|
10279
|
+
const out = positionals[1] ?? join18(dir, `out.${format}`);
|
|
10068
10280
|
const parallel = numFlag(flags, "parallel", 1);
|
|
10069
10281
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
10070
10282
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -10311,15 +10523,15 @@ async function resolveStyleRef(flags) {
|
|
|
10311
10523
|
}
|
|
10312
10524
|
async function resolveDocRef(ref, flags, what) {
|
|
10313
10525
|
const styleRef = ref;
|
|
10314
|
-
const file =
|
|
10526
|
+
const file = existsSync15(styleRef) ? resolve9(
|
|
10315
10527
|
styleRef,
|
|
10316
|
-
|
|
10528
|
+
existsSync15(join18(styleRef, "doc.json")) ? "doc.json" : ""
|
|
10317
10529
|
) : null;
|
|
10318
|
-
if (file &&
|
|
10530
|
+
if (file && existsSync15(file)) {
|
|
10319
10531
|
return {
|
|
10320
10532
|
from: file,
|
|
10321
10533
|
doc: migrateHostedDoc5(
|
|
10322
|
-
JSON.parse(await
|
|
10534
|
+
JSON.parse(await readFile15(file, "utf8"))
|
|
10323
10535
|
)
|
|
10324
10536
|
};
|
|
10325
10537
|
}
|
|
@@ -10371,7 +10583,7 @@ async function cmdDigest(argv) {
|
|
|
10371
10583
|
const take = await loadTake(dir);
|
|
10372
10584
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
10373
10585
|
const transcriptPath = strFlag(flags, "transcript");
|
|
10374
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
10586
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile15(transcriptPath, "utf8"))) : null;
|
|
10375
10587
|
const style = await resolveStyleRef(flags);
|
|
10376
10588
|
const noFrames = flags["no-frames"] === true;
|
|
10377
10589
|
let browser = null;
|
|
@@ -10402,7 +10614,7 @@ async function cmdDigest(argv) {
|
|
|
10402
10614
|
kinds,
|
|
10403
10615
|
frames: d.images.full,
|
|
10404
10616
|
crops: d.images.crop,
|
|
10405
|
-
sheet: d.images.sheet ?
|
|
10617
|
+
sheet: d.images.sheet ? join18(result.outDir, d.images.sheet) : null,
|
|
10406
10618
|
scenes: kinds.scene ?? 0,
|
|
10407
10619
|
sourceDuration: d.take.sourceDuration,
|
|
10408
10620
|
outputDuration: d.take.outputDuration,
|
|
@@ -10426,7 +10638,7 @@ async function cmdOpen(argv) {
|
|
|
10426
10638
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
10427
10639
|
const r = createReporter(flags.json === true);
|
|
10428
10640
|
const take = await loadTake(dir);
|
|
10429
|
-
if (!
|
|
10641
|
+
if (!existsSync15(take.paths.recording)) {
|
|
10430
10642
|
throw new UsageError(`${dir} has no recording.webm \u2014 re-run record`);
|
|
10431
10643
|
}
|
|
10432
10644
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
@@ -10467,12 +10679,12 @@ async function cmdJudge(argv) {
|
|
|
10467
10679
|
"vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
|
|
10468
10680
|
);
|
|
10469
10681
|
const r = createReporter(flags.json === true);
|
|
10470
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
10471
|
-
if (!
|
|
10472
|
-
if (!
|
|
10682
|
+
const kitFile = target.endsWith("kit.json") ? target : join18(target, "kit.json");
|
|
10683
|
+
if (!existsSync15(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
10684
|
+
if (!existsSync15(against))
|
|
10473
10685
|
throw new UsageError(`${against}: no reference manifest`);
|
|
10474
10686
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
10475
|
-
const verdicts = JSON.parse(await
|
|
10687
|
+
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
10476
10688
|
const rate = winRate(verdicts);
|
|
10477
10689
|
const lines = result.sheets.map(
|
|
10478
10690
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -10487,12 +10699,109 @@ Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judg
|
|
|
10487
10699
|
);
|
|
10488
10700
|
return EXIT_OK;
|
|
10489
10701
|
}
|
|
10702
|
+
async function cmdCallout(argv) {
|
|
10703
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
10704
|
+
const dir = positionals[0];
|
|
10705
|
+
const shape = positionals[1];
|
|
10706
|
+
if (!dir || !shape || !isCalloutShape(shape))
|
|
10707
|
+
throw new UsageError(
|
|
10708
|
+
'vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side \u2026] [--mark ring|underline|none] [--leader] [--color #hex] [--brand BRAND.md] [--ground #hex --accent #hex] [--font "\u2026"] [--body-px 14] [--id x] [--print] [--json]'
|
|
10709
|
+
);
|
|
10710
|
+
const r = createReporter(flags.json === true);
|
|
10711
|
+
const take = await loadTake(dir);
|
|
10712
|
+
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
10713
|
+
const brand = await readBrandBesideTake(dir, strFlag(flags, "brand"));
|
|
10714
|
+
const bodyPx = strFlag(flags, "body-px");
|
|
10715
|
+
const register = registerFrom(brand?.roles ?? null, {
|
|
10716
|
+
ground: strFlag(flags, "ground"),
|
|
10717
|
+
accent: strFlag(flags, "accent"),
|
|
10718
|
+
face: strFlag(flags, "font"),
|
|
10719
|
+
mono: strFlag(flags, "mono"),
|
|
10720
|
+
body: bodyPx !== void 0 ? Number(bodyPx) : void 0
|
|
10721
|
+
});
|
|
10722
|
+
if (!register)
|
|
10723
|
+
throw new UsageError(
|
|
10724
|
+
`no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
|
|
10725
|
+
);
|
|
10726
|
+
const codeFile = strFlag(flags, "code-file");
|
|
10727
|
+
const code = codeFile ? await readFile15(codeFile, "utf8") : strFlag(flags, "code");
|
|
10728
|
+
const words = {
|
|
10729
|
+
kicker: strFlag(flags, "kicker"),
|
|
10730
|
+
title: strFlag(flags, "title"),
|
|
10731
|
+
body: strFlag(flags, "body"),
|
|
10732
|
+
code: code?.replace(/\\n/g, "\n")
|
|
10733
|
+
};
|
|
10734
|
+
if (shape === "code" && !words.code)
|
|
10735
|
+
throw new UsageError(
|
|
10736
|
+
'a code callout needs --code "\u2026" or --code-file <file>'
|
|
10737
|
+
);
|
|
10738
|
+
if (shape === "note" && !words.title)
|
|
10739
|
+
throw new UsageError('a note needs --title "\u2026"');
|
|
10740
|
+
if (shape === "tag" && !words.kicker && !words.title)
|
|
10741
|
+
throw new UsageError('a tag needs --kicker "\u2026"');
|
|
10742
|
+
const stepRaw = strFlag(flags, "step");
|
|
10743
|
+
const atRaw = strFlag(flags, "at");
|
|
10744
|
+
const secondsRaw = strFlag(flags, "seconds");
|
|
10745
|
+
const side = strFlag(flags, "side");
|
|
10746
|
+
const mark = strFlag(flags, "mark");
|
|
10747
|
+
let composed;
|
|
10748
|
+
try {
|
|
10749
|
+
composed = composeCallout(take.doc, register, {
|
|
10750
|
+
shape,
|
|
10751
|
+
words,
|
|
10752
|
+
id: strFlag(flags, "id"),
|
|
10753
|
+
step: stepRaw !== void 0 ? /^\d+$/.test(stepRaw) ? Number(stepRaw) : stepRaw : void 0,
|
|
10754
|
+
at: atRaw !== void 0 ? Number(atRaw) : void 0,
|
|
10755
|
+
seconds: secondsRaw !== void 0 ? Number(secondsRaw) : void 0,
|
|
10756
|
+
side,
|
|
10757
|
+
mark,
|
|
10758
|
+
leader: flags.leader === true,
|
|
10759
|
+
color: strFlag(flags, "color")
|
|
10760
|
+
});
|
|
10761
|
+
} catch (e) {
|
|
10762
|
+
throw new UsageError(e instanceof Error ? e.message : String(e));
|
|
10763
|
+
}
|
|
10764
|
+
const { clip, window: window2 } = composed;
|
|
10765
|
+
if (flags.print === true) {
|
|
10766
|
+
r.done(
|
|
10767
|
+
{ clip, window: window2, register, brand: brand?.file ?? null },
|
|
10768
|
+
JSON.stringify(clip, null, 2)
|
|
10769
|
+
);
|
|
10770
|
+
return EXIT_OK;
|
|
10771
|
+
}
|
|
10772
|
+
const doc = take.doc;
|
|
10773
|
+
const overlays = (doc.overlays ?? []).filter((o) => o.id !== clip.id);
|
|
10774
|
+
const next = { ...doc, overlays: [...overlays, clip] };
|
|
10775
|
+
const lint = lintDoc(next);
|
|
10776
|
+
if (lint.problems.length) {
|
|
10777
|
+
r.done(
|
|
10778
|
+
{ valid: false, problems: lint.problems, warnings: lint.warnings, clip },
|
|
10779
|
+
`the callout would not validate:
|
|
10780
|
+
${lint.problems.join("\n ")}`
|
|
10781
|
+
);
|
|
10782
|
+
return EXIT_ERROR;
|
|
10783
|
+
}
|
|
10784
|
+
await writeJson(take.paths.doc, next, true);
|
|
10785
|
+
r.done(
|
|
10786
|
+
{
|
|
10787
|
+
clip,
|
|
10788
|
+
window: window2,
|
|
10789
|
+
register,
|
|
10790
|
+
brand: brand?.file ?? null,
|
|
10791
|
+
warnings: lint.warnings
|
|
10792
|
+
},
|
|
10793
|
+
`${shape} "${clip.id}" at ${window2.start.toFixed(2)}s for ${window2.duration.toFixed(2)}s${clip.pin ? `, pinned to ${clip.pin.step !== void 0 ? `step ${String(clip.pin.step)}` : `the press at ${String(clip.pin.press)}s`}` : ""}, ground ${register.ground} \u2192 card in the product's hue, written to ${take.paths.doc}${lint.warnings.length ? `
|
|
10794
|
+
warnings:
|
|
10795
|
+
${lint.warnings.join("\n ")}` : ""}`
|
|
10796
|
+
);
|
|
10797
|
+
return EXIT_OK;
|
|
10798
|
+
}
|
|
10490
10799
|
async function cmdValidate(argv) {
|
|
10491
10800
|
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
10492
10801
|
const target = positionals[0];
|
|
10493
10802
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
10494
10803
|
const r = createReporter(flags.json === true);
|
|
10495
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
10804
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync15(join18(target, "kit.json")) && !isTakeDir(target) ? join18(target, "kit.json") : null;
|
|
10496
10805
|
if (kitTarget) {
|
|
10497
10806
|
const picture = flags.picture === true;
|
|
10498
10807
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -10524,9 +10833,9 @@ async function cmdValidate(argv) {
|
|
|
10524
10833
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
10525
10834
|
return EXIT_OK;
|
|
10526
10835
|
}
|
|
10527
|
-
if (!isTakeDir(target) &&
|
|
10836
|
+
if (!isTakeDir(target) && existsSync15(join18(target, "config.json"))) {
|
|
10528
10837
|
const parsed = JSON.parse(
|
|
10529
|
-
await
|
|
10838
|
+
await readFile15(join18(target, "config.json"), "utf8")
|
|
10530
10839
|
);
|
|
10531
10840
|
const pre = preflightConfig(parsed);
|
|
10532
10841
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -10567,7 +10876,7 @@ async function cmdValidate(argv) {
|
|
|
10567
10876
|
const take = await loadTake(target);
|
|
10568
10877
|
const problems = [];
|
|
10569
10878
|
const warnings = [];
|
|
10570
|
-
if (!
|
|
10879
|
+
if (!existsSync15(take.paths.recording))
|
|
10571
10880
|
problems.push("missing recording.webm (re-run record)");
|
|
10572
10881
|
if (!take.doc) problems.push("missing doc.json (run plan)");
|
|
10573
10882
|
if (take.doc) {
|
|
@@ -10579,6 +10888,36 @@ async function cmdValidate(argv) {
|
|
|
10579
10888
|
);
|
|
10580
10889
|
problems.push(...probe.problems);
|
|
10581
10890
|
warnings.push(...probe.warnings);
|
|
10891
|
+
if (flags.picture === true && (take.doc.overlays ?? []).some((o) => o.kind === "html")) {
|
|
10892
|
+
const doc = take.doc;
|
|
10893
|
+
const times = [
|
|
10894
|
+
...new Set(
|
|
10895
|
+
(doc.overlays ?? []).filter((o) => o.kind === "html").map((o) => +(o.start + Math.min(0.35, o.duration / 2)).toFixed(3))
|
|
10896
|
+
)
|
|
10897
|
+
];
|
|
10898
|
+
const browser = await launchBrowser();
|
|
10899
|
+
try {
|
|
10900
|
+
const frames = await footageFrames(
|
|
10901
|
+
browser,
|
|
10902
|
+
target,
|
|
10903
|
+
doc,
|
|
10904
|
+
times,
|
|
10905
|
+
framesTake
|
|
10906
|
+
);
|
|
10907
|
+
const findings = await groundFindings(
|
|
10908
|
+
doc,
|
|
10909
|
+
async (t) => frames.get(+t.toFixed(3)) ?? null
|
|
10910
|
+
);
|
|
10911
|
+
for (const f of findings) {
|
|
10912
|
+
if (f.level === "problem") problems.push(`doc.json: ${f.message}`);
|
|
10913
|
+
else if (f.level === "warning")
|
|
10914
|
+
warnings.push(`doc.json: ${f.message}`);
|
|
10915
|
+
else r.event({ event: "ground", ...f });
|
|
10916
|
+
}
|
|
10917
|
+
} finally {
|
|
10918
|
+
await browser.close();
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10582
10921
|
}
|
|
10583
10922
|
if (problems.length) {
|
|
10584
10923
|
r.done(
|
|
@@ -10695,6 +11034,8 @@ async function run(argv) {
|
|
|
10695
11034
|
return await cmdOpen(rest);
|
|
10696
11035
|
case "validate":
|
|
10697
11036
|
return await cmdValidate(rest);
|
|
11037
|
+
case "callout":
|
|
11038
|
+
return await cmdCallout(rest);
|
|
10698
11039
|
case "judge":
|
|
10699
11040
|
return await cmdJudge(rest);
|
|
10700
11041
|
case "fetch":
|
|
@@ -10778,4 +11119,4 @@ export {
|
|
|
10778
11119
|
convertAgentBrowser,
|
|
10779
11120
|
run
|
|
10780
11121
|
};
|
|
10781
|
-
//# sourceMappingURL=chunk-
|
|
11122
|
+
//# sourceMappingURL=chunk-6O55OD6D.js.map
|