@vosjs/cli 0.28.1 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -161,6 +161,8 @@ Verified the flow in agent-browser already? `vos actions from-agent-browser step
161
161
 
162
162
  **Image and video overlays.** Media kinds on the same lane: `{ "id": "m0", "kind": "image" | "video", "start": 2, "duration": 4, "key": "/logo.png", "width": 0.35, "radius": 12, "opacity": 1, "loop": false, "transform": { … } }`. `key` is a file inside the take directory (`"/logo.png"`) or a URL; `width` is a fraction of the frame width, height follows the media's aspect; corners in design px. Video time is clip-local and muted by design; soundtracks belong to `doc.audio`.
163
163
 
164
+ **HTML layers.** A UI component authored as DOM on the same lane, sharp at any export size because it is rasterized from source, not magnified from the footage: `{ "id": "h0", "kind": "html", "start": 2, "duration": 4, "html": "<div class=\"card\">Open Code</div>", "css": ".card{font-family:'Inter';background:#0b0b0d;border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45)}", "box": { "width": 452, "height": 132 }, "transform": { … } }`. The markup is well-formed XML (`<br />`, `&amp;`, quoted attributes; `vos validate` refuses the rest in words, because a malformed layer decodes to nothing and says nothing). `box` is the design box in 1080p px; `width` defaults to the design size. The CSS is the chrome (shadow, radius, border; `bleed` is derived from the shadows, or stated), a `font-family` it names resolves against the hosted catalog at every `font-weight` it names (a face outside the catalog rides `fonts: [{ family, url }]`), an image inside must be a `data:` URI, and an `animation` runs on the wall clock rather than the timeline, so animate the layer with `anim` and `motion`. It is never re-hosted and never an upload.
165
+
164
166
  **3D props.** `doc.objects`: world-space props between the card and the overlays. `{ "id": "p0", "asset": { "kind": "primitive", "shape": "knot", "color": "#ffb03a" }, "span": { "start": 1, "duration": 3 }, "transform3d": { "x": 0.8, "y": 0.28, "z": 0.5, "rx": 0, "ry": 40, "rz": 0, "scale": 0.18 }, "animation": "spin" }`. Shapes `cube`, `sphere`, `torus`, `knot`; `kind: "gltf"` with `key` loads a GLB (bounding-box normalized so `scale` means the same for every model; a bad model fails open). `x`/`y` are frame fractions, `z` world units toward the camera from the card plane, `scale` a fraction of the frame height; `animation` is `spin` or `float`, deterministic.
165
167
 
166
168
  **Audio.** `doc.audio` clips (music, effects, `key` a take-directory file or a URL) are mixed with gain, fade and loop envelopes and muxed into every full render: Opus for webm, AAC (Opus fallback) for mp4. Audio forces single-flight (`--parallel` is ignored with a note); a `--range` render keeps its audio.
@@ -189,6 +189,7 @@ import {
189
189
  SEGMENT_EXIT_KINDS,
190
190
  enterOf,
191
191
  exitOf,
192
+ htmlLayerProblems,
192
193
  segmentOutputExtents,
193
194
  transitionSeconds,
194
195
  CARD_EXIT_KINDS,
@@ -926,9 +927,9 @@ function lintDoc(docIn) {
926
927
  problems.push(`${name} must be an object`);
927
928
  continue;
928
929
  }
929
- if (o.kind !== "text" && o.kind !== "image" && o.kind !== "video") {
930
+ if (o.kind !== "text" && o.kind !== "image" && o.kind !== "video" && o.kind !== "html") {
930
931
  problems.push(
931
- `${name}.kind must be "text" | "image" | "video" (got ${String(o.kind)})`
932
+ `${name}.kind must be "text" | "image" | "video" | "html" (got ${String(o.kind)})`
932
933
  );
933
934
  }
934
935
  if (!isNum(o.start) || o.start < 0)
@@ -1105,6 +1106,55 @@ function lintDoc(docIn) {
1105
1106
  problems.push(`${name}.opacity must be 0..1`);
1106
1107
  }
1107
1108
  }
1109
+ if (o.kind === "html") {
1110
+ if (typeof o.html !== "string") {
1111
+ problems.push(`${name}.html must be the layer's markup (a string)`);
1112
+ }
1113
+ if (o.css !== void 0 && typeof o.css !== "string") {
1114
+ problems.push(`${name}.css must be a string of CSS`);
1115
+ }
1116
+ const box = isObj(o.box) ? o.box : void 0;
1117
+ if (!box || !isNum(box.width) || !isNum(box.height) || box.width <= 0 || box.height <= 0) {
1118
+ problems.push(
1119
+ `${name}.box must be { width, height } in design px, both > 0 (the 1080p frame is 1920 wide)`
1120
+ );
1121
+ }
1122
+ if (o.bleed !== void 0 && (!isNum(o.bleed) || o.bleed < 0)) {
1123
+ problems.push(
1124
+ `${name}.bleed must be \u2265 0 design px (absent = derived from the CSS's shadows)`
1125
+ );
1126
+ }
1127
+ if (o.fonts !== void 0) {
1128
+ if (!Array.isArray(o.fonts)) {
1129
+ problems.push(
1130
+ `${name}.fonts must be an array of { family, url, weight?, style? }`
1131
+ );
1132
+ } else {
1133
+ o.fonts.forEach((f, fi) => {
1134
+ if (!isObj(f) || typeof f.family !== "string" || typeof f.url !== "string" || !/^https?:\/\//.test(f.url))
1135
+ problems.push(
1136
+ `${name}.fonts[${fi}] must be { family, url (https), weight?, style? }: a face the catalog does not host. A hosted family needs no entry; name it in the CSS`
1137
+ );
1138
+ });
1139
+ }
1140
+ }
1141
+ if (o.width !== void 0 && (!isNum(o.width) || o.width <= 0 || o.width > 1)) {
1142
+ problems.push(
1143
+ `${name}.width must be a fraction of the frame width in (0..1] (got ${String(o.width)}; absent = the design size)`
1144
+ );
1145
+ }
1146
+ if (o.opacity !== void 0 && (!isNum(o.opacity) || o.opacity < 0 || o.opacity > 1)) {
1147
+ problems.push(`${name}.opacity must be 0..1`);
1148
+ }
1149
+ if (typeof o.html === "string") {
1150
+ for (const p of htmlLayerProblems(o)) {
1151
+ ;
1152
+ (p.level === "problem" ? problems : warnings).push(
1153
+ `${name}: ${p.message}`
1154
+ );
1155
+ }
1156
+ }
1157
+ }
1108
1158
  const tf = isObj(o.transform) ? o.transform : void 0;
