@vosjs/cli 0.65.3 → 0.66.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 CHANGED
@@ -263,7 +263,7 @@ Verified the flow in agent-browser already? `vos actions from-agent-browser step
263
263
 
264
264
  **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.
265
265
 
266
- **Text overlays.** `doc.overlays`: screen-space clips above the card, outside the zoom, output-anchored. `{ "id": "t0", "kind": "text", "start": 1, "duration": 3, "text": "Ship it", "preset": "title", "transform": { "x": 0.5, "y": 0.82, "scale": 1, "rotation": 0 }, "enter": "rise", "exit": "fade" }`. Presets `title`, `caption`, `label`, overridable with `size` (12..200 design px) and `color`; `\n` breaks lines; a caption (a layer with no referent) is a lower third at `y` ≈ 0.82. Enter and exit: `rise`, `fade`, `none`. Fonts load at render start, fail-open to system stacks.
266
+ **Text overlays.** `doc.overlays`: screen-space clips above the card, outside the zoom, output-anchored. `{ "id": "t0", "kind": "text", "start": 1, "duration": 3, "text": "Ship it", "preset": "title", "transform": { "x": 0.5, "y": 0.82, "scale": 1, "rotation": 0 }, "enter": "rise", "exit": "fade" }`. Presets `title`, `caption`, `label`, overridable with `size` (12..200 design px) and `color`; `\n` breaks lines; a caption (a layer with no referent) is a lower third at `y` ≈ 0.82. Enter and exit: `rise`, `fade`, `none`. Fonts load at render start, fail-open to system stacks. **Styled words are runs.** `text` is a string, or a list of runs when parts of it are set differently: `"text": [{ "text": "Ship " }, { "text": "faster", "weight": 700, "underline": true }]`. A run is a piece of text plus the fields in which it departs from the layer: `weight` (700 is bold; snapped to what the family hosts), `italic`, `color`, `underline`, `strike`, `highlight` (a colour behind it). There is no markup in the words (an asterisk is an asterisk), a style may start and end anywhere, a run may hold `\n`, and a per-word or per-character entrance keeps every unit in its own style. `vos validate` names a run field it does not know and prints the runs to write for a layer still using the retired `emphasis`.
267
267
 
268
268
  **Callouts in the grammar.** `vos callout <take> note --step copy --kicker "index.css" --title "Every token you tuned, as CSS variables." --mark ring` writes a callout in the house grammar (three shapes a viewer learns once: `note`, a kicker, a title and a line; `tag`, one label on the accent; `code`, the payload block) from the product's REGISTER: `BRAND.md` beside the take (`bgA` the ground, `accent`, `fontBody`; a kit whose accent is RESERVED, a recorder's red that marks time and nothing else, names a `callout` role and the notes speak in that hue instead) or `--ground #hex --accent #hex --font "…"`. The card inverts the app's value in the product's hue (a light app gets a dark card, a dark app a light one), keeps the accent for the kicker, sets its title at 1.35× the app's body AS SEEN ON SCREEN at the layer's start (the camera's level is read from the document; `--body-px <n>` instead sets the body size on the DELIVERED frame and is never rescaled, which is the flag a rich capture needs, since a 2560-wide take on a padded frame sits near scale 0.6 and lands every default on the grammar's floor; the verb prints the three sizes and the scale it measured), lifts on a real shadow and a hairline, rises in and fades out, opens a beat after the step's press lands and closes before the next scroll or navigation, and is pinned to the step (`--side`, `--mark`, `--leader`, `--color` refine the pin; `--color` paints the mark and the leader, never the kicker, which is the accent). `--at <s> --seconds <n>` places one without a step; `--print` prints the clip instead of writing it. `vos validate <take> --picture` renders the footage under every html layer at its start and reports the ΔE between the card's ground and what it covers: under 8 is a problem (the card reads as one more panel), under 16 a warning. An unpinned html or media layer whose window holds a step with an element gets the pin named in a warning.
269
269
 
@@ -4,12 +4,12 @@ import {
4
4
  localManifestRefs,
5
5
  pullManifest,
6
6
  uploadManifest
7
- } from "./chunk-7CIAU4QP.js";
8
- import "./chunk-WVQAZTYU.js";
7
+ } from "./chunk-63KINQOJ.js";
8
+ import "./chunk-UQLC4BEZ.js";
9
9
  export {
10
10
  hostedLiteralWarnings,
11
11
  localManifestRefs,
12
12
  pullManifest,
13
13
  uploadManifest
14
14
  };
