hyperframes 0.8.68 → 0.8.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.8.68" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.69" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -84131,17 +84131,17 @@ function mediaSrcTagRe(tagAlternation) {
84131
84131
  return new RegExp(`<(${tagAlternation})\\b[^>]*\\ssrc\\s*=\\s*["']([^"']+)["'][^>]*>`, "gi");
84132
84132
  }
84133
84133
  function normalized(source) {
84134
- let text2 = "";
84134
+ const chars2 = [];
84135
84135
  const offsets = [];
84136
+ let lastWasSpace = false;
84136
84137
  for (let i = 0; i < source.length; i++) {
84137
- const char = source[i];
84138
- if (/\s/.test(char)) {
84139
- if (text2.endsWith(" ")) continue;
84140
- text2 += " ";
84141
- } else text2 += char;
84138
+ const isSpace = /\s/.test(source[i]);
84139
+ if (isSpace && lastWasSpace) continue;
84140
+ chars2.push(isSpace ? " " : source[i]);
84142
84141
  offsets.push(i);
84142
+ lastWasSpace = isSpace;
84143
84143
  }
84144
- return { text: text2, offsets };
84144
+ return { text: chars2.join(""), offsets };
84145
84145
  }
84146
84146
  function createSourceLocator(source) {
84147
84147
  const tags = parseHtmlStructure(source).tags;
@@ -92408,6 +92408,15 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
92408
92408
  `FFmpeg exited successfully but produced no frames (start=${startTime}, duration=${duration})`
92409
92409
  );
92410
92410
  }
