hyperframes 0.7.3 → 0.7.4

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.7.3" : "0.0.0-dev";
53
+ VERSION = true ? "0.7.4" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -26253,13 +26253,49 @@ function cssTransformToGsapProps(cssTransform) {
26253
26253
  }
26254
26254
  return parts.length > 0 ? parts.join(", ") : null;
26255
26255
  }
26256
- var SCENE_BOUNDARY_EPSILON_SECONDS, gsapWindowsCache, gsapRules;
26256
+ function targetedSelectorTokens(selector) {
26257
+ const tokens = /* @__PURE__ */ new Set();
26258
+ for (const group of selector.split(",")) {
26259
+ const compounds = group.trim().split(/[\s>+~]+/).filter(Boolean);
26260
+ const last = compounds[compounds.length - 1];
26261
+ if (!last) continue;
26262
+ const simple2 = last.match(/[#.][A-Za-z0-9_-]+/g);
26263
+ if (simple2) for (const token of simple2) tokens.add(token);
26264
+ }
26265
+ return tokens;
26266
+ }
26267
+ function matchCssTransform(gsapSelector, cssMap) {
26268
+ if (cssMap.size === 0) return void 0;
26269
+ const direct = cssMap.get(gsapSelector);
26270
+ if (direct) return direct;
26271
+ const tokens = targetedSelectorTokens(gsapSelector);
26272
+ for (const [cssSelector, value] of cssMap) {
26273
+ if (tokens.has(cssSelector)) return value;
26274
+ }
26275
+ return void 0;
26276
+ }
26277
+ function extractStandaloneGsapTransformCalls(script) {
26278
+ const calls = [];
26279
+ const pattern = /gsap\.(set|to|from|fromTo)\s*\(\s*(["'])([^"']+)\2\s*,\s*\{([^{}]*)\}/g;
26280
+ let match;
26281
+ while ((match = pattern.exec(script)) !== null) {
26282
+ const method = match[1] ?? "set";
26283
+ const selector = match[3] ?? "";
26284
+ const propsBody = match[4] ?? "";
26285
+ const properties = [...propsBody.matchAll(/([A-Za-z_$][\w$]*)\s*:/g)].map((m2) => m2[1] ?? "");
26286
+ calls.push({ method, selector, properties, raw: truncateSnippet(match[0]) ?? match[0] });
26287
+ }
26288
+ return calls;
26289
+ }
26290
+ var SCENE_BOUNDARY_EPSILON_SECONDS, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, gsapRules;
26257
26291
  var init_gsap = __esm({
26258
26292
  "../core/src/lint/rules/gsap.ts"() {
26259
26293
  "use strict";
26260
26294
  init_utils2();
26261
26295
  SCENE_BOUNDARY_EPSILON_SECONDS = 0.05;
26262
26296
  gsapWindowsCache = /* @__PURE__ */ new Map();
26297
+ CONFLICTING_TRANSLATE_PROPS = ["x", "y", "xPercent", "yPercent"];
26298
+ CONFLICTING_SCALE_PROPS = ["scale", "scaleX", "scaleY"];
26263
26299
  gsapRules = [
26264
26300
  // overlapping_gsap_tweens + gsap_animates_clip_element + unscoped_gsap_selector
26265
26301
  // fallow-ignore-next-line complexity
@@ -26413,22 +26449,30 @@ ${right.raw}`)
26413
26449
  for (const script of scripts) {
26414
26450
  if (!/gsap\.timeline/.test(script.content)) continue;
26415
26451
  const windows = await cachedExtractGsapWindows(script.content);
26452
+ const calls = [
26453
+ ...windows.map((win) => ({
26454
+ method: win.method,
26455
+ selector: win.targetSelector,
26456
+ properties: win.properties,
26457
+ raw: win.raw
26458
+ })),
26459
+ ...extractStandaloneGsapTransformCalls(stripJsComments(script.content))
26460
+ ];
26416
26461
  const conflicts = /* @__PURE__ */ new Map();
26417
- for (const win of windows) {
26418
- if (win.method === "fromTo" || win.method === "from") continue;
26419
- const sel = win.targetSelector;
26420
- const cssKey = sel.startsWith("#") || sel.startsWith(".") ? sel : `#${sel}`;
26421
- const translateProps = win.properties.filter(
26422
- (p2) => ["x", "y", "xPercent", "yPercent"].includes(p2)
26462
+ for (const call of calls) {
26463
+ if (call.method === "fromTo" || call.method === "from") continue;
26464
+ const sel = call.selector;
26465
+ const translateProps = call.properties.filter(
26466
+ (p2) => CONFLICTING_TRANSLATE_PROPS.includes(p2)
26423
26467
  );
26424
- const scaleProps = win.properties.filter((p2) => p2 === "scale");
26425
- const cssFromTranslate = translateProps.length > 0 ? cssTranslateSelectors.get(cssKey) : void 0;
26426
- const cssFromScale = scaleProps.length > 0 ? cssScaleSelectors.get(cssKey) : void 0;
26468
+ const scaleProps = call.properties.filter((p2) => CONFLICTING_SCALE_PROPS.includes(p2));
26469
+ const cssFromTranslate = translateProps.length > 0 ? matchCssTransform(sel, cssTranslateSelectors) : void 0;
26470
+ const cssFromScale = scaleProps.length > 0 ? matchCssTransform(sel, cssScaleSelectors) : void 0;
26427
26471
  if (!cssFromTranslate && !cssFromScale) continue;
26428
26472
  const existing = conflicts.get(sel) ?? {
26429
26473
  cssTransform: [cssFromTranslate, cssFromScale].filter(Boolean).join(" "),
26430
26474
  props: /* @__PURE__ */ new Set(),
26431
- raw: win.raw
26475
+ raw: call.raw
26432
26476
  };
26433
26477
  for (const p2 of [...translateProps, ...scaleProps]) existing.props.add(p2);
26434
26478
  conflicts.set(sel, existing);
@@ -26925,6 +26969,35 @@ function isCompositionRootOrMount(rawTag) {
26925
26969
  readAttr(rawTag, "data-composition-id") || readAttr(rawTag, "data-composition-src")
26926
26970
  );
26927
26971
  }
26972
+ function extractCssSelectors(css) {
26973
+ const out = [];
26974
+ const noComments = css.replace(/\/\*[\s\S]*?\*\//g, "");
26975
+ const ruleHeader = /([^{}]+)\{/g;
26976
+ let m2;
26977
+ while ((m2 = ruleHeader.exec(noComments)) !== null) {
26978
+ const header = (m2[1] ?? "").trim();
26979
+ if (!header || header.startsWith("@")) continue;
26980
+ for (const sel of header.split(",")) {
26981
+ const s2 = sel.trim();
26982
+ if (s2) out.push(s2);
26983
+ }
26984
+ }
26985
+ return out;
26986
+ }
26987
+ function leftmostCompoundClasses(selector) {
26988
+ const leftmost = selector.trim().split(/[\s>+~]+/)[0] ?? "";
26989
+ return (leftmost.match(/\.([\w-]+)/g) ?? []).map((c3) => c3.slice(1));
26990
+ }
26991
+ function rootClassStyledSelectors(styles, rootClasses) {
26992
+ const offenders = [];
26993
+ for (const style of styles) {
26994
+ for (const selector of extractCssSelectors(style.content)) {
26995
+ const hitsRoot = leftmostCompoundClasses(selector).some((c3) => rootClasses.includes(c3));
26996
+ if (hitsRoot && !offenders.includes(selector)) offenders.push(selector);
26997
+ }
26998
+ }
26999
+ return offenders;
27000
+ }
26928
27001
  var MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, compositionRules;
26929
27002
  var init_composition = __esm({
26930
27003
  "../core/src/lint/rules/composition.ts"() {
@@ -27377,6 +27450,39 @@ var init_composition = __esm({
27377
27450
  });
27378
27451
  }
27379
27452
  return findings;
27453
+ },
27454
+ // subcomposition_root_styled_by_class
27455
+ // A sub-composition's <style> is scoped at render time to
27456
+ // `[data-composition-id="<id>"] <selector>` so scenes inlined into one document
27457
+ // can't leak styles into each other. A rule whose LEFTMOST selector is the ROOT
27458
+ // element's own class (e.g. `.frame { ... }` on the same element that carries
27459
+ // data-composition-id) therefore becomes a DESCENDANT selector that can never
27460
+ // match the root — the whole scene renders unstyled (tiny text top-left, images
27461
+ // at natural size). lint/validate/inspect evaluate the file in isolation (no
27462
+ // scoping) and Studio previews each scene in its own iframe (no scoping), so the
27463
+ // break is invisible until the composited MP4 render. Style the root via `#root`
27464
+ // (the scoper special-cases the root id) and descendants via plain selectors,
27465
+ // like the registry blocks — the runtime already scopes each scene by id, so a
27466
+ // class namespace on the root is redundant.
27467
+ ({ rootTag, rootCompositionId, styles, options }) => {
27468
+ if (!options.isSubComposition) return [];
27469
+ if (isRegistrySourceFile(options.filePath)) return [];
27470
+ if (!rootTag || !rootCompositionId) return [];
27471
+ const rootClasses = (readAttr(rootTag.raw, "class") || "").split(/\s+/).filter(Boolean);
27472
+ if (rootClasses.length === 0) return [];
27473
+ const offenders = rootClassStyledSelectors(styles, rootClasses);
27474
+ if (offenders.length === 0) return [];
27475
+ const example = offenders.slice(0, 3).join(", ");
27476
+ return [
27477
+ {
27478
+ code: "subcomposition_root_styled_by_class",
27479
+ severity: "error",
27480
+ message: `Root element has class="${rootClasses.join(" ")}" and is styled by ${offenders.length} rule(s) keyed off that class (e.g. ${example}). At render, every sub-composition rule is scoped to [data-composition-id="${rootCompositionId}"] <selector>, so a selector whose leftmost part is the ROOT's own class becomes a descendant selector that cannot match the root \u2014 the scene renders unstyled (tiny text top-left, full-size images). lint/validate/inspect and Studio's per-frame iframe preview do not scope, so this passes every static check and looks correct in preview.`,
27481
+ selector: example,
27482
+ fixHint: `Give the root id="root" and style it with \`#root { ... }\` plus plain descendant selectors (\`.kicker\`, \`#hero\`) \u2014 the runtime already scopes each sub-composition by data-composition-id, so a class namespace on the root is redundant and breaks under scoping.`,
27483
+ snippet: truncateSnippet(rootTag.raw)
27484
+ }
27485
+ ];
27380
27486
  }
27381
27487
  ];
27382
27488
  }
@@ -33175,11 +33281,10 @@ async function pageScreenshotCapture(page, options) {
33175
33281
  format: isPng ? "png" : "jpeg",
33176
33282
  quality: isPng ? void 0 : options.quality ?? 80,
33177
33283
  fromSurface: true,
33178
- // The explicit clip rect constrains output to exact composition
33179
- // dimensions. The viewport-boundary pre-clip from captureBeyondViewport:
33180
- // false is redundant, and Chrome's compositor rounds it inward under
33181
- // multi-tab load — clipping the bottom/right edge of tall viewports.
33182
- captureBeyondViewport: true,
33284
+ // Use Chrome's faster viewport-bound screenshot path by default. Callers
33285
+ // opt into the beyond-viewport path only for known compositor edge cases,
33286
+ // such as native video surfaces in tall portrait renders.
33287
+ captureBeyondViewport: options.captureBeyondViewport ?? false,
33183
33288
  optimizeForSpeed: !isPng,
33184
33289
  clip
33185
33290
  });
@@ -33194,8 +33299,8 @@ async function captureScreenshotWithAlpha(page, width, height) {
33194
33299
  const result = await client.send("Page.captureScreenshot", {
33195
33300
  format: "png",
33196
33301
  fromSurface: true,
33302
+ // Preserve the #1094 tall-portrait edge-clipping guard on HDR alpha captures.
33197
33303
  captureBeyondViewport: true,
33198
- // see pageScreenshotCapture for rationale
33199
33304
  optimizeForSpeed: false,
33200
33305
  // `true` uses a zero-alpha-aware fast path that crushes real alpha values — observed empirically, CDP docs don't spell it out
33201
33306
  clip: { x: 0, y: 0, width, height, scale: 1 }
@@ -33224,8 +33329,8 @@ async function captureAlphaPng(page, width, height) {
33224
33329
  const result = await client.send("Page.captureScreenshot", {
33225
33330
  format: "png",
33226
33331
  fromSurface: true,
33332
+ // Preserve the #1094 tall-portrait edge-clipping guard on HDR alpha captures.
33227
33333
  captureBeyondViewport: true,
33228
- // see pageScreenshotCapture for rationale
33229
33334
  optimizeForSpeed: false,
33230
33335
  // must be false to preserve alpha
33231
33336
  clip: { x: 0, y: 0, width, height, scale: 1 }
@@ -37049,21 +37154,6 @@ function defaultBuildScopeSelector(compId) {
37049
37154
  const escaped = compId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
37050
37155
  return `[data-composition-id="${escaped}"]`;
37051
37156
  }
37052
- function emptyCompositionHtmlError(src) {
37053
- return new Error(
37054
- `Composition HTML is empty or could not be parsed: ${src}. Check that the file referenced by data-composition-src contains valid HTML.`
37055
- );
37056
- }
37057
- function assertNonEmptyCompositionHtml(html, src) {
37058
- if (!html.trim()) {
37059
- throw emptyCompositionHtmlError(src);
37060
- }
37061
- }
37062
- function assertParsedCompositionDocument(doc, src) {
37063
- if (!doc.documentElement) {
37064
- throw emptyCompositionHtmlError(src);
37065
- }
37066
- }
37067
37157
  function inlineSubCompositions(document2, hosts, options) {
37068
37158
  const {
37069
37159
  resolveHtml,
@@ -37089,15 +37179,15 @@ function inlineSubCompositions(document2, hosts, options) {
37089
37179
  const src = hostEl.getAttribute("data-composition-src");
37090
37180
  if (!src) continue;
37091
37181
  const compHtml = resolveHtml(src);
37092
- if (compHtml == null) {
37093
- if (onMissingComposition) {
37094
- onMissingComposition(src);
37095
- }
37182
+ if (compHtml == null || !compHtml.trim()) {
37183
+ onMissingComposition?.(src);
37096
37184
  continue;
37097
37185
  }
37098
- assertNonEmptyCompositionHtml(compHtml, src);
37099
37186
  const compDoc = parseHtml2(compHtml);
37100
- assertParsedCompositionDocument(compDoc, src);
37187
+ if (!compDoc.documentElement) {
37188
+ onMissingComposition?.(src);
37189
+ continue;
37190
+ }
37101
37191
  let compId;
37102
37192
  let runtimeCompId;
37103
37193
  if (hostIdentityMap) {
@@ -37110,9 +37200,15 @@ function inlineSubCompositions(document2, hosts, options) {
37110
37200
  }
37111
37201
  const contentRoot = compDoc.querySelector("template");
37112
37202
  const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body?.innerHTML || "";
37113
- assertNonEmptyCompositionHtml(contentHtml, src);
37203
+ if (!contentHtml.trim()) {
37204
+ onMissingComposition?.(src);
37205
+ continue;
37206
+ }
37114
37207
  const contentDoc = parseHtml2(contentHtml);
37115
- assertParsedCompositionDocument(contentDoc, src);
37208
+ if (!contentDoc.documentElement) {
37209
+ onMissingComposition?.(src);
37210
+ continue;
37211
+ }
37116
37212
  const innerRoot = compId ? queryByAttr(contentDoc, "data-composition-id", compId) : contentDoc.querySelector("[data-composition-id]");
37117
37213
  const inferredCompId = innerRoot?.getAttribute("data-composition-id")?.trim() || "";
37118
37214
  const authoredRootId = innerRoot?.getAttribute("id")?.trim() || null;
@@ -45203,6 +45299,7 @@ __export(manager_exports, {
45203
45299
  ensureModel: () => ensureModel,
45204
45300
  ensureWhisper: () => ensureWhisper,
45205
45301
  findWhisper: () => findWhisper,
45302
+ getInstallInstructions: () => getInstallInstructions,
45206
45303
  hasFFmpeg: () => hasFFmpeg,
45207
45304
  isWhisperUnavailable: () => isWhisperUnavailable
45208
45305
  });
@@ -45309,6 +45406,12 @@ function getInstallInstructions() {
45309
45406
  if (platform4() === "darwin") {
45310
45407
  return "brew install whisper-cpp";
45311
45408
  }
45409
+ if (platform4() === "linux") {
45410
+ return "Build from source: https://github.com/ggml-org/whisper.cpp#building (requires cmake and a C compiler)";
45411
+ }
45412
+ if (platform4() === "win32") {
45413
+ return "Build with cmake: https://github.com/ggml-org/whisper.cpp#building";
45414
+ }
45312
45415
  return "See https://github.com/ggml-org/whisper.cpp#building";
45313
45416
  }
45314
45417
  function hasBrew() {
@@ -45910,7 +46013,8 @@ var init_npxCommand = __esm({
45910
46013
  // src/commands/skills.ts
45911
46014
  var skills_exports = {};
45912
46015
  __export(skills_exports, {
45913
- default: () => skills_default
46016
+ default: () => skills_default,
46017
+ installAllSkills: () => installAllSkills
45914
46018
  });
45915
46019
  import { execFileSync as execFileSync4, spawn as spawn7 } from "child_process";
45916
46020
  function hasNpx() {
@@ -45922,12 +46026,13 @@ function hasNpx() {
45922
46026
  return false;
45923
46027
  }
45924
46028
  }
45925
- function runSkillsAdd(repo) {
45926
- const npx = buildNpxCommand(["skills", "add", repo, "--all"]);
46029
+ function runSkillsAdd(repo, opts = {}) {
46030
+ const npx = buildNpxCommand(["skills", "add", repo, ...opts.extraArgs ?? ["--all"]]);
45927
46031
  return new Promise((resolve58, reject) => {
45928
46032
  const child = spawn7(npx.command, npx.args, {
45929
46033
  stdio: "inherit",
45930
46034
  timeout: 12e4,
46035
+ cwd: opts.cwd,
45931
46036
  // GH #316 — the upstream `skills` CLI shells out to `git clone`.
45932
46037
  // When Git's clone-hook protection is active (shipped on by
45933
46038
  // default in 2.45.1, reverted in 2.45.2, still present on many
@@ -45945,6 +46050,22 @@ function runSkillsAdd(repo) {
45945
46050
  child.on("error", reject);
45946
46051
  });
45947
46052
  }
46053
+ async function installAllSkills(opts = {}) {
46054
+ if (!hasNpx()) {
46055
+ R2.error(c.error("npx not found. Install Node.js and retry."));
46056
+ return;
46057
+ }
46058
+ for (const source of SOURCES) {
46059
+ console.log();
46060
+ console.log(c.bold(`Installing ${source.name} skills...`));
46061
+ console.log();
46062
+ try {
46063
+ await runSkillsAdd(source.repo, opts);
46064
+ } catch {
46065
+ console.log(c.dim(`${source.name} skills skipped`));
46066
+ }
46067
+ }
46068
+ }
45948
46069
  var SOURCES, skills_default;
45949
46070
  var init_skills = __esm({
45950
46071
  "src/commands/skills.ts"() {
@@ -45961,20 +46082,7 @@ var init_skills = __esm({
45961
46082
  },
45962
46083
  args: {},
45963
46084
  async run() {
45964
- if (!hasNpx()) {
45965
- R2.error(c.error("npx not found. Install Node.js and retry."));
45966
- return;
45967
- }
45968
- for (const source of SOURCES) {
45969
- console.log();
45970
- console.log(c.bold(`Installing ${source.name} skills...`));
45971
- console.log();
45972
- try {
45973
- await runSkillsAdd(source.repo);
45974
- } catch {
45975
- console.log(c.dim(`${source.name} skills skipped`));
45976
- }
45977
- }
46085
+ await installAllSkills();
45978
46086
  }
45979
46087
  });
45980
46088
  }
@@ -46171,7 +46279,16 @@ async function lintProject(project) {
46171
46279
  const allHtmlSources = [{ html: rootHtml }];
46172
46280
  const compositionsDir = resolve12(project.dir, "compositions");
46173
46281
  if (existsSync22(compositionsDir)) {
46174
- const files = readdirSync8(compositionsDir).filter((f3) => f3.endsWith(".html"));
46282
+ const collectHtmlFiles = (dir, rel) => {
46283
+ const out = [];
46284
+ for (const entry of readdirSync8(dir, { withFileTypes: true })) {
46285
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
46286
+ if (entry.isDirectory()) out.push(...collectHtmlFiles(join21(dir, entry.name), relPath));
46287
+ else if (entry.isFile() && entry.name.endsWith(".html")) out.push(relPath);
46288
+ }
46289
+ return out;
46290
+ };
46291
+ const files = collectHtmlFiles(compositionsDir, "").sort();
46175
46292
  for (const file of files) {
46176
46293
  const filePath = join21(compositionsDir, file);
46177
46294
  const html = readFileSync15(filePath, "utf-8");
@@ -93728,7 +93845,16 @@ var init_fileServer2 = __esm({
93728
93845
  var root = document.querySelector('[data-composition-id]');
93729
93846
  if (!root) return 0;
93730
93847
  var d = Number(root.getAttribute('data-duration'));
93731
- return Number.isFinite(d) && d > 0 ? d : 0;
93848
+ if (Number.isFinite(d) && d > 0) return d;
93849
+ var comps = document.querySelectorAll('[data-composition-src]');
93850
+ var maxEnd = 0;
93851
+ for (var i = 0; i < comps.length; i++) {
93852
+ var start = Number(comps[i].getAttribute('data-start')) || 0;
93853
+ var dur = Number(comps[i].getAttribute('data-duration')) || 0;
93854
+ if (dur > 0) maxEnd = Math.max(maxEnd, start + dur);
93855
+ }
93856
+ if (maxEnd > 0) console.warn('[HF Bridge] No root data-duration; derived ' + maxEnd + 's from sub-compositions');
93857
+ return maxEnd;
93732
93858
  }
93733
93859
  function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
93734
93860
  var frames;
@@ -95272,7 +95398,10 @@ function inlineSubCompositions2(html, subCompositions, projectDir) {
95272
95398
  },
95273
95399
  parseHtml: (htmlStr) => parseHTML(htmlStr).document,
95274
95400
  scriptErrorLabel: "[Compiler] Composition script failed",
95275
- compoundAuthoredRoot: true
95401
+ compoundAuthoredRoot: true,
95402
+ onMissingComposition: (srcPath) => {
95403
+ console.warn(`[Compiler] Composition file missing or empty: ${srcPath}`);
95404
+ }
95276
95405
  }
95277
95406
  );
95278
95407
  for (const hostEl of hosts) {
@@ -99550,6 +99679,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
99550
99679
  job.startedAt = /* @__PURE__ */ new Date();
99551
99680
  assertNotAborted();
99552
99681
  assertConfiguredFfmpegBinariesExist();
99682
+ if (!existsSync45(workDir)) mkdirSync26(workDir, { recursive: true });
99683
+ if (job.config.debug) {
99684
+ const logPath = join55(workDir, "render.log");
99685
+ restoreLogger = installDebugLogger(logPath, log2);
99686
+ log2.info("[Render] Debug artifacts enabled", { workDir, logPath });
99687
+ }
99553
99688
  log2.info("[Render] Pipeline started", {
99554
99689
  platform: process.platform,
99555
99690
  arch: process.arch,
@@ -99574,11 +99709,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
99574
99709
  playerReadyTimeoutMs: cfg.playerReadyTimeout,
99575
99710
  requestedWorkers: job.config.workers ?? "auto"
99576
99711
  });
99577
- if (!existsSync45(workDir)) mkdirSync26(workDir, { recursive: true });
99578
- if (job.config.debug) {
99579
- const logPath = join55(workDir, "render.log");
99580
- restoreLogger = installDebugLogger(logPath, log2);
99581
- }
99582
99712
  const entryFile = job.config.entryFile || "index.html";
99583
99713
  let htmlPath = join55(projectDir, entryFile);
99584
99714
  if (!existsSync45(htmlPath)) {
@@ -99796,8 +99926,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
99796
99926
  format: needsAlpha ? "png" : "jpeg",
99797
99927
  quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95,
99798
99928
  variables: job.config.variables,
99799
- deviceScaleFactor
99929
+ deviceScaleFactor,
99930
+ captureBeyondViewport: composition.videos.length > 0
99800
99931
  };
99932
+ updateCaptureObservability({
99933
+ captureBeyondViewport: captureOptions.captureBeyondViewport ?? false
99934
+ });
99801
99935
  const buildCaptureOptions = () => ({
99802
99936
  ...captureOptions,
99803
99937
  videoMetadataHints,
@@ -99926,6 +100060,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
99926
100060
  observability.checkpoint("capture_strategy", "resolved", {
99927
100061
  workerCount,
99928
100062
  forceScreenshot: captureForceScreenshot,
100063
+ captureBeyondViewport: captureOptions.captureBeyondViewport ?? false,
99929
100064
  useStreamingEncode,
99930
100065
  useLayeredComposite,
99931
100066
  usePageSideCompositing: usePageSideCompositingForTransitions,
@@ -102036,6 +102171,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
102036
102171
  // declare `data-composition-variables` leave this undefined and the
102037
102172
  // engine skips the `evaluateOnNewDocument` injection.
102038
102173
  variables: encoder.variables,
102174
+ captureBeyondViewport: (planVideos?.videos.length ?? 0) > 0,
102039
102175
  // lock the BeginFrame warmup loop to a fixed iteration count so
102040
102176
  // `beginFrameTimeTicks` is host-independent. Only chunks ever set this.
102041
102177
  lockWarmupTicks: true
@@ -104527,11 +104663,22 @@ var init_init = __esm({
104527
104663
  for (const f3 of readdirSync23(destDir2).filter((f4) => !f4.startsWith("."))) {
104528
104664
  console.log(` ${c.accent(f3)}`);
104529
104665
  }
104666
+ if (!skipSkills) {
104667
+ const { installAllSkills: installAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
104668
+ const args2 = process.env["CLAUDECODE"] ? ["--agent", "claude-code", "--yes"] : ["--yes"];
104669
+ await installAllSkills2({ cwd: destDir2, extraArgs: args2 });
104670
+ }
104530
104671
  console.log();
104531
104672
  console.log("Get started:");
104532
104673
  console.log();
104533
- console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
104534
- console.log(` ${c.accent("npx skills add heygen-com/hyperframes")}`);
104674
+ if (skipSkills) {
104675
+ console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
104676
+ console.log(` ${c.accent("npx skills add heygen-com/hyperframes --yes")}`);
104677
+ } else {
104678
+ console.log(
104679
+ ` ${c.accent("1.")} Restart your AI agent (new session) so it loads the skills.`
104680
+ );
104681
+ }
104535
104682
  console.log();
104536
104683
  console.log(` ${c.accent("2.")} Open this project with your AI coding agent:`);
104537
104684
  console.log(
@@ -104718,8 +104865,8 @@ ${c.dim("Use --example blank for offline use.")}`
104718
104865
  process.exit(0);
104719
104866
  }
104720
104867
  if (installSkills) {
104721
- const skillsCmd = await Promise.resolve().then(() => (init_skills(), skills_exports)).then((m2) => m2.default);
104722
- await runCommand(skillsCmd, { rawArgs: [] });
104868
+ const { installAllSkills: installAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
104869
+ await installAllSkills2({ cwd: destDir });
104723
104870
  }
104724
104871
  }
104725
104872
  R2.info("Opening studio preview...");
@@ -106649,6 +106796,10 @@ function buildDockerRunArgs(input2) {
106649
106796
  `${projectDir}:/project:ro`,
106650
106797
  "-v",
106651
106798
  `${outputDir}:/output`,
106799
+ // Keep debug artifacts on the mounted host output path. The producer roots
106800
+ // `.debug` at dirname(PRODUCER_RENDERS_DIR), so `/output/renders` maps to
106801
+ // `/output/.debug/<job id>` instead of a disposable container path.
106802
+ ...options.debug ? ["-e", "PRODUCER_RENDERS_DIR=/output/renders"] : [],
106652
106803
  imageTag,
106653
106804
  "/project",
106654
106805
  "--output",
@@ -106666,6 +106817,7 @@ function buildDockerRunArgs(input2) {
106666
106817
  ...options.videoBitrate ? ["--video-bitrate", options.videoBitrate] : [],
106667
106818
  ...options.videoFrameFormat && options.videoFrameFormat !== "auto" ? ["--video-frame-format", options.videoFrameFormat] : [],
106668
106819
  ...options.quiet ? ["--quiet"] : [],
106820
+ ...options.debug ? ["--debug"] : [],
106669
106821
  ...options.gpu ? ["--gpu"] : [],
106670
106822
  ...options.browserGpu ? [] : ["--no-browser-gpu"],
106671
106823
  ...options.hdrMode === "force-hdr" ? ["--hdr"] : [],
@@ -107413,6 +107565,7 @@ async function renderDocker(projectDir, outputPath, options) {
107413
107565
  entryFile: options.entryFile,
107414
107566
  outputResolution: options.outputResolution,
107415
107567
  pageSideCompositing: options.pageSideCompositing,
107568
+ debug: options.debug,
107416
107569
  pageNavigationTimeoutMs: options.pageNavigationTimeoutMs
107417
107570
  }
107418
107571
  });
@@ -107480,7 +107633,7 @@ async function renderLocal(projectDir, outputPath, options) {
107480
107633
  const producer = await loadProducer();
107481
107634
  const startTime = Date.now();
107482
107635
  const logger = createRenderTelemetryLogger(
107483
- producer.createConsoleLogger?.("info") ?? createNoopProducerLogger()
107636
+ producer.createConsoleLogger?.(options.debug ? "debug" : "info") ?? createNoopProducerLogger()
107484
107637
  );
107485
107638
  const job = producer.createRenderJob({
107486
107639
  fps: options.fps,
@@ -107503,7 +107656,8 @@ async function renderLocal(projectDir, outputPath, options) {
107503
107656
  videoFrameFormat: options.videoFrameFormat,
107504
107657
  variables: options.variables,
107505
107658
  entryFile: options.entryFile,
107506
- outputResolution: options.outputResolution
107659
+ outputResolution: options.outputResolution,
107660
+ debug: options.debug
107507
107661
  });
107508
107662
  const onProgress = options.quiet ? void 0 : (progressJob, message) => {
107509
107663
  renderProgress(progressJob.progress, message);
@@ -107882,6 +108036,11 @@ var init_render2 = __esm({
107882
108036
  description: "Suppress verbose output",
107883
108037
  default: false
107884
108038
  },
108039
+ debug: {
108040
+ type: "boolean",
108041
+ description: "Write full render diagnostics and keep intermediate artifacts under the producer .debug directory.",
108042
+ default: false
108043
+ },
107885
108044
  strict: {
107886
108045
  type: "boolean",
107887
108046
  description: "Fail render on lint errors",
@@ -108109,6 +108268,7 @@ var init_render2 = __esm({
108109
108268
  const browserGpuArg = args["browser-gpu"];
108110
108269
  const browserGpuMode = resolveBrowserGpuForCli(useDocker, browserGpuArg);
108111
108270
  const quiet = args.quiet ?? false;
108271
+ const debug = args.debug ?? false;
108112
108272
  const batchJson = args.json ?? false;
108113
108273
  const effectiveQuiet = quiet || batchPath != null && batchJson;
108114
108274
  const strictAll = args["strict-all"] ?? false;
@@ -108280,6 +108440,7 @@ var init_render2 = __esm({
108280
108440
  pageNavigationTimeoutMs,
108281
108441
  protocolTimeout,
108282
108442
  playerReadyTimeout,
108443
+ debug,
108283
108444
  exitAfterComplete: false,
108284
108445
  throwOnError: true,
108285
108446
  skipFeedback: true
@@ -108323,6 +108484,7 @@ var init_render2 = __esm({
108323
108484
  videoBitrate,
108324
108485
  videoFrameFormat,
108325
108486
  quiet,
108487
+ debug,
108326
108488
  variables,
108327
108489
  entryFile,
108328
108490
  outputResolution,
@@ -108348,6 +108510,7 @@ var init_render2 = __esm({
108348
108510
  videoFrameFormat,
108349
108511
  quiet,
108350
108512
  browserPath,
108513
+ debug,
108351
108514
  variables,
108352
108515
  entryFile,
108353
108516
  outputResolution,
@@ -112167,6 +112330,18 @@ function checkEnvironment() {
112167
112330
  }
112168
112331
  return { ok: true, detail: parts.join(" \xB7 ") };
112169
112332
  }
112333
+ async function checkWhisper() {
112334
+ const { findWhisper: findWhisper2, getInstallInstructions: getInstallInstructions2 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
112335
+ const result = findWhisper2();
112336
+ if (result) {
112337
+ return { ok: true, detail: result.executablePath };
112338
+ }
112339
+ return {
112340
+ ok: false,
112341
+ detail: "Not found (optional \u2014 needed for transcription)",
112342
+ hint: getInstallInstructions2()
112343
+ };
112344
+ }
112170
112345
  function redactHome(s2) {
112171
112346
  const home = process.env["HOME"] || process.env["USERPROFILE"];
112172
112347
  if (!home) return s2;
@@ -112221,6 +112396,7 @@ var init_doctor = __esm({
112221
112396
  checks.push({ name: "/dev/shm", run: checkShm });
112222
112397
  }
112223
112398
  checks.push({ name: "Environment", run: checkEnvironment });
112399
+ checks.push({ name: "whisper-cpp", run: checkWhisper });
112224
112400
  const outcomes = [];
112225
112401
  for (const check of checks) {
112226
112402
  const result = await check.run();
@@ -112541,6 +112717,58 @@ Run ${c.accent("hyperframes telemetry --help")} for usage.`
112541
112717
  }
112542
112718
  });
112543
112719
 
112720
+ // src/commands/events.ts
112721
+ var events_exports2 = {};
112722
+ __export(events_exports2, {
112723
+ default: () => events_default
112724
+ });
112725
+ var ALLOWED_EVENTS, ALLOWED_OUTCOMES, SKILL_SLUG, events_default;
112726
+ var init_events2 = __esm({
112727
+ "src/commands/events.ts"() {
112728
+ "use strict";
112729
+ init_dist();
112730
+ init_client();
112731
+ ALLOWED_EVENTS = ["skill_invoked", "skill_completed"];
112732
+ ALLOWED_OUTCOMES = ["success", "error", "abort"];
112733
+ SKILL_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
112734
+ events_default = defineCommand({
112735
+ meta: {
112736
+ name: "events",
112737
+ description: "Emit an anonymous skill-usage telemetry event (skills report their own invocation/outcome). Honors DO_NOT_TRACK / telemetry opt-out."
112738
+ },
112739
+ args: {
112740
+ skill: {
112741
+ type: "string",
112742
+ description: "Authoring skill slug, e.g. product-launch-video"
112743
+ },
112744
+ event: {
112745
+ type: "string",
112746
+ description: "Event name: skill_invoked | skill_completed (default: skill_invoked)",
112747
+ default: "skill_invoked"
112748
+ },
112749
+ outcome: {
112750
+ type: "string",
112751
+ description: "Optional outcome for completion events: success | error | abort"
112752
+ }
112753
+ },
112754
+ async run({ args }) {
112755
+ try {
112756
+ const skill = typeof args.skill === "string" ? args.skill.trim() : "";
112757
+ if (!SKILL_SLUG.test(skill)) return;
112758
+ const event = ALLOWED_EVENTS.includes(args.event) ? args.event : "skill_invoked";
112759
+ const props = { authoring_skill: skill };
112760
+ if (args.outcome && ALLOWED_OUTCOMES.includes(args.outcome)) {
112761
+ props["outcome"] = args.outcome;
112762
+ }
112763
+ trackEvent(event, props);
112764
+ await flush();
112765
+ } catch {
112766
+ }
112767
+ }
112768
+ });
112769
+ }
112770
+ });
112771
+
112544
112772
  // src/utils/compositionViewport.ts
112545
112773
  function parseViewportDimension(value) {
112546
112774
  if (!value) return null;
@@ -162861,6 +163089,7 @@ var commandLoaders = {
162861
163089
  skills: () => Promise.resolve().then(() => (init_skills(), skills_exports)).then((m2) => m2.default),
162862
163090
  feedback: () => Promise.resolve().then(() => (init_feedback2(), feedback_exports)).then((m2) => m2.default),
162863
163091
  telemetry: () => Promise.resolve().then(() => (init_telemetry(), telemetry_exports)).then((m2) => m2.default),
163092
+ events: () => Promise.resolve().then(() => (init_events2(), events_exports2)).then((m2) => m2.default),
162864
163093
  validate: () => Promise.resolve().then(() => (init_validate(), validate_exports)).then((m2) => m2.default),
162865
163094
  snapshot: () => Promise.resolve().then(() => (init_snapshot(), snapshot_exports)).then((m2) => m2.default),
162866
163095
  capture: () => Promise.resolve().then(() => (init_capture2(), capture_exports2)).then((m2) => m2.default),
@@ -162891,7 +163120,7 @@ var _flushSync;
162891
163120
  var _trackCliError;
162892
163121
  var _trackCommandResult;
162893
163122
  var _printUpdateNotice;
162894
- if (!isHelp && command !== "telemetry" && command !== "unknown") {
163123
+ if (!isHelp && command !== "telemetry" && command !== "events" && command !== "unknown") {
162895
163124
  Promise.resolve().then(() => (init_telemetry2(), telemetry_exports2)).then((mod) => {
162896
163125
  _flush = mod.flush;
162897
163126
  _flushSync = mod.flushSync;
@@ -162902,7 +163131,7 @@ if (!isHelp && command !== "telemetry" && command !== "unknown") {
162902
163131
  if (mod.shouldTrack()) mod.incrementCommandCount();
162903
163132
  });
162904
163133
  }
162905
- if (!isHelp && !hasJsonFlag && command !== "upgrade") {
163134
+ if (!isHelp && !hasJsonFlag && command !== "upgrade" && command !== "events") {
162906
163135
  Promise.resolve().then(() => (init_autoUpdate(), autoUpdate_exports)).then((mod) => mod.reportCompletedUpdate()).catch(() => {
162907
163136
  });
162908
163137
  Promise.resolve().then(() => (init_updateCheck(), updateCheck_exports)).then(async (mod) => {