1109
1159
  if (!tf) {
1110
1160
  problems.push(
@@ -6515,6 +6565,7 @@ import { basename, join as join15 } from "path";
6515
6565
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
6516
6566
 
6517
6567
  // src/plugin/media.ts
6568
+ import { isKeyedOverlay } from "@vosjs/studio-core";
6518
6569
  import { createWriteStream, existsSync as existsSync11 } from "fs";
6519
6570
  import { writeFile as writeFile9 } from "fs/promises";
6520
6571
  import { join as join13 } from "path";
@@ -6562,7 +6613,7 @@ function docMediaRefs(doc, keep = isTakeRelativeKey) {
6562
6613
  });
6563
6614
  }
6564
6615
  for (const clip of doc.overlays ?? []) {
6565
- if (clip.kind === "text" || !keep(clip.key)) continue;
6616
+ if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
6566
6617
  out.push({
6567
6618
  where: `overlay ${clip.id}`,
6568
6619
  key: clip.key,
@@ -7355,7 +7406,7 @@ import {
7355
7406
  migrateConfig,
7356
7407
  vosConfigJsonSchema
7357
7408
  } from "@vosjs/core";
7358
- import { migrateHostedDoc as migrateHostedDoc4 } from "@vosjs/studio-core";
7409
+ import { lowerProgramDoc, migrateHostedDoc as migrateHostedDoc4 } from "@vosjs/studio-core";
7359
7410
 
7360
7411
  // src/plugin/login.ts
7361
7412
  import { hostname } from "os";
@@ -7653,6 +7704,7 @@ async function createProgramVos(opts) {
7653
7704
  if (opts.remixOfId) body.remixOfId = opts.remixOfId;
7654
7705
  if (opts.label) body.label = opts.label;
7655
7706
  if (opts.note) body.note = opts.note;
7707
+ if (opts.doc) body.doc = opts.doc;
7656
7708
  const res = await apiJson(opts.origin, "/api/vos", {
7657
7709
  method: "POST",
7658
7710
  key: opts.key,
@@ -7753,7 +7805,10 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
7753
7805
  }
7754
7806
  if (vosId) {
7755
7807
  const trackedBase = state && state.vosId === vosId && state.versionId ? state.versionId : void 0;
7756
- const body = { config, client: clientId() };
7808
+ const body = {
7809
+ config: storedProgramConfig(config, programDoc),
7810
+ client: clientId()
7811
+ };
7757
7812
  if (programDoc) body.doc = programDoc;
7758
7813
  const base = strFlag(flags, "base") ?? trackedBase;
7759
7814
  if (base) body.baseVersionId = base;
@@ -7818,7 +7873,12 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
7818
7873
  const created = await createProgramVos({
7819
7874
  origin,
7820
7875
  key,
7821
- config,
7876
+ config: storedProgramConfig(config, programDoc),
7877
+ // The document rode the version path and not the create path, so a
7878
+ // fresh push of a program directory silently dropped its doc.json
7879
+ // (found seeding one-layer members: `--with` then said the vos carried
7880
+ // no doc).
7881
+ doc: programDoc ?? void 0,
7822
7882
  title,
7823
7883
  slug: strFlag(flags, "slug"),
7824
7884
  description: desc,
@@ -8015,6 +8075,11 @@ async function storeKey(key, origin, r) {
8015
8075
  function isTakeDir(target) {
8016
8076
  return directoryKind(target) === "take";
8017
8077
  }
8078
+ function storedProgramConfig(config, programDoc) {
8079
+ if (!programDoc) return config;
8080
+ const doc = { audio: [], ...programDoc };
8081
+ return lowerProgramDoc(doc).config;
8082
+ }
8018
8083
  async function readProgramDoc(dir, config) {
8019
8084
  const docPath = join16(dir, "doc.json");
8020
8085
  if (!existsSync13(docPath)) return null;
@@ -10536,4 +10601,4 @@ export {
10536
10601
  convertAgentBrowser,
10537
10602
  run
10538
10603
  };
10539
- //# sourceMappingURL=chunk-T35XY4YW.js.map
10604
+ //# sourceMappingURL=chunk-LST5JGI5.js.map