@vosjs/cli 0.34.0 → 0.36.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 +1 -1
- package/dist/{chunk-43FCXWNS.js → chunk-MIZWK5AN.js} +188 -52
- package/dist/chunk-MIZWK5AN.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-EOIIBITT.js → run-LHMYTKV7.js} +2 -2
- package/package.json +3 -3
- package/schema/doc.schema.json +4 -4
- package/dist/chunk-43FCXWNS.js.map +0 -1
- /package/dist/{run-EOIIBITT.js.map → run-LHMYTKV7.js.map} +0 -0
package/README.md
CHANGED
|
@@ -153,7 +153,7 @@ Verified the flow in agent-browser already? `vos actions from-agent-browser step
|
|
|
153
153
|
|
|
154
154
|
**Contracts that bite.** Time is source seconds in `zoom`, `segments`, `speed` and `tilt`, and output seconds in `overlays` and `audio`. Zoom `cx`/`cy`, overlay `transform.x`/`y` and a pin's `rect` are normalized fractions of the frame in `[0, 1]` (`0.5, 0.5` is the centre), never pixels. `level` is 1..5. The planners write `source: "auto"`; spans you add or edit carry `source: "manual"`, which a re-plan never touches, and a deleted automatic span is recorded in `doc.rejected` so a re-plan does not bring it back.
|
|
155
155
|
|
|
156
|
-
**Camera styles.** `doc.zoomStyle` is one of `glide` (default), `focus
|
|
156
|
+
**Camera styles.** `doc.zoomStyle` is one of `glide` (default: one steady zoom that travels between clicks), `focus` (a zoom per click, framed and released), `cinema` (slow moves and long holds), `snappy` (quick cycles), `cut` (instant zoom-ins) or `none`. Every style times the tilt track to its own zoom tempo, so a lean lands with its zoom; how far the card leans is `tiltStyle` (`off` | `subtle` | `medium` | `strong`), which the studio stamps when a style is picked and `doc.json` sets itself, with `tilt` spans. `keynote` and `drift` are retired names that still read: `glide` + `tiltStyle: medium` and `cinema` + `tiltStyle: subtle`.
|
|
157
157
|
|
|
158
158
|
**Tilt.** `doc.tilt`: source-anchored, non-overlapping spans where the card leans to a pose and returns to rest: `[{ "id": "u0", "in": 4, "out": 8, "rx": 6, "ry": -9, "source": "manual" }]`. `rx`/`ry` are degrees (±45 hard limit; ±5..18 reads well); `+rx` brings the top edge toward the camera, `+ry` the left edge, so a lean toward a right-side focus is a negative `ry`. Rest is flat. `doc.tiltStyle` (`off`, `subtle`, `medium`, `strong`) is the intensity the planner derives automatic spans from.
|
|
159
159
|
|
|
@@ -207,6 +207,7 @@ import {
|
|
|
207
207
|
TEXT_ENTER_KINDS,
|
|
208
208
|
TEXT_EXIT_KINDS,
|
|
209
209
|
TILT_DEG_MAX,
|
|
210
|
+
RETIRED_ZOOM_STYLES,
|
|
210
211
|
TILT_SPAN_MIN,
|
|
211
212
|
ZOOM_LEVEL_MAX,
|
|
212
213
|
ZOOM_LEVEL_MIN,
|
|
@@ -666,6 +667,12 @@ function lintDoc(docIn) {
|
|
|
666
667
|
);
|
|
667
668
|
}
|
|
668
669
|
}
|
|
670
|
+
if (typeof doc.zoomStyle === "string" && doc.zoomStyle in RETIRED_ZOOM_STYLES) {
|
|
671
|
+
const r = RETIRED_ZOOM_STYLES[doc.zoomStyle];
|
|
672
|
+
warnings.push(
|
|
673
|
+
`zoomStyle "${doc.zoomStyle}" is retired: it reads as "${r.style}" with tiltStyle "${r.tilt}" (write those instead)`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
669
676
|
if (doc.tiltStyle !== void 0 && !["off", "subtle", "medium", "strong"].includes(doc.tiltStyle)) {
|
|
670
677
|
problems.push(
|
|
671
678
|
`tiltStyle must be "off" | "subtle" | "medium" | "strong" (got ${String(doc.tiltStyle)})`
|
|
@@ -3869,9 +3876,9 @@ function proposeTransitions(input, kind) {
|
|
|
3869
3876
|
for (const step of steps) {
|
|
3870
3877
|
if (!step.navigated || step.skipped) continue;
|
|
3871
3878
|
const name = step.id ?? String(step.step);
|
|
3872
|
-
const
|
|
3873
|
-
const outEnd = round3(
|
|
3874
|
-
const inStart = round3(
|
|
3879
|
+
const wait2 = step.do === "wait";
|
|
3880
|
+
const outEnd = round3(wait2 ? step.tStart : step.tEnd);
|
|
3881
|
+
const inStart = round3(wait2 ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
|
|
3875
3882
|
const segs = doc.segments;
|
|
3876
3883
|
if (segs.some(
|
|
3877
3884
|
(s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
|
|
@@ -6158,8 +6165,8 @@ async function clockMotion(dur, at2, clock) {
|
|
|
6158
6165
|
await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
|
|
6159
6166
|
samples++;
|
|
6160
6167
|
const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
|
|
6161
|
-
const
|
|
6162
|
-
if (
|
|
6168
|
+
const wait2 = Math.min(next, start + dur) - clock.now();
|
|
6169
|
+
if (wait2 > 0) await clock.sleep(wait2);
|
|
6163
6170
|
}
|
|
6164
6171
|
await at2(1);
|
|
6165
6172
|
return samples + 1;
|
|
@@ -6169,8 +6176,8 @@ async function clockTyping(chars, delay, type, clock) {
|
|
|
6169
6176
|
for (let i = 0; i < chars.length; i++) {
|
|
6170
6177
|
await type(chars[i], i);
|
|
6171
6178
|
const due = start + (i + 1) * delay;
|
|
6172
|
-
const
|
|
6173
|
-
if (
|
|
6179
|
+
const wait2 = due - clock.now();
|
|
6180
|
+
if (wait2 > 0 && i < chars.length - 1) await clock.sleep(wait2);
|
|
6174
6181
|
}
|
|
6175
6182
|
}
|
|
6176
6183
|
function askedMs(step) {
|
|
@@ -7378,7 +7385,7 @@ async function cmdMove(argv) {
|
|
|
7378
7385
|
});
|
|
7379
7386
|
const key = requireCredential(strFlag(flags, "key"));
|
|
7380
7387
|
const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
|
|
7381
|
-
let
|
|
7388
|
+
let failed2 = 0;
|
|
7382
7389
|
const moved = [];
|
|
7383
7390
|
for (const raw of positionals) {
|
|
7384
7391
|
const id = parseVosId(raw);
|
|
@@ -7393,28 +7400,28 @@ async function cmdMove(argv) {
|
|
|
7393
7400
|
continue;
|
|
7394
7401
|
}
|
|
7395
7402
|
if (asVos.status === 404) {
|
|
7396
|
-
const
|
|
7403
|
+
const asAsset2 = await apiJson(origin, `/api/assets/${id}`, {
|
|
7397
7404
|
method: "PATCH",
|
|
7398
7405
|
key,
|
|
7399
7406
|
body: { folderId }
|
|
7400
7407
|
});
|
|
7401
|
-
if (
|
|
7408
|
+
if (asAsset2.status === 200) {
|
|
7402
7409
|
moved.push({ id, kind: "asset" });
|
|
7403
7410
|
r.log(`moved asset ${id}`);
|
|
7404
7411
|
continue;
|
|
7405
7412
|
}
|
|
7406
|
-
|
|
7407
|
-
r.log(apiError(`move ${id}`,
|
|
7413
|
+
failed2++;
|
|
7414
|
+
r.log(apiError(`move ${id}`, asAsset2));
|
|
7408
7415
|
continue;
|
|
7409
7416
|
}
|
|
7410
|
-
|
|
7417
|
+
failed2++;
|
|
7411
7418
|
r.log(apiError(`move ${id}`, asVos));
|
|
7412
7419
|
}
|
|
7413
7420
|
r.done(
|
|
7414
|
-
{ moved, failed, folderId },
|
|
7415
|
-
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${
|
|
7421
|
+
{ moved, failed: failed2, folderId },
|
|
7422
|
+
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed2 ? ` \u2014 ${failed2} failed` : ""}`
|
|
7416
7423
|
);
|
|
7417
|
-
return
|
|
7424
|
+
return failed2 ? EXIT_ERROR : EXIT_OK;
|
|
7418
7425
|
}
|
|
7419
7426
|
function recipeLine(filename, body) {
|
|
7420
7427
|
const h = recipeHints(body);
|
|
@@ -7560,6 +7567,144 @@ async function cmdFolder(argv) {
|
|
|
7560
7567
|
}
|
|
7561
7568
|
}
|
|
7562
7569
|
|
|
7570
|
+
// src/plugin/uploadAsset.ts
|
|
7571
|
+
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
7572
|
+
var PART_ATTEMPTS = 3;
|
|
7573
|
+
function planParts(totalBytes, partBytes) {
|
|
7574
|
+
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
7575
|
+
const parts = [];
|
|
7576
|
+
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
7577
|
+
parts.push({
|
|
7578
|
+
partNumber: parts.length + 1,
|
|
7579
|
+
start,
|
|
7580
|
+
end: Math.min(start + partBytes, totalBytes)
|
|
7581
|
+
});
|
|
7582
|
+
}
|
|
7583
|
+
return parts;
|
|
7584
|
+
}
|
|
7585
|
+
function failed(what, r) {
|
|
7586
|
+
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
7587
|
+
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
7588
|
+
}
|
|
7589
|
+
function asAsset(body) {
|
|
7590
|
+
return {
|
|
7591
|
+
id: String(body.id),
|
|
7592
|
+
url: String(body.url),
|
|
7593
|
+
size: typeof body.size === "number" ? body.size : 0,
|
|
7594
|
+
reused: body.reused === true
|
|
7595
|
+
};
|
|
7596
|
+
}
|
|
7597
|
+
function transient(status) {
|
|
7598
|
+
return status >= 500 || status === 429;
|
|
7599
|
+
}
|
|
7600
|
+
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7601
|
+
async function uploadAsset(target, bytes, opts) {
|
|
7602
|
+
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
7603
|
+
}
|
|
7604
|
+
async function uploadWhole(target, bytes, opts) {
|
|
7605
|
+
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
7606
|
+
method: "POST",
|
|
7607
|
+
key: target.key,
|
|
7608
|
+
headers: {
|
|
7609
|
+
"Content-Type": opts.contentType,
|
|
7610
|
+
"Content-Length": String(bytes.length),
|
|
7611
|
+
"X-Filename": opts.filename,
|
|
7612
|
+
"X-Content-Hash": opts.contentHash,
|
|
7613
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
|
|
7614
|
+
},
|
|
7615
|
+
raw: bytes
|
|
7616
|
+
});
|
|
7617
|
+
if (res.status !== 201 && res.status !== 200)
|
|
7618
|
+
throw failed("upload failed", res);
|
|
7619
|
+
return asAsset(res.body);
|
|
7620
|
+
}
|
|
7621
|
+
async function uploadInParts(target, bytes, opts) {
|
|
7622
|
+
const begun = await apiJson(
|
|
7623
|
+
target.origin,
|
|
7624
|
+
"/api/assets/recording/multipart",
|
|
7625
|
+
{
|
|
7626
|
+
method: "POST",
|
|
7627
|
+
key: target.key,
|
|
7628
|
+
body: {
|
|
7629
|
+
filename: opts.filename,
|
|
7630
|
+
contentType: opts.contentType,
|
|
7631
|
+
size: bytes.length,
|
|
7632
|
+
contentHash: opts.contentHash,
|
|
7633
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
);
|
|
7637
|
+
if (begun.status !== 201 && begun.status !== 200) {
|
|
7638
|
+
throw failed("upload failed", begun);
|
|
7639
|
+
}
|
|
7640
|
+
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
7641
|
+
return asAsset(begun.body);
|
|
7642
|
+
}
|
|
7643
|
+
const uploadId = String(begun.body.id);
|
|
7644
|
+
const partBytes = Number(begun.body.partBytes);
|
|
7645
|
+
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
7646
|
+
throw new Error("upload failed: the platform returned no part size");
|
|
7647
|
+
}
|
|
7648
|
+
const parts = planParts(bytes.length, partBytes);
|
|
7649
|
+
const etags = [];
|
|
7650
|
+
try {
|
|
7651
|
+
for (const part of parts) {
|
|
7652
|
+
const chunk2 = bytes.subarray(part.start, part.end);
|
|
7653
|
+
let sent = null;
|
|
7654
|
+
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
7655
|
+
try {
|
|
7656
|
+
sent = await apiJson(
|
|
7657
|
+
target.origin,
|
|
7658
|
+
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
7659
|
+
{
|
|
7660
|
+
method: "PUT",
|
|
7661
|
+
key: target.key,
|
|
7662
|
+
headers: {
|
|
7663
|
+
"Content-Type": "application/octet-stream",
|
|
7664
|
+
"Content-Length": String(chunk2.length)
|
|
7665
|
+
},
|
|
7666
|
+
raw: chunk2
|
|
7667
|
+
}
|
|
7668
|
+
);
|
|
7669
|
+
} catch (networkError) {
|
|
7670
|
+
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
7671
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
7672
|
+
continue;
|
|
7673
|
+
}
|
|
7674
|
+
if (sent.status === 200) break;
|
|
7675
|
+
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
7676
|
+
throw failed(`part ${part.partNumber} failed`, sent);
|
|
7677
|
+
}
|
|
7678
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
7679
|
+
}
|
|
7680
|
+
if (!sent || sent.status !== 200) {
|
|
7681
|
+
throw new Error(`part ${part.partNumber} failed`);
|
|
7682
|
+
}
|
|
7683
|
+
etags.push({
|
|
7684
|
+
partNumber: Number(sent.body.partNumber),
|
|
7685
|
+
etag: String(sent.body.etag)
|
|
7686
|
+
});
|
|
7687
|
+
opts.onPart?.(etags.length, parts.length);
|
|
7688
|
+
}
|
|
7689
|
+
const sealed = await apiJson(
|
|
7690
|
+
target.origin,
|
|
7691
|
+
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
7692
|
+
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
7693
|
+
);
|
|
7694
|
+
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
7695
|
+
throw failed("upload failed", sealed);
|
|
7696
|
+
}
|
|
7697
|
+
return asAsset(sealed.body);
|
|
7698
|
+
} catch (err) {
|
|
7699
|
+
await apiJson(
|
|
7700
|
+
target.origin,
|
|
7701
|
+
`/api/assets/recording/multipart/${uploadId}`,
|
|
7702
|
+
{ method: "DELETE", key: target.key }
|
|
7703
|
+
).catch(() => void 0);
|
|
7704
|
+
throw err;
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
|
|
7563
7708
|
// src/plugin/sync.ts
|
|
7564
7709
|
function apiContext(flags) {
|
|
7565
7710
|
return {
|
|
@@ -7642,31 +7787,25 @@ async function pushTake(dir, flags, r) {
|
|
|
7642
7787
|
const bytes = await readFile13(take.paths.recording);
|
|
7643
7788
|
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7644
7789
|
r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
"
|
|
7650
|
-
|
|
7651
|
-
"X-Content-Hash": hash,
|
|
7790
|
+
let upload;
|
|
7791
|
+
try {
|
|
7792
|
+
upload = await uploadAsset(ctx, new Uint8Array(bytes), {
|
|
7793
|
+
filename: RECORDING_NAME,
|
|
7794
|
+
contentType: "video/webm",
|
|
7795
|
+
contentHash: hash,
|
|
7652
7796
|
// The take's length so the server holds it to the plan's cap.
|
|
7653
|
-
...take.meta.durationMs > 0 ? {
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
})
|
|
7657
|
-
if (upload.status !== 201 && upload.status !== 200) {
|
|
7797
|
+
...take.meta.durationMs > 0 ? { durationSeconds: take.meta.durationMs / 1e3 } : {},
|
|
7798
|
+
onPart: (done, total) => r.log(` part ${done}/${total}`)
|
|
7799
|
+
});
|
|
7800
|
+
} catch (e) {
|
|
7658
7801
|
throw new Error(
|
|
7659
|
-
`recording upload failed
|
|
7802
|
+
`recording upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7660
7803
|
);
|
|
7661
7804
|
}
|
|
7662
|
-
const assetId =
|
|
7663
|
-
const assetUrl =
|
|
7664
|
-
r.event({
|
|
7665
|
-
|
|
7666
|
-
assetId,
|
|
7667
|
-
reused: upload.json.reused === true
|
|
7668
|
-
});
|
|
7669
|
-
if (upload.json.reused === true) r.log(" recording already hosted \u2014 reused");
|
|
7805
|
+
const assetId = upload.id;
|
|
7806
|
+
const assetUrl = upload.url;
|
|
7807
|
+
r.event({ event: "recording", assetId, reused: upload.reused });
|
|
7808
|
+
if (upload.reused) r.log(" recording already hosted \u2014 reused");
|
|
7670
7809
|
const docForPush = structuredClone(take.doc);
|
|
7671
7810
|
docForPush.source.videoKey = assetUrl;
|
|
7672
7811
|
if (docForPush.source.camKey && !/^(https?:)?\//.test(docForPush.source.camKey)) {
|
|
@@ -7686,24 +7825,21 @@ async function pushTake(dir, flags, r) {
|
|
|
7686
7825
|
const media = await readFile13(file);
|
|
7687
7826
|
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
7688
7827
|
const type = mediaContentType(file);
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
raw: new Uint8Array(media)
|
|
7698
|
-
});
|
|
7699
|
-
if (put.status !== 201 && put.status !== 200) {
|
|
7828
|
+
let put;
|
|
7829
|
+
try {
|
|
7830
|
+
put = await uploadAsset(ctx, new Uint8Array(media), {
|
|
7831
|
+
filename: basename(file),
|
|
7832
|
+
contentType: type,
|
|
7833
|
+
contentHash: mediaHash
|
|
7834
|
+
});
|
|
7835
|
+
} catch (e) {
|
|
7700
7836
|
throw new Error(
|
|
7701
|
-
`${ref.where} upload failed
|
|
7837
|
+
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7702
7838
|
);
|
|
7703
7839
|
}
|
|
7704
|
-
ref.set(
|
|
7840
|
+
ref.set(put.url);
|
|
7705
7841
|
r.log(
|
|
7706
|
-
` ${ref.where}: ${ref.key} \u2192 asset ${
|
|
7842
|
+
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
7707
7843
|
);
|
|
7708
7844
|
}
|
|
7709
7845
|
const lowered = lowerToComposition3(docForPush);
|
|
@@ -11239,4 +11375,4 @@ export {
|
|
|
11239
11375
|
convertAgentBrowser,
|
|
11240
11376
|
run
|
|
11241
11377
|
};
|
|
11242
|
-
//# sourceMappingURL=chunk-
|
|
11378
|
+
//# sourceMappingURL=chunk-MIZWK5AN.js.map
|