92411
+ if (framePaths.size > 0 && duration > 0 && !options.finalFrameOnly) {
92412
+ const expectedAtFps = extractionFrameCountForDuration(duration, normalizedFps, metadata.isVFR);
92413
+ if (expectedAtFps > 0 && framePaths.size <= expectedAtFps * 0.6) {
92414
+ const correctedDuration = startTime + framePaths.size / fps;
92415
+ if (correctedDuration < metadata.videoStreamDurationSeconds) {
92416
+ metadata.videoStreamDurationSeconds = correctedDuration;
92417
+ }
92418
+ }
92419
+ }
92411
92420
  return {
92412
92421
  videoId,
92413
92422
  srcPath: videoPath,
@@ -109389,14 +109398,6 @@ function decode(raw) {
109389
109398
  function encode2(json, quote) {
109390
109399
  return quote === "'" ? json.replace(/'/g, "&#39;") : json.replace(/"/g, "&quot;");
109391
109400
  }
109392
- function optionValues(decl) {
109393
- return decl.type === "enum" ? decl.options.map((option) => option.value) : null;
109394
- }
109395
- function rejectEnum(decl, value) {
109396
- const options = optionValues(decl);
109397
- if (!options) return null;
109398
- return options.includes(String(value)) ? null : `not one of ${options.join(", ")}`;
109399
- }
109400
109401
  function rejectNumber(decl, value) {
109401
109402
  if (decl.type !== "number") return null;
109402
109403
  const n3 = typeof value === "number" ? value : Number(value);
@@ -109406,7 +109407,12 @@ function rejectNumber(decl, value) {
109406
109407
  return null;
109407
109408
  }
109408
109409
  function reject(decl, value) {
109409
- return rejectEnum(decl, value) ?? rejectNumber(decl, value);
109410
+ if (decl.type === "number") return rejectNumber(decl, value);
109411
+ const [issue] = validateVariables({ [decl.id]: value }, [decl]);
109412
+ if (!issue) return null;
109413
+ if (issue.kind === "enum-out-of-range") return `not one of ${issue.allowed.join(", ")}`;
109414
+ if (issue.kind === "type-mismatch") return `expected ${issue.expected}, got ${issue.actual}`;
109415
+ return null;
109410
109416
  }
109411
109417
  function applyVariableDefaults(source, values) {
109412
109418
  const ids = Object.keys(values);
@@ -109436,7 +109442,7 @@ function applyVariableDefaults(source, values) {
109436
109442
  invalid.push({ id, reason });
109437
109443
  continue;
109438
109444
  }
109439
- updated.set(id, decl.type === "number" ? Number(value) : String(value));
109445
+ updated.set(id, decl.type === "number" ? Number(value) : value);
109440
109446
  applied.push(id);
109441
109447
  }
109442
109448
  const unknown = ids.filter((id) => !byId2.has(id));
@@ -109452,11 +109458,19 @@ ${body}
109452
109458
  const html = source.slice(0, found.start) + rewritten + source.slice(found.end);
109453
109459
  return { html, applied, unknown, invalid };
109454
109460
  }
109455
- var ATTR;
109461
+ var InvalidVariableValuesError, ATTR;
109456
109462
  var init_variableDefaults = __esm({
109457
109463
  "src/registry/variableDefaults.ts"() {
109458
109464
  "use strict";
109459
109465
  init_variables();
109466
+ InvalidVariableValuesError = class extends Error {
109467
+ constructor(invalid) {
109468
+ super(`--vars refused: ${invalid.map(({ id, reason }) => `${id}: ${reason}`).join("; ")}`);
109469
+ this.invalid = invalid;
109470
+ this.name = "InvalidVariableValuesError";
109471
+ }
109472
+ invalid;
109473
+ };
109460
109474
  ATTR = "data-composition-variables";
109461
109475
  }
109462
109476
  });
@@ -109465,7 +109479,9 @@ var init_variableDefaults = __esm({
109465
109479
  var installer_exports = {};
109466
109480
  __export(installer_exports, {
109467
109481
  hasLocalEdits: () => hasLocalEdits,
109468
- installItem: () => installItem
109482
+ installItem: () => installItem,
109483
+ prepareItem: () => prepareItem,
109484
+ publishItem: () => publishItem
109469
109485
  });
109470
109486
  import { createHash as createHash5 } from "crypto";
109471
109487
  import { existsSync as existsSync23, readFileSync as readFileSync14 } from "fs";
@@ -109498,6 +109514,9 @@ function hasLocalEdits(record2, target, onDisk) {
109498
109514
  if (!installed) return true;
109499
109515
  return installed !== digest(onDisk);
109500
109516
  }
109517
+ function keptByProject(record2, destPath, target, force) {
109518
+ return !force && existsSync23(destPath) && hasLocalEdits(record2, target, readFileSync14(destPath));
109519
+ }
109501
109520
  function assertSafeTarget(destDir, target) {
109502
109521
  if (isAbsolute6(target)) {
109503
109522
  throw new Error(`Unsafe target "${target}": absolute paths are not allowed.`);
@@ -109527,30 +109546,40 @@ function addRegistryItemMarker(source, item) {
109527
109546
  return `<!-- hyperframes-registry-item: ${item.name} -->
109528
109547
  ${source}`;
109529
109548
  }
109530
- async function installOneFile(item, file, destDir, baseUrl, record2, options, budget) {
109549
+ function bakeVariables(item, file, bytes, values) {
109550
+ const component = isInstalledComponentSnippet(item, file);
109551
+ if (!values || !(component || isInstalledRegistryBlockComposition(item, file))) {
109552
+ return { bytes, vars: null };
109553
+ }
109554
+ const vars = applyVariableDefaults(bytes.toString("utf8"), values);
109555
+ const rewrite = component && vars.applied.length > 0;
109556
+ return { bytes: rewrite ? Buffer.from(vars.html) : bytes, vars };
109557
+ }
109558
+ async function prepareOneFile(item, file, destDir, baseUrl, record2, options, budget) {
109531
109559
  const destPath = registryTargetPath(destDir, file.target);
109532
- if (!options.force && existsSync23(destPath) && hasLocalEdits(record2, file.target, readFileSync14(destPath))) {
109533
- return { destPath, target: file.target, preserved: true, hash: null, vars: null };
109560
+ if (keptByProject(record2, destPath, file.target, options.force)) {
109561
+ return { destPath, target: file.target, preserved: true, hash: null, vars: null, bytes: null };
109534
109562
  }
109535
109563
  let bytes = await fetchItemFile(item, file, baseUrl, budget);
109536
109564
  if (isInstalledRegistryBlockComposition(item, file)) {
109537
109565
  bytes = Buffer.from(addRegistryItemMarker(bytes.toString("utf8"), item));
109538
109566
  }
109539
- let vars = null;
109540
- if (options.variableValues && isInstalledComponentSnippet(item, file)) {
109541
- vars = applyVariableDefaults(bytes.toString("utf8"), options.variableValues);
109542
- if (vars.applied.length > 0) bytes = Buffer.from(vars.html);
109543
- }
109544
- publishRegistryFile(destDir, file.target, bytes);
109567
+ const baked = bakeVariables(item, file, bytes, options.variableValues);
109568
+ bytes = baked.bytes;
109569
+ const vars = baked.vars;
109545
109570
  return {
109546
109571
  destPath,
109547
109572
  target: file.target,
109548
109573
  preserved: false,
109549
109574
  hash: digest(bytes),
109550
- vars
109575
+ vars,
109576
+ bytes
109551
109577
  };
109552
109578
  }
109553
109579
  async function installItem(item, options) {
109580
+ return publishItem(await prepareItem(item, options));
109581
+ }
109582
+ async function prepareItem(item, options) {
109554
109583
  if (!validRegistryItem(item, item.name, item.type)) throw new Error("Invalid registry item");
109555
109584
  const baseUrl = options.baseUrl ?? DEFAULT_REGISTRY_URL;
109556
109585
  const destDir = resolve11(options.destDir);
@@ -109563,8 +109592,20 @@ async function installItem(item, options) {
109563
109592
  const budget = { remainingBytes: 512 * 1024 * 1024 };
109564
109593
  const outcomes = await installFileBatches(
109565
109594
  item.files,
109566
- (file) => installOneFile(item, file, root, baseUrl, record2, options, budget)
109595
+ (file) => prepareOneFile(item, file, root, baseUrl, record2, options, budget)
109596
+ );
109597
+ const invalid = outcomes.flatMap((o) => o.vars?.invalid ?? []);
109598
+ if (invalid.length > 0) throw new InvalidVariableValuesError(invalid);
109599
+ return { root, outcomes, force: options.force ?? false };
109600
+ }
109601
+ function publishItem({ root, outcomes: prepared, force }) {
109602
+ const record2 = readInstallRecord(root);
109603
+ const outcomes = prepared.map(
109604
+ (o) => o.bytes && keptByProject(record2, o.destPath, o.target, force) ? { ...o, preserved: true, hash: null, vars: null, bytes: null } : o
109567
109605
  );
109606
+ for (const outcome of outcomes) {
109607
+ if (outcome.bytes) publishRegistryFile(root, outcome.target, outcome.bytes);
109608
+ }
109568
109609
  const written = outcomes.filter((o) => !o.preserved).map((o) => o.destPath);
109569
109610
  const preserved = outcomes.filter((o) => o.preserved).map((o) => o.destPath);
109570
109611
  if (written.length > 0) {
@@ -109583,8 +109624,7 @@ async function installItem(item, options) {
109583
109624
  variablesUnknown: vars.length ? vars.reduce(
109584
109625
  (acc, v2) => acc.filter((id) => v2.unknown.includes(id)),
109585
109626
  vars[0].unknown
109586
- ) : [],
109587
- variablesInvalid: vars.flatMap((v2) => v2.invalid)
109627
+ ) : []
109588
109628
  };
109589
109629
  }
109590
109630
  function validatePhysicalTargets(root, files) {
@@ -109599,10 +109639,10 @@ function validatePhysicalTargets(root, files) {
109599
109639
  targets.add(key2);
109600
109640
  }
109601
109641
  }
109602
- async function installFileBatches(files, install2) {
109642
+ async function installFileBatches(files, prepare2) {
109603
109643
  const outcomes = [];
109604
109644
  for (let at4 = 0; at4 < files.length; at4 += 4) {
109605
- const batch = await Promise.allSettled(files.slice(at4, at4 + 4).map(install2));
109645
+ const batch = await Promise.allSettled(files.slice(at4, at4 + 4).map(prepare2));
109606
109646
  for (const result of batch) {
109607
109647
  if (result.status === "rejected") throw result.reason;
109608
109648
  outcomes.push(result.value);
@@ -127640,6 +127680,7 @@ function createProjectWatcher(projectDir) {
127640
127680
  const listeners = /* @__PURE__ */ new Set();
127641
127681
  const pendingPaths = /* @__PURE__ */ new Set();
127642
127682
  let debounceTimer = null;
127683
+ let lastFlushAt = Number.NEGATIVE_INFINITY;
127643
127684
  let closeTree = null;
127644
127685
  try {
127645
127686
  closeTree = watchProjectTree(projectDir, (relativePath) => {
@@ -127648,16 +127689,18 @@ function createProjectWatcher(projectDir) {
127648
127689
  }
127649
127690
  pendingPaths.add(relativePath);
127650
127691
  if (debounceTimer) clearTimeout(debounceTimer);
127692
+ const delay2 = Math.max(QUIET_MS, lastFlushAt + BURST_MS - Date.now());
127651
127693
  debounceTimer = setTimeout(() => {
127652
127694
  const changedPaths = [...pendingPaths];
127653
127695
  pendingPaths.clear();
127654
127696
  debounceTimer = null;
127697
+ lastFlushAt = Date.now();
127655
127698
  for (const changedPath of changedPaths) {
127656
127699
  for (const fn of listeners) {
127657
127700
  fn(changedPath);
127658
127701
  }
127659
127702
  }
127660
- }, DEBOUNCE_MS);
127703
+ }, delay2);
127661
127704
  });
127662
127705
  } catch {
127663
127706
  }
@@ -127676,7 +127719,7 @@ function createProjectWatcher(projectDir) {
127676
127719
  }
127677
127720
  };
127678
127721
  }
127679
- var WATCHER_EXCLUDED_DIRS, DEBOUNCE_MS;
127722
+ var WATCHER_EXCLUDED_DIRS, QUIET_MS, BURST_MS;
127680
127723
  var init_fileWatcher = __esm({
127681
127724
  "src/server/fileWatcher.ts"() {
127682
127725
  "use strict";
@@ -127697,7 +127740,8 @@ var init_fileWatcher = __esm({
127697
127740
  "outputs",
127698
127741
  "renders"
127699
127742
  ]);
127700
- DEBOUNCE_MS = 300;
127743
+ QUIET_MS = 30;
127744
+ BURST_MS = 300;
127701
127745
  }
127702
127746
  });
127703
127747
 
@@ -138837,6 +138881,12 @@ function resolveFontFetchRetryPolicy(configured) {
138837
138881
  )
138838
138882
  };
138839
138883
  }
138884
+ function placeAfterEveryStylesheet(document2, head, styleEl) {
138885
+ const stylesheets = document2.querySelectorAll("style, link[rel~=stylesheet]");
138886
+ const lastStylesheet = stylesheets[stylesheets.length - 1];
138887
+ if (lastStylesheet) lastStylesheet.after(styleEl);
138888
+ else head.appendChild(styleEl);
138889
+ }
138840
138890
  async function injectDeterministicFontFaces(html, options = {}) {
138841
138891
  const failClosedFontFetch = options.failClosedFontFetch === true;
138842
138892
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -138887,7 +138937,7 @@ async function injectDeterministicFontFaces(html, options = {}) {
138887
138937
  const styleEl = document2.createElement("style");
138888
138938
  styleEl.setAttribute("data-hyperframes-deterministic-fonts", "true");
138889
138939
  styleEl.textContent = css;
138890
- head.insertBefore(styleEl, head.firstChild);
138940
+ placeAfterEveryStylesheet(document2, head, styleEl);
138891
138941
  defaultLogger.info(
138892
138942
  `[Compiler] Injected deterministic @font-face rules for ${pendingFamilies.size - unresolved.length} requested font families`
138893
138943
  );
@@ -155594,29 +155644,34 @@ async function installAll(installPlan, destDir, baseUrl, force, requestedName, v
155594
155644
  const preserved = [];
155595
155645
  let variablesApplied = [];
155596
155646
  let variablesUnknown = [];
155597
- let variablesInvalid = [];
155598
155647
  try {
155648
+ const prepared = [];
155599
155649
  for (const planItem of installPlan) {
155600
- const result = await installItem(planItem, {
155601
- destDir,
155602
- baseUrl,
155603
- force,
155604
- // Only the item the user named. A dependency dragged in behind it never
155605
- // declared these variables and must not be rewritten by them.
155606
- variableValues: planItem.name === requestedName ? variableValues : null
155607
- });
155650
+ prepared.push(
155651
+ await prepareItem(planItem, {
155652
+ destDir,
155653
+ baseUrl,
155654
+ force,
155655
+ // Only the item the user named. A dependency dragged in behind it never
155656
+ // declared these variables and must not be rewritten by them.
155657
+ variableValues: planItem.name === requestedName ? variableValues : null
155658
+ })
155659
+ );
155660
+ }
155661
+ for (const [i, planItem] of installPlan.entries()) {
155662
+ const result = publishItem(prepared[i]);
155608
155663
  written.push(...result.written);
155609
155664
  preserved.push(...result.preserved);
155610
155665
  if (planItem.name === requestedName) {
155611
155666
  variablesApplied = result.variablesApplied;
155612
155667
  variablesUnknown = result.variablesUnknown;
155613
- variablesInvalid = result.variablesInvalid;
155614
155668
  }
155615
155669
  }
155616
155670
  } catch (err) {
155671
+ if (err instanceof InvalidVariableValuesError) throw new AddError(err.message, "invalid-vars");
155617
155672
  throw new AddError(describeInstallFailure(err, baseUrl), "install-failed");
155618
155673
  }
155619
- return { written, preserved, variablesApplied, variablesUnknown, variablesInvalid };
155674
+ return { written, preserved, variablesApplied, variablesUnknown };
155620
155675
  }
155621
155676
  function describeInstallFailure(err, registry) {
155622
155677
  const message = err instanceof Error ? err.message : String(err);
@@ -155659,7 +155714,7 @@ async function runAdd(opts) {
155659
155714
  }))
155660
155715
  }));
155661
155716
  const variableValues = parseVariableValues(opts.vars);
155662
- const { written, preserved, variablesApplied, variablesUnknown, variablesInvalid } = await installAll(
155717
+ const { written, preserved, variablesApplied, variablesUnknown } = await installAll(
155663
155718
  installPlan,
155664
155719
  projectDir,
155665
155720
  config.registry,
@@ -155686,9 +155741,6 @@ async function runAdd(opts) {
155686
155741
  const snippetTargetRel = primaryInstalledTarget(itemForInstall);
155687
155742
  const snippet = buildSnippet(item, snippetTargetRel, variableValues);
155688
155743
  const clipboardCopied = !opts.skipClipboard && snippet ? copyToClipboard(snippet) : false;
155689
- for (const { id, reason } of variablesInvalid) {
155690
- warnings.push(`--vars ${id} ignored: ${reason}`);
155691
- }
155692
155744
  if (variablesUnknown.length > 0) {
155693
155745
  warnings.push(`--vars ignored (not declared by ${item.name}): ${variablesUnknown.join(", ")}`);
155694
155746
  }
@@ -155716,6 +155768,7 @@ var init_add = __esm({
155716
155768
  init_colors();
155717
155769
  init_registry3();
155718
155770
  init_resolver();
155771
+ init_variableDefaults();
155719
155772
  init_compatibility();
155720
155773
  init_projectConfig();
155721
155774
  init_clipboard();
@@ -155870,8 +155923,9 @@ var init_add = __esm({
155870
155923
  if (!json) console.log(` ${c.warn("Warning:")} ${warning}`);
155871
155924
  }
155872
155925
  if (!json) console.log(` ${c.success("\u2713")} ${result.name}`);
155873
- } catch {
155874
- if (!json) console.log(` ${c.error("\u2717")} ${item.name} (skipped)`);
155926
+ } catch (err) {
155927
+ const reason = err instanceof Error ? err.message : String(err);
155928
+ if (!json) console.log(` ${c.error("\u2717")} ${item.name} (skipped: ${reason})`);
155875
155929
  }
155876
155930
  }
155877
155931
  if (json) {
@@ -72911,17 +72911,17 @@ function truncateSnippet(value, maxLength = 220) {
72911
72911
  return `${normalized2.slice(0, maxLength - 3)}...`;
72912
72912
  }
72913
72913
  function normalized(source) {
72914
- let text = "";
72914
+ const chars = [];
72915
72915
  const offsets = [];
72916
+ let lastWasSpace = false;
72916
72917
  for (let i = 0; i < source.length; i++) {
72917
- const char = source[i];
72918
- if (/\s/.test(char)) {
72919
- if (text.endsWith(" ")) continue;
72920
- text += " ";
72921
- } else text += char;
72918
+ const isSpace = /\s/.test(source[i]);
72919
+ if (isSpace && lastWasSpace) continue;
72920
+ chars.push(isSpace ? " " : source[i]);
72922
72921
  offsets.push(i);
72922
+ lastWasSpace = isSpace;
72923
72923
  }
72924
- return { text, offsets };
72924
+ return { text: chars.join(""), offsets };
72925
72925
  }
72926
72926
  function createSourceLocator(source) {
72927
72927
  const tags = parseHtmlStructure(source).tags;
@@ -79430,6 +79430,15 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
79430
79430
  `FFmpeg exited successfully but produced no frames (start=${startTime}, duration=${duration})`
79431
79431
  );
79432
79432
  }
79433
+ if (framePaths.size > 0 && duration > 0 && !options.finalFrameOnly) {
79434
+ const expectedAtFps = extractionFrameCountForDuration(duration, normalizedFps, metadata.isVFR);
79435
+ if (expectedAtFps > 0 && framePaths.size <= expectedAtFps * 0.6) {
79436
+ const correctedDuration = startTime + framePaths.size / fps;
79437
+ if (correctedDuration < metadata.videoStreamDurationSeconds) {
79438
+ metadata.videoStreamDurationSeconds = correctedDuration;
79439
+ }
79440
+ }
79441
+ }
79433
79442
  return {
79434
79443
  videoId,
79435
79444
  srcPath: videoPath,
@@ -94205,6 +94214,12 @@ function resolveFontFetchRetryPolicy(configured) {
94205
94214
  )
94206
94215
  };
94207
94216
  }
94217
+ function placeAfterEveryStylesheet(document2, head, styleEl) {
94218
+ const stylesheets = document2.querySelectorAll("style, link[rel~=stylesheet]");
94219
+ const lastStylesheet = stylesheets[stylesheets.length - 1];
94220
+ if (lastStylesheet) lastStylesheet.after(styleEl);
94221
+ else head.appendChild(styleEl);
94222
+ }
94208
94223
  async function injectDeterministicFontFaces(html, options = {}) {
94209
94224
  const failClosedFontFetch = options.failClosedFontFetch === true;
94210
94225
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -94255,7 +94270,7 @@ async function injectDeterministicFontFaces(html, options = {}) {
94255
94270
  const styleEl = document2.createElement("style");
94256
94271
  styleEl.setAttribute("data-hyperframes-deterministic-fonts", "true");
94257
94272
  styleEl.textContent = css;
94258
- head.insertBefore(styleEl, head.firstChild);
94273
+ placeAfterEveryStylesheet(document2, head, styleEl);
94259
94274
  defaultLogger.info(
94260
94275
  `[Compiler] Injected deterministic @font-face rules for ${pendingFamilies.size - unresolved.length} requested font families`
94261
94276
  );
@@ -103722,8 +103737,8 @@ async function runFontLocalize(io, localize) {
103722
103737
  }
103723
103738
 
103724
103739
  // src/version.ts
103725
- var VERSION = true ? "0.8.68" : "0.0.0-dev";
103726
- var PRODUCER_VERSION = true ? "0.8.68" : "0.0.0-dev";
103740
+ var VERSION = true ? "0.8.69" : "0.0.0-dev";
103741
+ var PRODUCER_VERSION = true ? "0.8.69" : "0.0.0-dev";
103727
103742
 
103728
103743
  // src/fontLocalizeCli.ts
103729
103744
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var Se=Object.defineProperty;var fn=Object.getOwnPropertyDescriptor;var hn=Object.getOwnPropertyNames;var bn=Object.prototype.hasOwnProperty;var gn=(n,e)=>{for(var t in e)Se(n,t,{get:e[t],enumerable:!0})},_n=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of hn(e))!bn.call(n,r)&&r!==t&&Se(n,r,{get:()=>e[r],enumerable:!(i=fn(e,r))||i.enumerable});return n};var yn=n=>_n(Se({},"__esModule",{value:!0}),n);var Xi={};gn(Xi,{HyperframesPlayer:()=>Ee,SPEED_PRESETS:()=>we,formatSpeed:()=>G,formatTime:()=>ie});function ct(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function H(n){return typeof n=="object"&&n!==null}function mt(n){return H(n)&&typeof n.getDuration=="function"}function pt(n){return H(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}var L="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.68/dist/hyperframe.runtime.iife.js";function V(n){if(n===null)return null;let e=Number.parseInt(n,10);return Number.isFinite(e)&&e>0?e:null}function vn(n){let e=n?.querySelector("[data-composition-id][data-width][data-height]")??n?.querySelector("[data-width][data-height]");if(!e)return null;let t=V(e.getAttribute("data-width")),i=V(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var ne=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),r=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(ct({hasRuntime:i,hasTimelines:r,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let a=this._resolvePlaybackDurationAdapter(t);if(a&&a.getDuration()>0){this.stop();let s=vn(this._iframe.contentDocument);this._callbacks.onReady({duration:a.getDuration(),adapter:a,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||H(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=L,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!H(t))return null;let i=Object.keys(t);if(i.length===0)return null;let r=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=r&&r in t?r:i[i.length-1],a=t[o];return pt(a)?a:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(mt(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var ft=`
1
+ "use strict";var HyperframesPlayer=(()=>{var Se=Object.defineProperty;var fn=Object.getOwnPropertyDescriptor;var hn=Object.getOwnPropertyNames;var bn=Object.prototype.hasOwnProperty;var gn=(n,e)=>{for(var t in e)Se(n,t,{get:e[t],enumerable:!0})},_n=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of hn(e))!bn.call(n,r)&&r!==t&&Se(n,r,{get:()=>e[r],enumerable:!(i=fn(e,r))||i.enumerable});return n};var yn=n=>_n(Se({},"__esModule",{value:!0}),n);var Xi={};gn(Xi,{HyperframesPlayer:()=>Ee,SPEED_PRESETS:()=>we,formatSpeed:()=>G,formatTime:()=>ie});function ct(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function H(n){return typeof n=="object"&&n!==null}function mt(n){return H(n)&&typeof n.getDuration=="function"}function pt(n){return H(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}var L="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.69/dist/hyperframe.runtime.iife.js";function V(n){if(n===null)return null;let e=Number.parseInt(n,10);return Number.isFinite(e)&&e>0?e:null}function vn(n){let e=n?.querySelector("[data-composition-id][data-width][data-height]")??n?.querySelector("[data-width][data-height]");if(!e)return null;let t=V(e.getAttribute("data-width")),i=V(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var ne=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),r=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(ct({hasRuntime:i,hasTimelines:r,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let a=this._resolvePlaybackDurationAdapter(t);if(a&&a.getDuration()>0){this.stop();let s=vn(this._iframe.contentDocument);this._callbacks.onReady({duration:a.getDuration(),adapter:a,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||H(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=L,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!H(t))return null;let i=Object.keys(t);if(i.length===0)return null;let r=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=r&&r in t?r:i[i.length-1],a=t[o];return pt(a)?a:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(mt(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var ft=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -25768,17 +25768,17 @@ function mediaSrcTagRe(tagAlternation) {
25768
25768
  return new RegExp(`<(${tagAlternation})\\b[^>]*\\ssrc\\s*=\\s*["']([^"']+)["'][^>]*>`, "gi");
25769
25769
  }
25770
25770
  function normalized(source) {
25771
- let text = "";
25771
+ const chars = [];
25772
25772
  const offsets = [];
25773
+ let lastWasSpace = false;
25773
25774
  for (let i = 0; i < source.length; i++) {
25774
- const char = source[i];
25775
- if (/\s/.test(char)) {
25776
- if (text.endsWith(" ")) continue;
25777
- text += " ";
25778
- } else text += char;
25775
+ const isSpace = /\s/.test(source[i]);
25776
+ if (isSpace && lastWasSpace) continue;
25777
+ chars.push(isSpace ? " " : source[i]);
25779
25778
  offsets.push(i);
25779
+ lastWasSpace = isSpace;
25780
25780
  }
25781
- return { text, offsets };
25781
+ return { text: chars.join(""), offsets };
25782
25782
  }
25783
25783
  function createSourceLocator(source) {
25784
25784
  const tags = parseHtmlStructure(source).tags;
@@ -1,20 +1,6 @@
1
- // Shared frame-packet builder — the script half of the frame-worker core/delta split.
2
- //
3
- // Each narrative workflow ships a thin `scripts/frame-packets.mjs` wrapper that pins
4
- // its own paths (animation skill, role delta, design-truth resolution, extra packet
5
- // sections) and delegates everything else here, exactly as the markdown half already
6
- // does with `references/frame-worker-core.md` + each workflow's delta. One owner for
7
- // the packet-building logic; the wrappers own only what genuinely differs per workflow.
8
- //
9
- // Packet (<frame_id>.md) = project inputs + the frame's exact `## Frame N` block
10
- // + the blueprint body + every cited rule recipe, inlined — so a worker never opens
11
- // the shared STORYBOARD.md or any skill document. Cited motions are found
12
- // mechanically: the explicit `- rules:` field when present, plus every valid rule id
13
- // (a filename under the animation skill's rules/) mentioned in the block.
14
- //
15
- // _role.md = frame-worker-core.md + the workflow's sub-agents/frame-worker.md,
16
- // concatenated verbatim — the complete worker role, assembled from the two source
17
- // documents so nothing is hand-maintained twice.
1
+ // Frame packets inline the storyboard frame, blueprint and cited recipes.
2
+ // Workflow wrappers supply resource paths and workflow-specific sections.
3
+ // The role combines the shared worker contract with the workflow delta.
18
4
 
19
5
  import {
20
6
  existsSync,
@@ -100,12 +86,8 @@ export function resourceSections(block, { animationDir, ruleIds, frameId }) {
100
86
  if (blueprint) {
101
87
  const blueprintsDir = join(animationDir, "blueprints");
102
88
  const path = join(blueprintsDir, `${blueprint}.md`);
103
- // A blueprint that resolved to nothing used to inline an empty string, so the
104
- // packet shipped without the one document the frame was designed against and
105
- // the run still reported success. Name it instead — but only when the library
106
- // is actually there to be named against. The animation skill installs on
107
- // demand, so an absent blueprints/ is a missing install, not a bad id, and it
108
- // degrades with a warning exactly like an absent rules/ (see knownRuleIds).
89
+ // An installed library must contain the named blueprint.
90
+ // An absent on-demand library warns, like missing motion rules.
109
91
  if (!existsSync(blueprintsDir)) {
110
92
  console.warn(
111
93
  `frame-packets: no blueprints dir at ${blueprintsDir} — packets will inline no blueprint`,
@@ -0,0 +1,4 @@
1
+ // Music sits under narration; a silent film brings music forward.
2
+ export const BGM_BED_VOLUME = 0.12;
3
+ export const BGM_SILENT_VOLUME = 0.9;
4
+ export const bgmDefaultVolume = (hasVoice) => (hasVoice ? BGM_BED_VOLUME : BGM_SILENT_VOLUME);
@@ -20,12 +20,8 @@ import { pythonInvocation } from "./python.mjs";
20
20
  const r3 = (x) => Number(x.toFixed(3));
21
21
  const lyriaKey = () => process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "";
22
22
 
23
- // Default BGM level. Under narration music is a bed that must stay under the
24
- // voice — 0.12 linear ≈ -18 dB. A silent film (no voice) has no voice to duck
25
- // beneath, so BGM sits forward at 0.9. Callers may override per composition.
26
- export const BGM_BED_VOLUME = 0.12;
27
- export const BGM_SILENT_VOLUME = 0.9;
28
- export const bgmDefaultVolume = (hasVoice) => (hasVoice ? BGM_BED_VOLUME : BGM_SILENT_VOLUME);
23
+ import { bgmDefaultVolume } from "./bgm-volume.mjs";
24
+ export { BGM_BED_VOLUME, BGM_SILENT_VOLUME, bgmDefaultVolume } from "./bgm-volume.mjs";
29
25
 
30
26
  const BGM_PY_DEPS = ["transformers", "torch", "soundfile", "numpy"];
31
27
  const BGM_PY_PROBE =
@@ -1,8 +1,10 @@
1
- import { cpSync, mkdtempSync, rmSync } from "node:fs";
1
+ import assert from "node:assert/strict";
2
+ import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
3
  import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { execFileSync } from "node:child_process";
5
6
  import test from "node:test";
7
+ import { pathToFileURL } from "node:url";
6
8
 
7
9
  function run(scriptsDir, script, args) {
8
10
  return execFileSync(process.execPath, [join(scriptsDir, script), ...args], {
@@ -25,3 +27,68 @@ test("published memory scripts run without reaching outside the skill", () => {
25
27
  rmSync(scratchDir, { recursive: true, force: true });
26
28
  }
27
29
  });
30
+
31
+ test("published audio helpers import without the CLI package tree", () => {
32
+ const root = mkdtempSync(join(tmpdir(), "media-use-audio-"));
33
+ const skill = join(root, "media-use");
34
+ try {
35
+ cpSync(new URL("..", import.meta.url), skill, { recursive: true });
36
+ for (const helper of ["heygen.mjs", "tts.mjs"])
37
+ run(join(skill, "audio/scripts/lib"), helper, []);
38
+ } finally {
39
+ rmSync(root, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ for (const name of ["faceless-explainer", "pr-to-video", "product-launch-video"]) {
44
+ test(name + " assembles from its installed directory alone", () => {
45
+ const root = mkdtempSync(join(tmpdir(), "installed-workflow-"));
46
+ try {
47
+ const skill = join(root, name);
48
+ cpSync(new URL("../../" + name, import.meta.url), skill, { recursive: true });
49
+ const project = join(root, "project");
50
+ mkdirSync(join(project, "compositions/frames"), { recursive: true });
51
+ writeFileSync(
52
+ join(project, "STORYBOARD.md"),
53
+ "---\nformat: 1920x1080\nmessage: Test\n---\n\n## Frame 1\n- duration: 3s\n- src: compositions/frames/01-a.html\n",
54
+ );
55
+ writeFileSync(
56
+ join(project, "compositions/frames/01-a.html"),
57
+ '<template><div data-composition-id="01-a" data-duration="3" data-width="1920" data-height="1080"><section class="clip" data-start="0" data-duration="3"></section></div></template>',
58
+ );
59
+ run(join(skill, "scripts"), "assemble-index.mjs", [
60
+ "--hyperframes",
61
+ project,
62
+ "--storyboard",
63
+ join(project, "STORYBOARD.md"),
64
+ ]);
65
+ assert.match(readFileSync(join(project, "index.html"), "utf8"), /01-a/);
66
+ importPacketBuilder(join(skill, "scripts/frame-packets.mjs"));
67
+ } finally {
68
+ rmSync(root, { recursive: true, force: true });
69
+ }
70
+ });
71
+ }
72
+
73
+ test("general-video packet builder starts from its installed directory alone", () => {
74
+ const root = mkdtempSync(join(tmpdir(), "installed-packets-"));
75
+ try {
76
+ cpSync(new URL("../../general-video", import.meta.url), root, { recursive: true });
77
+ importPacketBuilder(join(root, "scripts/frame-packets.mjs"));
78
+ } finally {
79
+ rmSync(root, { recursive: true, force: true });
80
+ }
81
+ });
82
+
83
+ function importPacketBuilder(path) {
84
+ execFileSync(
85
+ process.execPath,
86
+ [
87
+ "--input-type=module",
88
+ "-e",
89
+ 'const m = await import(process.argv[1]); if (typeof m.buildFramePackets !== "function") throw new Error("missing builder");',
90
+ pathToFileURL(path).href,
91
+ ],
92
+ { stdio: "pipe" },
93
+ );
94
+ }