15
- //# sourceMappingURL=assetManifest-RQYJYQ7Q.js.map
15
+ //# sourceMappingURL=assetManifest-RDHHKAAM.js.map
@@ -9,7 +9,7 @@ import {
9
9
  manifestRefs,
10
10
  nameForType,
11
11
  resolveMediaType
12
- } from "./chunk-WVQAZTYU.js";
12
+ } from "./chunk-UQLC4BEZ.js";
13
13
 
14
14
  // src/plugin/assetManifest.ts
15
15
  import {
@@ -700,4 +700,4 @@ export {
700
700
  pullManifest,
701
701
  hostedLiteralWarnings
702
702
  };
703
- //# sourceMappingURL=chunk-7CIAU4QP.js.map
703
+ //# sourceMappingURL=chunk-63KINQOJ.js.map
@@ -9,7 +9,7 @@ import {
9
9
  reencodeStill,
10
10
  renderStill,
11
11
  videoFormat
12
- } from "./chunk-DU3ANWU4.js";
12
+ } from "./chunk-TYGDQEEI.js";
13
13
  import {
14
14
  PREV_DOC_NAME,
15
15
  RECORDING_NAME,
@@ -33,7 +33,7 @@ import {
33
33
  uploadDocRefs,
34
34
  uploadManifest,
35
35
  writeJson
36
- } from "./chunk-7CIAU4QP.js";
36
+ } from "./chunk-63KINQOJ.js";
37
37
  import {
38
38
  CREDENTIALS_PATH,
39
39
  MEDIA_HEAD_BYTES,
@@ -44,6 +44,7 @@ import {
44
44
  clientId,
45
45
  containerMismatch,
46
46
  deriveSlug,
47
+ detailLine,
47
48
  formatChanges,
48
49
  hasFlag,
49
50
  hostedAssetId,
@@ -64,7 +65,7 @@ import {
64
65
  strFlag,
65
66
  writeCredential,
66
67
  writeSyncState
67
- } from "./chunk-WVQAZTYU.js";
68
+ } from "./chunk-UQLC4BEZ.js";
68
69
  import {
69
70
  deadLine,
70
71
  deadWarns
@@ -213,7 +214,12 @@ import {
213
214
  pinCandidates,
214
215
  pinRectOnScreen,
215
216
  pinReferent,
216
- parseEmphasis,
217
+ marksEmphasis,
218
+ migrateTextClip,
219
+ plainText,
220
+ resolveOverlayStyle,
221
+ snapRunWeight,
222
+ RUN_STYLE_KEYS,
217
223
  ratedSegments,
218
224
  recommendedExportResolution,
219
225
  resolvePins,
@@ -243,7 +249,7 @@ function wordsOf(text) {
243
249
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((x) => x.length >= 3 && !LABEL_STOPWORDS.has(x));
244
250
  }
245
251
  function layerWords(o) {
246
- const src = typeof o.html === "string" ? o.html.replace(/<[^>]*>/g, " ") : typeof o.text === "string" ? o.text : "";
252
+ const src = typeof o.html === "string" ? o.html.replace(/<[^>]*>/g, " ") : typeof o.text === "string" || Array.isArray(o.text) ? plainText(o.text) : "";
247
253
  return new Set(wordsOf(src));
248
254
  }
249
255
  function selectorLabelWords(selector) {
@@ -1062,28 +1068,75 @@ function lintDoc(docIn) {
1062
1068
  }
1063
1069
  checkPin(o, name, docIn, recording, problems, warnings);
1064
1070
  if (o.kind === "text") {
1065
- if (typeof o.text !== "string")
1066
- problems.push(`${name}.text must be a string`);
1071
+ if (Array.isArray(o.text)) {
1072
+ const HINTS = {
1073
+ bold: "weight: 700",
1074
+ fontWeight: "weight",
1075
+ strikethrough: "strike: true",
1076
+ background: "highlight",
1077
+ colour: "color"
1078
+ };
1079
+ o.text.forEach((run2, ri) => {
1080
+ const at2 = `${name}.text[${ri}]`;
1081
+ if (!run2 || typeof run2 !== "object" || Array.isArray(run2)) {
1082
+ problems.push(
1083
+ `${at2} must be a run: { text, weight?, italic?, color?, underline?, strike?, highlight? }`
1084
+ );
1085
+ return;
1086
+ }
1087
+ const r = run2;
1088
+ if (typeof r.text !== "string")
1089
+ problems.push(`${at2}.text must be a string`);
1090
+ else if (!r.text)
1091
+ warnings.push(`${at2} has no text and paints nothing; drop it`);
1092
+ for (const k of Object.keys(r)) {
1093
+ if (k === "text" || RUN_STYLE_KEYS.includes(k))
1094
+ continue;
1095
+ problems.push(
1096
+ `${at2}.${k} is not a run field (${RUN_STYLE_KEYS.join(", ")})${HINTS[k] ? `: write ${HINTS[k]}` : ""}`
1097
+ );
1098
+ }
1099
+ if (r.weight !== void 0 && (!isNum(r.weight) || r.weight < 100 || r.weight > 900))
1100
+ problems.push(`${at2}.weight must be 100..900`);
1101
+ for (const k of ["italic", "underline", "strike"]) {
1102
+ if (r[k] !== void 0 && typeof r[k] !== "boolean")
1103
+ problems.push(`${at2}.${k} must be true or false`);
1104
+ }
1105
+ for (const k of ["color", "highlight"]) {
1106
+ if (r[k] !== void 0 && typeof r[k] !== "string")
1107
+ problems.push(`${at2}.${k} must be a CSS colour`);
1108
+ }
1109
+ if (isNum(r.weight) && r.weight >= 100 && r.weight <= 900) {
1110
+ const layer = o;
1111
+ const own = resolveOverlayStyle({ ...layer, text: "" }).weight;
1112
+ if (r.weight !== own && snapRunWeight(layer, r.weight) === own)
1113
+ warnings.push(
1114
+ `${at2}.weight ${r.weight} paints no differently: the layer's family hosts no weight nearer to it than the layer's own ${own}`
1115
+ );
1116
+ }
1117
+ });
1118
+ } else if (typeof o.text !== "string") {
1119
+ problems.push(
1120
+ `${name}.text must be a string, or a list of runs ([{ text, weight?, italic?, color?, underline?, strike?, highlight? }])`
1121
+ );
1122
+ }
1067
1123
  if (typeof o.preset === "string" && !PRESETS.includes(o.preset)) {
1068
1124
  warnings.push(
1069
1125
  `${name}.preset "${String(o.preset)}" is not a known preset (${PRESETS.join("|")}) \u2014 falls back to "title"`
1070
1126
  );
1071
1127
  }
1072
- if (o.emphasis !== void 0) {
1073
- const em = o.emphasis;
1074
- if (!em || typeof em !== "object" || Array.isArray(em)) {
1075
- problems.push(
1076
- `${name}.emphasis must be { weight?, color? } ({} turns *markers* on)`
1077
- );
1078
- } else {
1079
- if (em.weight !== void 0 && (!isNum(em.weight) || em.weight < 100 || em.weight > 900))
1080
- problems.push(`${name}.emphasis.weight must be 100..900`);
1081
- if (em.color !== void 0 && typeof em.color !== "string")
1082
- problems.push(`${name}.emphasis.color must be a CSS colour`);
1083
- }
1084
- } else if (typeof o.text === "string" && parseEmphasis(o.text) !== null) {
1128
+ if (o.emphasis !== void 0 && typeof o.text === "string") {
1129
+ const runs = migrateTextClip(o).text;
1085
1130
  warnings.push(
1086
- `${name}.text has *marked* words but no emphasis, so the asterisks show as typed; add emphasis: {} to set them bold (\\* keeps one literal)`
1131
+ `${name}.emphasis is retired (styled words are runs now, with no markers in the text). It is still read, but write it as: "text": ${JSON.stringify(runs)} and drop "emphasis"`
1132
+ );
1133
+ } else if (typeof o.text === "string" && marksEmphasis(o.text)) {
1134
+ const runs = migrateTextClip({
1135
+ ...o,
1136
+ emphasis: {}
1137
+ }).text;
1138
+ warnings.push(
1139
+ `${name}.text has words between asterisks, which show as typed. To set them bold write runs: "text": ${JSON.stringify(runs)}`
1087
1140
  );
1088
1141
  }
1089
1142
  if (o.size !== void 0 && (!isNum(o.size) || o.size < 12 || o.size > 200)) {
@@ -4332,8 +4385,9 @@ import {
4332
4385
  computeCardLayout,
4333
4386
  docStillTime as docStillTime2,
4334
4387
  migrateHostedDoc,
4388
+ overlayPlainText,
4335
4389
  overlayRect,
4336
- resolveOverlayStyle
4390
+ resolveOverlayStyle as resolveOverlayStyle2
4337
4391
  } from "@vosjs/studio-core";
4338
4392
  var POSTER_CLASSES = [
4339
4393
  "landscape",
@@ -4452,7 +4506,7 @@ function posterTextBoxes(doc, px, time) {
4452
4506
  if (clip.kind !== "text") continue;
4453
4507
  if (time < clip.start || time > clip.start + clip.duration) continue;
4454
4508
  const rect = overlayRect(clip, estimateWidth, W, H);
4455
- const style = resolveOverlayStyle(clip);
4509
+ const style = resolveOverlayStyle2(clip);
4456
4510
  const r3 = (v) => Math.round(v * 1e3) / 1e3;
4457
4511
  out.push({
4458
4512
  x: r3((rect.cx - rect.w / 2) / W),
@@ -4460,7 +4514,7 @@ function posterTextBoxes(doc, px, time) {
4460
4514
  w: r3(rect.w / W),
4461
4515
  h: r3(rect.h / H),
4462
4516
  role: clip.preset === "title" ? "headline" : "body",
4463
- label: clip.text.split("\n")[0].slice(0, 40),
4517
+ label: overlayPlainText(clip).split("\n")[0].slice(0, 40),
4464
4518
  .../^#[0-9a-f]{6}$/i.test(style.color) ? { color: style.color } : {}
4465
4519
  });
4466
4520
  }
@@ -7128,16 +7182,6 @@ async function api(ctx, path, init) {
7128
7182
  const r = await apiJson(ctx.origin, `/api${path}`, { ...init, key: ctx.key });
7129
7183
  return { status: r.status, json: r.body };
7130
7184
  }
7131
- function detailLine(body) {
7132
- const details = body.details;
7133
- if (!Array.isArray(details) || !details.length) return "";
7134
- const lines = details.map((d) => {
7135
- const path = Array.isArray(d.path) ? d.path.join(".") : "";
7136
- return `${path ? `${path}: ` : ""}${String(d.message ?? JSON.stringify(d))}`;
7137
- });
7138
- return `
7139
- ${lines.join("\n ")}`;
7140
- }
7141
7185
  function printChanges(r, changes, protectedIds) {
7142
7186
  if (Array.isArray(changes)) {
7143
7187
  for (const entry of changes) {
@@ -7641,6 +7685,7 @@ import { existsSync as existsSync13, realpathSync } from "fs";
7641
7685
  function isLocalName(value) {
7642
7686
  if (/^(https?:|data:|blob:|asset:|media:)/.test(value)) return false;
7643
7687
  if (value.startsWith("$")) return false;
7688
+ if (value.startsWith("<svg") || value.startsWith("<?xml")) return false;
7644
7689
  if (value.startsWith("/")) return existsSync13(value);
7645
7690
  return true;
7646
7691
  }
@@ -9154,14 +9199,15 @@ async function cmdDelete(argv) {
9154
9199
  });
9155
9200
  if (res.status !== 200 && res.status !== 204)
9156
9201
  throw new Error(apiError(`delete ${vosId}`, res));
9202
+ const tracking = dir ?? trackingCwd(vosId);
9157
9203
  let unlinked = false;
9158
- if (dir) {
9159
- rmSync2(join16(dir, SYNC_STATE_NAME), { force: true });
9204
+ if (tracking) {
9205
+ rmSync2(join16(tracking, SYNC_STATE_NAME), { force: true });
9160
9206
  unlinked = true;
9161
9207
  }
9162
9208
  const trashed = res.body.trashed === true;
9163
9209
  const restoreUntil = typeof res.body.restoreUntil === "string" ? res.body.restoreUntil : null;
9164
- const tail = unlinked ? `; ${dir} no longer tracks it` : "";
9210
+ const tail = unlinked ? `; ${dir ?? "this directory"} no longer tracks it` : "";
9165
9211
  r.done(
9166
9212
  {
9167
9213
  id: vosId,
@@ -9179,6 +9225,11 @@ async function cmdDelete(argv) {
9179
9225
  );
9180
9226
  return EXIT_OK;
9181
9227
  }
9228
+ function trackingCwd(vosId) {
9229
+ const cwd = process.cwd();
9230
+ if (!existsSync15(join16(cwd, SYNC_STATE_NAME))) return null;
9231
+ return readSyncState(cwd)?.vosId === vosId ? cwd : null;
9232
+ }
9182
9233
 
9183
9234
  // src/plugin/buildProgram.ts
9184
9235
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
@@ -15095,4 +15146,4 @@ export {
15095
15146
  verbHelp,
15096
15147
  run
15097
15148
  };
15098
- //# sourceMappingURL=chunk-CODEEH2Z.js.map
15149
+ //# sourceMappingURL=chunk-SFU4U3GM.js.map