hyperframes 0.1.13 → 0.1.14

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
@@ -422,7 +422,7 @@ var VERSION;
422
422
  var init_version = __esm({
423
423
  "src/version.ts"() {
424
424
  "use strict";
425
- VERSION = true ? "0.1.13" : "0.0.0-dev";
425
+ VERSION = true ? "0.1.14" : "0.0.0-dev";
426
426
  }
427
427
  });
428
428
 
@@ -2682,7 +2682,7 @@ import { join as join2 } from "path";
2682
2682
  import { get as httpsGet } from "https";
2683
2683
  import { pipeline } from "stream/promises";
2684
2684
  function downloadFile(url, dest) {
2685
- return new Promise((resolve21, reject) => {
2685
+ return new Promise((resolve22, reject) => {
2686
2686
  const follow = (u) => {
2687
2687
  httpsGet(u, (res) => {
2688
2688
  if (res.statusCode === 301 || res.statusCode === 302) {
@@ -2697,7 +2697,7 @@ function downloadFile(url, dest) {
2697
2697
  return;
2698
2698
  }
2699
2699
  const file = createWriteStream(dest);
2700
- pipeline(res, file).then(resolve21).catch(reject);
2700
+ pipeline(res, file).then(resolve22).catch(reject);
2701
2701
  }).on("error", reject);
2702
2702
  };
2703
2703
  follow(url);
@@ -2854,7 +2854,7 @@ var init_manager = __esm({
2854
2854
  "src/whisper/manager.ts"() {
2855
2855
  "use strict";
2856
2856
  MODELS_DIR = join2(homedir2(), ".cache", "hyperframes", "whisper", "models");
2857
- DEFAULT_MODEL = "base.en";
2857
+ DEFAULT_MODEL = "small.en";
2858
2858
  BUILD_DIR = join2(homedir2(), ".cache", "hyperframes", "whisper", "whisper.cpp");
2859
2859
  WHISPER_REPO = "https://github.com/ggml-org/whisper.cpp.git";
2860
2860
  }
@@ -3143,6 +3143,169 @@ Examples:
3143
3143
  }
3144
3144
  });
3145
3145
 
3146
+ // src/whisper/normalize.ts
3147
+ var normalize_exports = {};
3148
+ __export(normalize_exports, {
3149
+ detectFormat: () => detectFormat,
3150
+ loadTranscript: () => loadTranscript,
3151
+ patchCaptionHtml: () => patchCaptionHtml
3152
+ });
3153
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
3154
+ import { extname, join as join4 } from "path";
3155
+ function detectFormat(filePath) {
3156
+ const ext = extname(filePath).toLowerCase();
3157
+ if (ext === ".srt") return "srt";
3158
+ if (ext === ".vtt") return "vtt";
3159
+ if (ext === ".json") return detectJsonFormat(JSON.parse(readFileSync3(filePath, "utf-8")));
3160
+ throw new Error(`Unsupported transcript file extension: ${ext}. Use .json, .srt, or .vtt`);
3161
+ }
3162
+ function detectJsonFormat(raw) {
3163
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
3164
+ const obj = raw;
3165
+ if (obj.transcription && Array.isArray(obj.transcription)) return "whisper-cpp";
3166
+ if (obj.words && Array.isArray(obj.words)) return "openai";
3167
+ }
3168
+ if (Array.isArray(raw) && raw[0]?.text !== void 0 && raw[0]?.start !== void 0) {
3169
+ return "words-json";
3170
+ }
3171
+ throw new Error(
3172
+ "Unrecognized JSON transcript format. Expected whisper.cpp (transcription[].tokens), OpenAI API (words[]), or normalized ([{text, start, end}])."
3173
+ );
3174
+ }
3175
+ function parseWhisperCpp(data) {
3176
+ const words = [];
3177
+ const transcription = data.transcription;
3178
+ for (const seg of transcription ?? []) {
3179
+ for (const token of seg.tokens ?? []) {
3180
+ const text = (token.text ?? "").trim();
3181
+ if (!text || text.startsWith("[_") || text.startsWith("[BLANK")) continue;
3182
+ const isPunctuation = /^[.,!?;:'")\]}>…–—-]+$/.test(text);
3183
+ const lastWord = words[words.length - 1];
3184
+ if (isPunctuation && lastWord) {
3185
+ lastWord.text += text;
3186
+ lastWord.end = round3((token.offsets?.to ?? 0) / 1e3);
3187
+ continue;
3188
+ }
3189
+ words.push({
3190
+ text,
3191
+ start: round3((token.offsets?.from ?? 0) / 1e3),
3192
+ end: round3((token.offsets?.to ?? 0) / 1e3)
3193
+ });
3194
+ }
3195
+ }
3196
+ return words;
3197
+ }
3198
+ function parseOpenAI(data) {
3199
+ const rawWords = data.words ?? [];
3200
+ return rawWords.map((w) => ({
3201
+ text: (w.word ?? w.text ?? "").trim(),
3202
+ start: round3(w.start ?? 0),
3203
+ end: round3(w.end ?? 0)
3204
+ })).filter((w) => w.text.length > 0);
3205
+ }
3206
+ function parseSrt(content) {
3207
+ const blocks = content.trim().split(/\n\n+/);
3208
+ const words = [];
3209
+ for (const block of blocks) {
3210
+ const lines = block.trim().split("\n");
3211
+ const timeLine = lines.find((l) => l.includes("-->"));
3212
+ if (!timeLine) continue;
3213
+ const [startStr, endStr] = timeLine.split("-->").map((s) => s.trim());
3214
+ if (!startStr || !endStr) continue;
3215
+ const text = lines.slice(lines.indexOf(timeLine) + 1).join(" ").replace(/<[^>]+>/g, "").trim();
3216
+ if (!text) continue;
3217
+ words.push({
3218
+ text,
3219
+ start: parseSrtTimestamp(startStr),
3220
+ end: parseSrtTimestamp(endStr)
3221
+ });
3222
+ }
3223
+ return words;
3224
+ }
3225
+ function parseVtt(content) {
3226
+ const body = content.replace(/^WEBVTT[^\n]*\n/, "").replace(/^[A-Z-]+:.*\n/gm, "");
3227
+ const blocks = body.trim().split(/\n\n+/);
3228
+ const words = [];
3229
+ for (const block of blocks) {
3230
+ const lines = block.trim().split("\n");
3231
+ const timeLine = lines.find((l) => l.includes("-->"));
3232
+ if (!timeLine) continue;
3233
+ const [startStr, endStr] = timeLine.split("-->").map((s) => s.trim());
3234
+ if (!startStr || !endStr) continue;
3235
+ const text = lines.slice(lines.indexOf(timeLine) + 1).join(" ").replace(/<[^>]+>/g, "").trim();
3236
+ if (!text) continue;
3237
+ words.push({
3238
+ text,
3239
+ start: parseVttTimestamp(startStr),
3240
+ end: parseVttTimestamp(endStr)
3241
+ });
3242
+ }
3243
+ return words;
3244
+ }
3245
+ function parseSrtTimestamp(ts) {
3246
+ const m = ts.match(/(\d+):(\d+):(\d+)[,.](\d+)/);
3247
+ if (!m) return 0;
3248
+ return parseInt(m[1], 10) * 3600 + parseInt(m[2], 10) * 60 + parseInt(m[3], 10) + parseInt(m[4].padEnd(3, "0"), 10) / 1e3;
3249
+ }
3250
+ function parseVttTimestamp(ts) {
3251
+ const parts = ts.split(":");
3252
+ if (parts.length === 3) return parseSrtTimestamp(ts);
3253
+ if (parts.length === 2) {
3254
+ const [min, secMs] = parts;
3255
+ const [sec, ms] = (secMs ?? "0.0").split(".");
3256
+ return parseInt(min, 10) * 60 + parseInt(sec, 10) + parseInt((ms ?? "0").padEnd(3, "0"), 10) / 1e3;
3257
+ }
3258
+ return 0;
3259
+ }
3260
+ function round3(n) {
3261
+ return Math.round(n * 1e3) / 1e3;
3262
+ }
3263
+ function loadTranscript(filePath) {
3264
+ const ext = extname(filePath).toLowerCase();
3265
+ const content = readFileSync3(filePath, "utf-8");
3266
+ if (ext === ".srt") return { words: parseSrt(content), format: "srt" };
3267
+ if (ext === ".vtt") return { words: parseVtt(content), format: "vtt" };
3268
+ const parsed = JSON.parse(content);
3269
+ const format = detectJsonFormat(parsed);
3270
+ const words = format === "whisper-cpp" ? parseWhisperCpp(parsed) : format === "openai" ? parseOpenAI(parsed) : parsed.map((w) => ({
3271
+ text: w.text.trim(),
3272
+ start: round3(w.start),
3273
+ end: round3(w.end)
3274
+ }));
3275
+ return { words, format };
3276
+ }
3277
+ function patchCaptionHtml(dir, words) {
3278
+ if (words.length === 0) return;
3279
+ const wordsJson = JSON.stringify(words, null, 2).replace(/\n/g, "\n ");
3280
+ let htmlFiles;
3281
+ try {
3282
+ htmlFiles = readdirSync2(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join4(e.parentPath ?? e.path, e.name));
3283
+ } catch {
3284
+ return;
3285
+ }
3286
+ for (const file of htmlFiles) {
3287
+ let content = readFileSync3(file, "utf-8");
3288
+ const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
3289
+ let scriptMatch = null;
3290
+ let transcriptMatch = null;
3291
+ for (const block of scriptBlocks) {
3292
+ scriptMatch = scriptMatch ?? block.match(/const script = \[[\s\S]*?\];/);
3293
+ transcriptMatch = transcriptMatch ?? block.match(/const TRANSCRIPT = \[[\s\S]*?\];/);
3294
+ }
3295
+ const match = scriptMatch ?? transcriptMatch;
3296
+ if (match) {
3297
+ const varName = scriptMatch ? "script" : "TRANSCRIPT";
3298
+ content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
3299
+ writeFileSync2(file, content, "utf-8");
3300
+ }
3301
+ }
3302
+ }
3303
+ var init_normalize = __esm({
3304
+ "src/whisper/normalize.ts"() {
3305
+ "use strict";
3306
+ }
3307
+ });
3308
+
3146
3309
  // ../core/src/parsers/gsapParser.ts
3147
3310
  function parseObjectLiteral(str) {
3148
3311
  const result = {};
@@ -3533,6 +3696,19 @@ function lintHyperframeHtml(html, options = {}) {
3533
3696
  )
3534
3697
  });
3535
3698
  }
3699
+ const clipIds = /* @__PURE__ */ new Map();
3700
+ const clipClasses = /* @__PURE__ */ new Map();
3701
+ for (const tag of tags) {
3702
+ const classAttr = readAttr(tag.raw, "class") || "";
3703
+ const classes = classAttr.split(/\s+/).filter(Boolean);
3704
+ if (!classes.includes("clip")) continue;
3705
+ const id = readAttr(tag.raw, "id");
3706
+ const info = { tag: tag.name, id: id || "", classes: classAttr };
3707
+ if (id) clipIds.set(`#${id}`, info);
3708
+ for (const cls of classes) {
3709
+ if (cls !== "clip") clipClasses.set(`.${cls}`, info);
3710
+ }
3711
+ }
3536
3712
  const classUsage = countClassUsage(tags);
3537
3713
  for (const script of scripts) {
3538
3714
  const localTimelineCompId = readRegisteredTimelineCompositionId(script.content);
@@ -3575,24 +3751,39 @@ ${right.raw}`)
3575
3751
  });
3576
3752
  }
3577
3753
  }
3754
+ for (const win of gsapWindows) {
3755
+ const sel = win.targetSelector;
3756
+ const clipInfo = clipIds.get(sel) || clipClasses.get(sel);
3757
+ if (!clipInfo) continue;
3758
+ const elDesc = `<${clipInfo.tag}${clipInfo.id ? ` id="${clipInfo.id}"` : ""} class="${clipInfo.classes}">`;
3759
+ pushFinding({
3760
+ code: "gsap_animates_clip_element",
3761
+ severity: "error",
3762
+ message: `GSAP animation targets a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility \u2014 animate an inner wrapper instead.`,
3763
+ selector: sel,
3764
+ elementId: clipInfo.id || void 0,
3765
+ fixHint: "Wrap content in a child <div> and target that with GSAP.",
3766
+ snippet: truncateSnippet(win.raw)
3767
+ });
3768
+ }
3578
3769
  if (!localTimelineCompId || localTimelineCompId === rootCompositionId) {
3579
3770
  continue;
3580
3771
  }
3581
- for (const window3 of gsapWindows) {
3582
- if (!isSuspiciousGlobalSelector(window3.targetSelector)) {
3772
+ for (const win of gsapWindows) {
3773
+ if (!isSuspiciousGlobalSelector(win.targetSelector)) {
3583
3774
  continue;
3584
3775
  }
3585
- const className = getSingleClassSelector(window3.targetSelector);
3776
+ const className = getSingleClassSelector(win.targetSelector);
3586
3777
  if (className && (classUsage.get(className) || 0) < 2) {
3587
3778
  continue;
3588
3779
  }
3589
3780
  pushFinding({
3590
3781
  code: "unscoped_gsap_selector",
3591
3782
  severity: "warning",
3592
- message: `Timeline "${localTimelineCompId}" uses unscoped selector "${window3.targetSelector}" that will target elements in ALL compositions when bundled, causing data loss (opacity, transforms, etc.).`,
3593
- selector: window3.targetSelector,
3594
- fixHint: `Scope the selector: \`[data-composition-id="${localTimelineCompId}"] ${window3.targetSelector}\` or use a unique id.`,
3595
- snippet: truncateSnippet(window3.raw)
3783
+ message: `Timeline "${localTimelineCompId}" uses unscoped selector "${win.targetSelector}" that will target elements in ALL compositions when bundled, causing data loss (opacity, transforms, etc.).`,
3784
+ selector: win.targetSelector,
3785
+ fixHint: `Scope the selector: \`[data-composition-id="${localTimelineCompId}"] ${win.targetSelector}\` or use a unique id.`,
3786
+ snippet: truncateSnippet(win.raw)
3596
3787
  });
3597
3788
  }
3598
3789
  }
@@ -3845,6 +4036,58 @@ ${right.raw}`)
3845
4036
  }
3846
4037
  }
3847
4038
  }
4039
+ for (const script of scripts) {
4040
+ const content = script.content;
4041
+ const hasExitTween = /\.to\s*\([^,]+,\s*\{[^}]*opacity\s*:\s*0/.test(content);
4042
+ const hasHardKill = /\.set\s*\([^,]+,\s*\{[^}]*(?:visibility\s*:\s*["']hidden["']|opacity\s*:\s*0)/.test(content);
4043
+ const hasCaptionLoop = /forEach|\.forEach\s*\(/.test(content) && /createElement|caption|group|cg-/.test(content);
4044
+ if (hasCaptionLoop && hasExitTween && !hasHardKill) {
4045
+ pushFinding({
4046
+ code: "caption_exit_missing_hard_kill",
4047
+ severity: "warning",
4048
+ message: "Caption exit animations (tl.to with opacity: 0) detected without a hard tl.set kill. Exit tweens can fail when karaoke word-level tweens conflict, leaving captions stuck on screen.",
4049
+ fixHint: 'Add `tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end)` after every exit tl.to animation as a deterministic kill.'
4050
+ });
4051
+ }
4052
+ }
4053
+ for (const style of styles) {
4054
+ const content = style.content;
4055
+ const captionBlocks = content.matchAll(
4056
+ /(\.caption[-_]?(?:group|container|text|line|word)|#caption[-_]?container)\s*\{([^}]+)\}/gi
4057
+ );
4058
+ for (const [, selector, body] of captionBlocks) {
4059
+ if (!body) continue;
4060
+ const hasNowrap = /white-space\s*:\s*nowrap/i.test(body);
4061
+ const hasMaxWidth = /max-width/i.test(body);
4062
+ if (hasNowrap && !hasMaxWidth) {
4063
+ pushFinding({
4064
+ code: "caption_text_overflow_risk",
4065
+ severity: "warning",
4066
+ selector: (selector ?? "").trim(),
4067
+ message: `Caption selector "${(selector ?? "").trim()}" has white-space: nowrap but no max-width. Long phrases will clip off-screen.`,
4068
+ fixHint: "Add max-width: 1600px (landscape) or max-width: 900px (portrait) and overflow: hidden."
4069
+ });
4070
+ }
4071
+ }
4072
+ }
4073
+ for (const style of styles) {
4074
+ const content = style.content;
4075
+ const captionBlocks = content.matchAll(
4076
+ /(\.caption[-_]?(?:group|container|text|line)|#caption[-_]?container)\s*\{([^}]+)\}/gi
4077
+ );
4078
+ for (const [, selector, body] of captionBlocks) {
4079
+ if (!body) continue;
4080
+ if (/position\s*:\s*relative/i.test(body)) {
4081
+ pushFinding({
4082
+ code: "caption_container_relative_position",
4083
+ severity: "warning",
4084
+ selector: (selector ?? "").trim(),
4085
+ message: `Caption selector "${(selector ?? "").trim()}" uses position: relative which causes overflow and breaks caption stacking.`,
4086
+ fixHint: "Use position: absolute for all caption elements."
4087
+ });
4088
+ }
4089
+ }
4090
+ }
3848
4091
  {
3849
4092
  const externalScriptRe = /<script\b[^>]*\bsrc=["'](https?:\/\/[^"']+)["'][^>]*>/gi;
3850
4093
  let match;
@@ -3863,11 +4106,13 @@ ${right.raw}`)
3863
4106
  }
3864
4107
  }
3865
4108
  const errorCount = findings.filter((finding) => finding.severity === "error").length;
3866
- const warningCount = findings.length - errorCount;
4109
+ const warningCount = findings.filter((finding) => finding.severity === "warning").length;
4110
+ const infoCount = findings.filter((finding) => finding.severity === "info").length;
3867
4111
  return {
3868
4112
  ok: errorCount === 0,
3869
4113
  errorCount,
3870
4114
  warningCount,
4115
+ infoCount,
3871
4116
  findings
3872
4117
  };
3873
4118
  }
@@ -4243,30 +4488,33 @@ var init_lint = __esm({
4243
4488
  });
4244
4489
 
4245
4490
  // src/utils/lintProject.ts
4246
- import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
4247
- import { join as join4, resolve } from "path";
4491
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
4492
+ import { join as join5, resolve } from "path";
4248
4493
  function lintProject(project) {
4249
4494
  const results = [];
4250
4495
  let totalErrors = 0;
4251
4496
  let totalWarnings = 0;
4252
- const rootHtml = readFileSync3(project.indexPath, "utf-8");
4497
+ let totalInfos = 0;
4498
+ const rootHtml = readFileSync4(project.indexPath, "utf-8");
4253
4499
  const rootResult = lintHyperframeHtml(rootHtml, { filePath: project.indexPath });
4254
4500
  results.push({ file: "index.html", result: rootResult });
4255
4501
  totalErrors += rootResult.errorCount;
4256
4502
  totalWarnings += rootResult.warningCount;
4503
+ totalInfos += rootResult.infoCount;
4257
4504
  const compositionsDir = resolve(project.dir, "compositions");
4258
4505
  if (existsSync5(compositionsDir)) {
4259
- const files = readdirSync2(compositionsDir).filter((f) => f.endsWith(".html"));
4506
+ const files = readdirSync3(compositionsDir).filter((f) => f.endsWith(".html"));
4260
4507
  for (const file of files) {
4261
- const filePath = join4(compositionsDir, file);
4262
- const html = readFileSync3(filePath, "utf-8");
4508
+ const filePath = join5(compositionsDir, file);
4509
+ const html = readFileSync4(filePath, "utf-8");
4263
4510
  const result = lintHyperframeHtml(html, { filePath });
4264
4511
  results.push({ file: `compositions/${file}`, result });
4265
4512
  totalErrors += result.errorCount;
4266
4513
  totalWarnings += result.warningCount;
4514
+ totalInfos += result.infoCount;
4267
4515
  }
4268
4516
  }
4269
- return { results, totalErrors, totalWarnings };
4517
+ return { results, totalErrors, totalWarnings, totalInfos };
4270
4518
  }
4271
4519
  function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings) {
4272
4520
  return strictErrors && totalErrors > 0 || strictAll && (totalErrors > 0 || totalWarnings > 0);
@@ -4279,14 +4527,20 @@ var init_lintProject = __esm({
4279
4527
  });
4280
4528
 
4281
4529
  // src/utils/lintFormat.ts
4282
- function formatLintFindings({ results, totalErrors, totalWarnings }, options = {}) {
4283
- const { showElementId = true, showSummary = false, errorsFirst = false } = options;
4530
+ function formatLintFindings({ results, totalErrors, totalWarnings, totalInfos }, options = {}) {
4531
+ const {
4532
+ showElementId = true,
4533
+ showSummary = false,
4534
+ errorsFirst = false,
4535
+ verbose = false
4536
+ } = options;
4284
4537
  const lines = [];
4285
4538
  const multiFile = results.length > 1;
4286
4539
  for (const { file, result } of results) {
4287
4540
  if (result.findings.length === 0) continue;
4288
4541
  const format = (finding) => {
4289
- const prefix = finding.severity === "error" ? c.error("\u2717") : c.warn("\u26A0");
4542
+ if (!verbose && finding.severity === "info") return;
4543
+ const prefix = finding.severity === "error" ? c.error("\u2717") : finding.severity === "warning" ? c.warn("\u26A0") : c.dim("\u2139");
4290
4544
  const fileLabel = multiFile ? c.dim(`[${file}] `) : "";
4291
4545
  const loc = showElementId && finding.elementId ? ` ${c.accent(`[${finding.elementId}]`)}` : "";
4292
4546
  lines.push(` ${prefix} ${fileLabel}${c.bold(finding.code)}${loc}: ${finding.message}`);
@@ -4295,6 +4549,9 @@ function formatLintFindings({ results, totalErrors, totalWarnings }, options = {
4295
4549
  if (errorsFirst) {
4296
4550
  for (const f of result.findings) if (f.severity === "error") format(f);
4297
4551
  for (const f of result.findings) if (f.severity === "warning") format(f);
4552
+ if (verbose) {
4553
+ for (const f of result.findings) if (f.severity === "info") format(f);
4554
+ }
4298
4555
  } else {
4299
4556
  for (const f of result.findings) format(f);
4300
4557
  }
@@ -4302,7 +4559,9 @@ function formatLintFindings({ results, totalErrors, totalWarnings }, options = {
4302
4559
  if (showSummary) {
4303
4560
  const icon = totalErrors > 0 ? c.error("\u25C7") : c.success("\u25C7");
4304
4561
  lines.push("");
4305
- lines.push(`${icon} ${totalErrors} error(s), ${totalWarnings} warning(s)`);
4562
+ const summaryParts = [`${totalErrors} error(s)`, `${totalWarnings} warning(s)`];
4563
+ if (verbose && totalInfos > 0) summaryParts.push(`${totalInfos} info(s)`);
4564
+ lines.push(`${icon} ${summaryParts.join(", ")}`);
4306
4565
  }
4307
4566
  return lines;
4308
4567
  }
@@ -4357,19 +4616,19 @@ var init_fileWatcher = __esm({
4357
4616
  });
4358
4617
 
4359
4618
  // ../core/src/studio-api/helpers/safePath.ts
4360
- import { resolve as resolve2, sep, join as join5 } from "path";
4361
- import { readdirSync as readdirSync3 } from "fs";
4619
+ import { resolve as resolve2, sep, join as join6 } from "path";
4620
+ import { readdirSync as readdirSync4 } from "fs";
4362
4621
  function isSafePath(base, resolved) {
4363
4622
  const norm = resolve2(base) + sep;
4364
4623
  return resolved.startsWith(norm) || resolved === resolve2(base);
4365
4624
  }
4366
4625
  function walkDir(dir, prefix = "") {
4367
4626
  const files = [];
4368
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
4627
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
4369
4628
  if (IGNORE_DIRS.has(entry.name)) continue;
4370
4629
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
4371
4630
  if (entry.isDirectory()) {
4372
- files.push(...walkDir(join5(dir, entry.name), rel));
4631
+ files.push(...walkDir(join6(dir, entry.name), rel));
4373
4632
  } else {
4374
4633
  files.push(rel);
4375
4634
  }
@@ -4414,7 +4673,7 @@ var init_projects = __esm({
4414
4673
  });
4415
4674
 
4416
4675
  // ../core/src/studio-api/routes/files.ts
4417
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
4676
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
4418
4677
  import { resolve as resolve3, dirname as dirname2 } from "path";
4419
4678
  function registerFileRoutes(api, adapter2) {
4420
4679
  api.get("/projects/:id/files/*", async (c2) => {
@@ -4425,7 +4684,7 @@ function registerFileRoutes(api, adapter2) {
4425
4684
  if (!isSafePath(project.dir, file) || !existsSync6(file)) {
4426
4685
  return c2.text("not found", 404);
4427
4686
  }
4428
- const content = readFileSync4(file, "utf-8");
4687
+ const content = readFileSync5(file, "utf-8");
4429
4688
  return c2.json({ filename: filePath, content });
4430
4689
  });
4431
4690
  api.put("/projects/:id/files/*", async (c2) => {
@@ -4439,7 +4698,7 @@ function registerFileRoutes(api, adapter2) {
4439
4698
  const dir = dirname2(file);
4440
4699
  if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
4441
4700
  const body = await c2.req.text();
4442
- writeFileSync2(file, body, "utf-8");
4701
+ writeFileSync3(file, body, "utf-8");
4443
4702
  return c2.json({ ok: true });
4444
4703
  });
4445
4704
  }
@@ -4489,18 +4748,18 @@ var init_mime = __esm({
4489
4748
  });
4490
4749
 
4491
4750
  // ../core/src/studio-api/helpers/subComposition.ts
4492
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
4493
- import { join as join6 } from "path";
4751
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
4752
+ import { join as join7 } from "path";
4494
4753
  function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref) {
4495
- const compFile = join6(projectDir, compPath);
4754
+ const compFile = join7(projectDir, compPath);
4496
4755
  if (!existsSync7(compFile)) return null;
4497
- const rawComp = readFileSync5(compFile, "utf-8");
4756
+ const rawComp = readFileSync6(compFile, "utf-8");
4498
4757
  const templateMatch = rawComp.match(/<template[^>]*>([\s\S]*)<\/template>/i);
4499
4758
  const content = templateMatch?.[1] ?? rawComp;
4500
- const indexPath = join6(projectDir, "index.html");
4759
+ const indexPath = join7(projectDir, "index.html");
4501
4760
  let headContent = "";
4502
4761
  if (existsSync7(indexPath)) {
4503
- const indexHtml = readFileSync5(indexPath, "utf-8");
4762
+ const indexHtml = readFileSync6(indexPath, "utf-8");
4504
4763
  const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
4505
4764
  headContent = headMatch?.[1] ?? "";
4506
4765
  }
@@ -4534,7 +4793,7 @@ var init_subComposition = __esm({
4534
4793
  });
4535
4794
 
4536
4795
  // ../core/src/studio-api/routes/preview.ts
4537
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync } from "fs";
4796
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync } from "fs";
4538
4797
  import { resolve as resolve4 } from "path";
4539
4798
  function registerPreviewRoutes(api, adapter2) {
4540
4799
  api.get("/projects/:id/preview", async (c2) => {
@@ -4545,7 +4804,7 @@ function registerPreviewRoutes(api, adapter2) {
4545
4804
  if (!bundled) {
4546
4805
  const indexPath = resolve4(project.dir, "index.html");
4547
4806
  if (!existsSync8(indexPath)) return c2.text("not found", 404);
4548
- bundled = readFileSync6(indexPath, "utf-8");
4807
+ bundled = readFileSync7(indexPath, "utf-8");
4549
4808
  }
4550
4809
  if (!bundled.includes("hyperframe.runtime") && !bundled.includes("hyperframes-preview-runtime")) {
4551
4810
  const runtimeTag = `<script src="${adapter2.runtimeUrl}"></script>`;
@@ -4560,7 +4819,7 @@ ${runtimeTag}`;
4560
4819
  return c2.html(bundled);
4561
4820
  } catch {
4562
4821
  const file = resolve4(project.dir, "index.html");
4563
- if (existsSync8(file)) return c2.html(readFileSync6(file, "utf-8"));
4822
+ if (existsSync8(file)) return c2.html(readFileSync7(file, "utf-8"));
4564
4823
  return c2.text("not found", 404);
4565
4824
  }
4566
4825
  });
@@ -4591,7 +4850,7 @@ ${runtimeTag}`;
4591
4850
  }
4592
4851
  const contentType = getMimeType(subPath);
4593
4852
  const isText2 = /\.(html|css|js|json|svg|txt|md)$/i.test(subPath);
4594
- const buffer = isText2 ? Buffer.from(readFileSync6(file, "utf-8"), "utf-8") : readFileSync6(file);
4853
+ const buffer = isText2 ? Buffer.from(readFileSync7(file, "utf-8"), "utf-8") : readFileSync7(file);
4595
4854
  const totalSize2 = buffer.length;
4596
4855
  const rangeHeader = c2.req.header("Range");
4597
4856
  if (rangeHeader) {
@@ -4631,8 +4890,8 @@ var init_preview = __esm({
4631
4890
  });
4632
4891
 
4633
4892
  // ../core/src/studio-api/routes/lint.ts
4634
- import { readFileSync as readFileSync7 } from "fs";
4635
- import { join as join7 } from "path";
4893
+ import { readFileSync as readFileSync8 } from "fs";
4894
+ import { join as join8 } from "path";
4636
4895
  function registerLintRoutes(api, adapter2) {
4637
4896
  api.get("/projects/:id/lint", async (c2) => {
4638
4897
  const project = await adapter2.resolveProject(c2.req.param("id"));
@@ -4641,7 +4900,7 @@ function registerLintRoutes(api, adapter2) {
4641
4900
  const htmlFiles = walkDir(project.dir).filter((f) => f.endsWith(".html"));
4642
4901
  const allFindings = [];
4643
4902
  for (const file of htmlFiles) {
4644
- const content = readFileSync7(join7(project.dir, file), "utf-8");
4903
+ const content = readFileSync8(join8(project.dir, file), "utf-8");
4645
4904
  const result = await adapter2.lint(content, { filePath: file });
4646
4905
  if (result?.findings) {
4647
4906
  for (const f of result.findings) {
@@ -4665,8 +4924,8 @@ var init_lint2 = __esm({
4665
4924
 
4666
4925
  // ../core/src/studio-api/routes/render.ts
4667
4926
  import { streamSSE } from "hono/streaming";
4668
- import { existsSync as existsSync9, readFileSync as readFileSync8, mkdirSync as mkdirSync5, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
4669
- import { join as join8 } from "path";
4927
+ import { existsSync as existsSync9, readFileSync as readFileSync9, mkdirSync as mkdirSync5, unlinkSync, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
4928
+ import { join as join9 } from "path";
4670
4929
  function registerRenderRoutes(api, adapter2) {
4671
4930
  const renderJobs = /* @__PURE__ */ new Map();
4672
4931
  const TTL_MS = 3e5;
@@ -4703,7 +4962,7 @@ function registerRenderRoutes(api, adapter2) {
4703
4962
  const rendersDir = adapter2.rendersDir(project);
4704
4963
  if (!existsSync9(rendersDir)) mkdirSync5(rendersDir, { recursive: true });
4705
4964
  const ext = format === "webm" ? ".webm" : ".mp4";
4706
- const outputPath = join8(rendersDir, `${jobId}${ext}`);
4965
+ const outputPath = join9(rendersDir, `${jobId}${ext}`);
4707
4966
  const jobState = adapter2.startRender({
4708
4967
  project,
4709
4968
  outputPath,
@@ -4764,7 +5023,7 @@ function registerRenderRoutes(api, adapter2) {
4764
5023
  const isWebm = job.outputPath.endsWith(".webm");
4765
5024
  const contentType = isWebm ? "video/webm" : "video/mp4";
4766
5025
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
4767
- const content = readFileSync8(job.outputPath);
5026
+ const content = readFileSync9(job.outputPath);
4768
5027
  return new Response(content, {
4769
5028
  headers: {
4770
5029
  "Content-Type": contentType,
@@ -4783,7 +5042,7 @@ function registerRenderRoutes(api, adapter2) {
4783
5042
  const isWebm = job.outputPath.endsWith(".webm");
4784
5043
  const contentType = isWebm ? "video/webm" : "video/mp4";
4785
5044
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
4786
- const content = readFileSync8(job.outputPath);
5045
+ const content = readFileSync9(job.outputPath);
4787
5046
  return new Response(content, {
4788
5047
  headers: {
4789
5048
  "Content-Type": contentType,
@@ -4797,7 +5056,7 @@ function registerRenderRoutes(api, adapter2) {
4797
5056
  if (state.id === jobId && state.outputPath) {
4798
5057
  const dir = state.outputPath.replace(/\/[^/]+$/, "");
4799
5058
  for (const ext of [".mp4", ".webm", ".meta.json"]) {
4800
- const fp = join8(dir, `${jobId}${ext}`);
5059
+ const fp = join9(dir, `${jobId}${ext}`);
4801
5060
  if (existsSync9(fp)) unlinkSync(fp);
4802
5061
  }
4803
5062
  break;
@@ -4806,21 +5065,41 @@ function registerRenderRoutes(api, adapter2) {
4806
5065
  renderJobs.delete(jobId);
4807
5066
  return c2.json({ deleted: true });
4808
5067
  });
5068
+ api.get("/projects/:id/renders/file/*", async (c2) => {
5069
+ const project = await adapter2.resolveProject(c2.req.param("id"));
5070
+ if (!project) return c2.json({ error: "not found" }, 404);
5071
+ const filename = c2.req.path.split("/renders/file/")[1];
5072
+ if (!filename) return c2.json({ error: "missing filename" }, 400);
5073
+ const rendersDir = adapter2.rendersDir(project);
5074
+ const fp = join9(rendersDir, filename);
5075
+ if (!existsSync9(fp)) return c2.json({ error: "not found" }, 404);
5076
+ const isWebm = fp.endsWith(".webm");
5077
+ const contentType = isWebm ? "video/webm" : "video/mp4";
5078
+ const content = readFileSync9(fp);
5079
+ return new Response(content, {
5080
+ headers: {
5081
+ "Content-Type": contentType,
5082
+ "Content-Disposition": `inline; filename="${filename}"`,
5083
+ "Accept-Ranges": "bytes",
5084
+ "Content-Length": String(content.length)
5085
+ }
5086
+ });
5087
+ });
4809
5088
  api.get("/projects/:id/renders", async (c2) => {
4810
5089
  const project = await adapter2.resolveProject(c2.req.param("id"));
4811
5090
  if (!project) return c2.json({ error: "not found" }, 404);
4812
5091
  const rendersDir = adapter2.rendersDir(project);
4813
5092
  if (!existsSync9(rendersDir)) return c2.json({ renders: [] });
4814
- const files = readdirSync4(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm")).map((f) => {
4815
- const fp = join8(rendersDir, f);
5093
+ const files = readdirSync5(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm")).map((f) => {
5094
+ const fp = join9(rendersDir, f);
4816
5095
  const stat = statSync2(fp);
4817
5096
  const rid = f.replace(/\.(mp4|webm)$/, "");
4818
- const metaPath = join8(rendersDir, `${rid}.meta.json`);
5097
+ const metaPath = join9(rendersDir, `${rid}.meta.json`);
4819
5098
  let status = "complete";
4820
5099
  let durationMs;
4821
5100
  if (existsSync9(metaPath)) {
4822
5101
  try {
4823
- const meta = JSON.parse(readFileSync8(metaPath, "utf-8"));
5102
+ const meta = JSON.parse(readFileSync9(metaPath, "utf-8"));
4824
5103
  if (meta.status === "failed") status = "failed";
4825
5104
  if (meta.durationMs) durationMs = meta.durationMs;
4826
5105
  } catch {
@@ -4841,7 +5120,7 @@ function registerRenderRoutes(api, adapter2) {
4841
5120
  id: file.id,
4842
5121
  status: file.status,
4843
5122
  progress: 100,
4844
- outputPath: join8(rendersDir, file.filename),
5123
+ outputPath: join9(rendersDir, file.filename),
4845
5124
  createdAt: file.createdAt
4846
5125
  });
4847
5126
  }
@@ -4856,8 +5135,8 @@ var init_render = __esm({
4856
5135
  });
4857
5136
 
4858
5137
  // ../core/src/studio-api/routes/thumbnail.ts
4859
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
4860
- import { join as join9 } from "path";
5138
+ import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
5139
+ import { join as join10 } from "path";
4861
5140
  function registerThumbnailRoutes(api, adapter2) {
4862
5141
  api.get("/projects/:id/thumbnail/*", async (c2) => {
4863
5142
  if (!adapter2.generateThumbnail) {
@@ -4876,9 +5155,9 @@ function registerThumbnailRoutes(api, adapter2) {
4876
5155
  let compW = vpWidth || 1920;
4877
5156
  let compH = vpHeight || 1080;
4878
5157
  if (!vpWidth) {
4879
- const htmlFile = join9(project.dir, compPath);
5158
+ const htmlFile = join10(project.dir, compPath);
4880
5159
  if (existsSync10(htmlFile)) {
4881
- const html = readFileSync9(htmlFile, "utf-8");
5160
+ const html = readFileSync10(htmlFile, "utf-8");
4882
5161
  const wMatch = html.match(/data-width=["'](\d+)["']/);
4883
5162
  const hMatch = html.match(/data-height=["'](\d+)["']/);
4884
5163
  if (wMatch?.[1]) compW = parseInt(wMatch[1]);
@@ -4886,11 +5165,11 @@ function registerThumbnailRoutes(api, adapter2) {
4886
5165
  }
4887
5166
  }
4888
5167
  const previewUrl = compPath === "index.html" ? `http://${c2.req.header("host")}/api/projects/${project.id}/preview` : `http://${c2.req.header("host")}/api/projects/${project.id}/preview/comp/${compPath}`;
4889
- const cacheDir = join9(project.dir, ".thumbnails");
5168
+ const cacheDir = join10(project.dir, ".thumbnails");
4890
5169
  const cacheKey = `${compPath.replace(/\//g, "_")}_${seekTime.toFixed(2)}.jpg`;
4891
- const cachePath = join9(cacheDir, cacheKey);
5170
+ const cachePath = join10(cacheDir, cacheKey);
4892
5171
  if (existsSync10(cachePath)) {
4893
- return new Response(new Uint8Array(readFileSync9(cachePath)), {
5172
+ return new Response(new Uint8Array(readFileSync10(cachePath)), {
4894
5173
  headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
4895
5174
  });
4896
5175
  }
@@ -4907,7 +5186,7 @@ function registerThumbnailRoutes(api, adapter2) {
4907
5186
  return c2.json({ error: "Thumbnail generation returned null" }, 500);
4908
5187
  }
4909
5188
  if (!existsSync10(cacheDir)) mkdirSync6(cacheDir, { recursive: true });
4910
- writeFileSync3(cachePath, buffer);
5189
+ writeFileSync4(cachePath, buffer);
4911
5190
  return new Response(new Uint8Array(buffer), {
4912
5191
  headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
4913
5192
  });
@@ -4971,7 +5250,7 @@ __export(manager_exports2, {
4971
5250
  import { execSync } from "child_process";
4972
5251
  import { existsSync as existsSync11, rmSync as rmSync3 } from "fs";
4973
5252
  import { homedir as homedir4 } from "os";
4974
- import { join as join10 } from "path";
5253
+ import { join as join11 } from "path";
4975
5254
  import { Browser, detectBrowserPlatform, getInstalledBrowsers, install } from "@puppeteer/browsers";
4976
5255
  function setBrowserPath(path) {
4977
5256
  _browserPathOverride = path;
@@ -5056,7 +5335,7 @@ var init_manager2 = __esm({
5056
5335
  "src/browser/manager.ts"() {
5057
5336
  "use strict";
5058
5337
  CHROME_VERSION = "131.0.6778.85";
5059
- CACHE_DIR = join10(homedir4(), ".cache", "hyperframes", "chrome");
5338
+ CACHE_DIR = join11(homedir4(), ".cache", "hyperframes", "chrome");
5060
5339
  SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
5061
5340
  "/usr/bin/google-chrome",
5062
5341
  "/usr/bin/google-chrome-stable",
@@ -5174,8 +5453,8 @@ var init_config2 = __esm({
5174
5453
  });
5175
5454
 
5176
5455
  // ../engine/src/services/browserManager.ts
5177
- import { existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
5178
- import { join as join11 } from "path";
5456
+ import { existsSync as existsSync12, readdirSync as readdirSync6 } from "fs";
5457
+ import { join as join12 } from "path";
5179
5458
  import { homedir as homedir5 } from "os";
5180
5459
  async function getPuppeteer() {
5181
5460
  if (_puppeteer) return _puppeteer;
@@ -5196,16 +5475,16 @@ function resolveHeadlessShellPath(config) {
5196
5475
  if (process.env.PRODUCER_HEADLESS_SHELL_PATH) {
5197
5476
  return process.env.PRODUCER_HEADLESS_SHELL_PATH;
5198
5477
  }
5199
- const baseDir = join11(homedir5(), ".cache", "puppeteer", "chrome-headless-shell");
5478
+ const baseDir = join12(homedir5(), ".cache", "puppeteer", "chrome-headless-shell");
5200
5479
  if (!existsSync12(baseDir)) return void 0;
5201
5480
  try {
5202
- const versions = readdirSync5(baseDir).sort().reverse();
5481
+ const versions = readdirSync6(baseDir).sort().reverse();
5203
5482
  for (const version of versions) {
5204
5483
  const candidates = [
5205
- join11(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
5206
- join11(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
5207
- join11(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
5208
- join11(baseDir, version, "chrome-headless-shell-win64", "chrome-headless-shell.exe")
5484
+ join12(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
5485
+ join12(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
5486
+ join12(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
5487
+ join12(baseDir, version, "chrome-headless-shell-win64", "chrome-headless-shell.exe")
5209
5488
  ];
5210
5489
  for (const binary of candidates) {
5211
5490
  if (existsSync12(binary)) return binary;
@@ -5990,6 +6269,69 @@ var init_gsap = __esm({
5990
6269
  }
5991
6270
  });
5992
6271
 
6272
+ // ../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/bidi.js
6273
+ var init_bidi = __esm({
6274
+ "../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/bidi.js"() {
6275
+ "use strict";
6276
+ }
6277
+ });
6278
+
6279
+ // ../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/analysis.js
6280
+ var arabicScriptRe, combiningMarkRe, decimalDigitRe;
6281
+ var init_analysis = __esm({
6282
+ "../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/analysis.js"() {
6283
+ "use strict";
6284
+ arabicScriptRe = new RegExp("\\p{Script=Arabic}", "u");
6285
+ combiningMarkRe = new RegExp("\\p{M}", "u");
6286
+ decimalDigitRe = new RegExp("\\p{Nd}", "u");
6287
+ }
6288
+ });
6289
+
6290
+ // ../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/measurement.js
6291
+ var emojiPresentationRe;
6292
+ var init_measurement = __esm({
6293
+ "../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/measurement.js"() {
6294
+ "use strict";
6295
+ init_analysis();
6296
+ emojiPresentationRe = new RegExp("\\p{Emoji_Presentation}", "u");
6297
+ }
6298
+ });
6299
+
6300
+ // ../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/line-break.js
6301
+ var init_line_break = __esm({
6302
+ "../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/line-break.js"() {
6303
+ "use strict";
6304
+ init_measurement();
6305
+ }
6306
+ });
6307
+
6308
+ // ../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/layout.js
6309
+ var init_layout = __esm({
6310
+ "../../node_modules/.bun/@chenglou+pretext@0.0.3/node_modules/@chenglou/pretext/dist/layout.js"() {
6311
+ "use strict";
6312
+ init_bidi();
6313
+ init_analysis();
6314
+ init_measurement();
6315
+ init_line_break();
6316
+ }
6317
+ });
6318
+
6319
+ // ../core/src/text/fitTextFontSize.ts
6320
+ var init_fitTextFontSize = __esm({
6321
+ "../core/src/text/fitTextFontSize.ts"() {
6322
+ "use strict";
6323
+ init_layout();
6324
+ }
6325
+ });
6326
+
6327
+ // ../core/src/text/index.ts
6328
+ var init_text = __esm({
6329
+ "../core/src/text/index.ts"() {
6330
+ "use strict";
6331
+ init_fitTextFontSize();
6332
+ }
6333
+ });
6334
+
5993
6335
  // ../core/src/index.ts
5994
6336
  var init_src = __esm({
5995
6337
  "../core/src/index.ts"() {
@@ -6007,6 +6349,7 @@ var init_src = __esm({
6007
6349
  init_hyperframesRuntime_engine();
6008
6350
  init_parityContract();
6009
6351
  init_gsap();
6352
+ init_text();
6010
6353
  }
6011
6354
  });
6012
6355
 
@@ -6174,8 +6517,8 @@ var init_screenshotService = __esm({
6174
6517
  });
6175
6518
 
6176
6519
  // ../engine/src/services/frameCapture.ts
6177
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
6178
- import { join as join12 } from "path";
6520
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
6521
+ import { join as join13 } from "path";
6179
6522
  async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
6180
6523
  if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
6181
6524
  const headlessShell = resolveHeadlessShellPath(config);
@@ -6336,13 +6679,13 @@ async function initializeSession(session) {
6336
6679
  }
6337
6680
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
6338
6681
  try {
6339
- const diagnosticsDir = join12(session.outputDir, "diagnostics");
6682
+ const diagnosticsDir = join13(session.outputDir, "diagnostics");
6340
6683
  if (!existsSync13(diagnosticsDir)) mkdirSync7(diagnosticsDir, { recursive: true });
6341
- const base = join12(diagnosticsDir, `frame-error-${frameIndex}`);
6684
+ const base = join13(diagnosticsDir, `frame-error-${frameIndex}`);
6342
6685
  await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
6343
6686
  const html = await session.page.content();
6344
- writeFileSync4(`${base}.html`, html, "utf-8");
6345
- writeFileSync4(
6687
+ writeFileSync5(`${base}.html`, html, "utf-8");
6688
+ writeFileSync5(
6346
6689
  `${base}.json`,
6347
6690
  JSON.stringify(
6348
6691
  {
@@ -6436,8 +6779,8 @@ async function captureFrame(session, frameIndex, time) {
6436
6779
  );
6437
6780
  const ext = options.format === "png" ? "png" : "jpg";
6438
6781
  const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
6439
- const framePath = join12(outputDir, frameName);
6440
- writeFileSync4(framePath, buffer);
6782
+ const framePath = join13(outputDir, frameName);
6783
+ writeFileSync5(framePath, buffer);
6441
6784
  return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
6442
6785
  }
6443
6786
  async function captureFrameToBuffer(session, frameIndex, time) {
@@ -6497,7 +6840,7 @@ var init_frameCapture = __esm({
6497
6840
  // ../engine/src/utils/gpuEncoder.ts
6498
6841
  import { spawn } from "child_process";
6499
6842
  async function detectGpuEncoder() {
6500
- return new Promise((resolve21) => {
6843
+ return new Promise((resolve22) => {
6501
6844
  const ffmpeg = spawn("ffmpeg", ["-encoders"], {
6502
6845
  stdio: ["pipe", "pipe", "pipe"]
6503
6846
  });
@@ -6506,13 +6849,13 @@ async function detectGpuEncoder() {
6506
6849
  stdout2 += data.toString();
6507
6850
  });
6508
6851
  ffmpeg.on("close", () => {
6509
- if (stdout2.includes("h264_nvenc")) resolve21("nvenc");
6510
- else if (stdout2.includes("h264_videotoolbox")) resolve21("videotoolbox");
6511
- else if (stdout2.includes("h264_vaapi")) resolve21("vaapi");
6512
- else if (stdout2.includes("h264_qsv")) resolve21("qsv");
6513
- else resolve21(null);
6852
+ if (stdout2.includes("h264_nvenc")) resolve22("nvenc");
6853
+ else if (stdout2.includes("h264_videotoolbox")) resolve22("videotoolbox");
6854
+ else if (stdout2.includes("h264_vaapi")) resolve22("vaapi");
6855
+ else if (stdout2.includes("h264_qsv")) resolve22("qsv");
6856
+ else resolve22(null);
6514
6857
  });
6515
- ffmpeg.on("error", () => resolve21(null));
6858
+ ffmpeg.on("error", () => resolve22(null));
6516
6859
  });
6517
6860
  }
6518
6861
  async function getCachedGpuEncoder() {
@@ -6551,7 +6894,7 @@ async function runFfmpeg(args, opts) {
6551
6894
  const signal = opts?.signal;
6552
6895
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
6553
6896
  const onStderr = opts?.onStderr;
6554
- return new Promise((resolve21) => {
6897
+ return new Promise((resolve22) => {
6555
6898
  const ffmpeg = spawn2("ffmpeg", args);
6556
6899
  let stderr = "";
6557
6900
  const onAbort = () => {
@@ -6577,7 +6920,7 @@ async function runFfmpeg(args, opts) {
6577
6920
  ffmpeg.on("close", (code) => {
6578
6921
  clearTimeout(timer);
6579
6922
  if (signal) signal.removeEventListener("abort", onAbort);
6580
- resolve21({
6923
+ resolve22({
6581
6924
  success: !signal?.aborted && code === 0,
6582
6925
  exitCode: code,
6583
6926
  stderr,
@@ -6587,7 +6930,7 @@ async function runFfmpeg(args, opts) {
6587
6930
  ffmpeg.on("error", (err) => {
6588
6931
  clearTimeout(timer);
6589
6932
  if (signal) signal.removeEventListener("abort", onAbort);
6590
- resolve21({
6933
+ resolve22({
6591
6934
  success: false,
6592
6935
  exitCode: null,
6593
6936
  stderr: err.message,
@@ -6606,8 +6949,8 @@ var init_runFfmpeg = __esm({
6606
6949
 
6607
6950
  // ../engine/src/services/chunkEncoder.ts
6608
6951
  import { spawn as spawn3 } from "child_process";
6609
- import { copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync6, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
6610
- import { join as join13, dirname as dirname4 } from "path";
6952
+ import { copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync7, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
6953
+ import { join as join14, dirname as dirname4 } from "path";
6611
6954
  function getEncoderPreset(quality, format = "mp4") {
6612
6955
  const base = ENCODER_PRESETS[quality];
6613
6956
  if (format === "webm") {
@@ -6690,7 +7033,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6690
7033
  const startTime = Date.now();
6691
7034
  const outputDir = dirname4(outputPath);
6692
7035
  if (!existsSync14(outputDir)) mkdirSync8(outputDir, { recursive: true });
6693
- const files = readdirSync6(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
7036
+ const files = readdirSync7(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
6694
7037
  const frameCount = files.length;
6695
7038
  if (frameCount === 0) {
6696
7039
  return {
@@ -6706,10 +7049,10 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6706
7049
  if (options.useGpu) {
6707
7050
  gpuEncoder = await getCachedGpuEncoder();
6708
7051
  }
6709
- const inputPath = join13(framesDir, framePattern);
7052
+ const inputPath = join14(framesDir, framePattern);
6710
7053
  const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
6711
7054
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
6712
- return new Promise((resolve21) => {
7055
+ return new Promise((resolve22) => {
6713
7056
  const ffmpeg = spawn3("ffmpeg", args);
6714
7057
  let stderr = "";
6715
7058
  const onAbort = () => {
@@ -6734,7 +7077,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6734
7077
  if (signal) signal.removeEventListener("abort", onAbort);
6735
7078
  const durationMs = Date.now() - startTime;
6736
7079
  if (signal?.aborted) {
6737
- resolve21({
7080
+ resolve22({
6738
7081
  success: false,
6739
7082
  outputPath,
6740
7083
  durationMs,
@@ -6745,7 +7088,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6745
7088
  return;
6746
7089
  }
6747
7090
  if (code !== 0) {
6748
- resolve21({
7091
+ resolve22({
6749
7092
  success: false,
6750
7093
  outputPath,
6751
7094
  durationMs,
@@ -6756,12 +7099,12 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6756
7099
  return;
6757
7100
  }
6758
7101
  const fileSize = existsSync14(outputPath) ? statSync3(outputPath).size : 0;
6759
- resolve21({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
7102
+ resolve22({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
6760
7103
  });
6761
7104
  ffmpeg.on("error", (err) => {
6762
7105
  clearTimeout(timer);
6763
7106
  if (signal) signal.removeEventListener("abort", onAbort);
6764
- resolve21({
7107
+ resolve22({
6765
7108
  success: false,
6766
7109
  outputPath,
6767
7110
  durationMs: Date.now() - startTime,
@@ -6774,7 +7117,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
6774
7117
  }
6775
7118
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal) {
6776
7119
  const start = Date.now();
6777
- const files = readdirSync6(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
7120
+ const files = readdirSync7(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
6778
7121
  if (files.length === 0) {
6779
7122
  return {
6780
7123
  success: false,
@@ -6787,7 +7130,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
6787
7130
  }
6788
7131
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
6789
7132
  const chunkCount = Math.ceil(files.length / chunkSize);
6790
- const chunkDir = join13(dirname4(outputPath), "chunk-encode");
7133
+ const chunkDir = join14(dirname4(outputPath), "chunk-encode");
6791
7134
  if (!existsSync14(chunkDir)) mkdirSync8(chunkDir, { recursive: true });
6792
7135
  const chunkPaths = [];
6793
7136
  for (let i = 0; i < chunkCount; i++) {
@@ -6804,8 +7147,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
6804
7147
  const startNumber = i * chunkSize;
6805
7148
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
6806
7149
  const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
6807
- const chunkPath = join13(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
6808
- const inputPath = join13(framesDir, framePattern);
7150
+ const chunkPath = join14(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
7151
+ const inputPath = join14(framesDir, framePattern);
6809
7152
  const inputArgs = [
6810
7153
  "-framerate",
6811
7154
  String(options.fps),
@@ -6819,18 +7162,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
6819
7162
  let gpuEncoder = null;
6820
7163
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
6821
7164
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
6822
- const chunkResult = await new Promise((resolve21) => {
7165
+ const chunkResult = await new Promise((resolve22) => {
6823
7166
  const ffmpeg = spawn3("ffmpeg", args);
6824
7167
  let stderr = "";
6825
7168
  ffmpeg.stderr.on("data", (d) => {
6826
7169
  stderr += d.toString();
6827
7170
  });
6828
7171
  ffmpeg.on("close", (code) => {
6829
- if (code === 0) resolve21({ success: true });
6830
- else resolve21({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
7172
+ if (code === 0) resolve22({ success: true });
7173
+ else resolve22({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
6831
7174
  });
6832
7175
  ffmpeg.on("error", (err) => {
6833
- resolve21({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
7176
+ resolve22({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
6834
7177
  });
6835
7178
  });
6836
7179
  if (!chunkResult.success) {
@@ -6845,9 +7188,9 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
6845
7188
  }
6846
7189
  chunkPaths.push(chunkPath);
6847
7190
  }
6848
- const concatListPath = join13(chunkDir, "concat-list.txt");
7191
+ const concatListPath = join14(chunkDir, "concat-list.txt");
6849
7192
  const concatInput = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
6850
- writeFileSync5(concatListPath, concatInput, "utf-8");
7193
+ writeFileSync6(concatListPath, concatInput, "utf-8");
6851
7194
  const concatArgs = [
6852
7195
  "-f",
6853
7196
  "concat",
@@ -6860,18 +7203,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
6860
7203
  "-y",
6861
7204
  outputPath
6862
7205
  ];
6863
- const concatResult = await new Promise((resolve21) => {
7206
+ const concatResult = await new Promise((resolve22) => {
6864
7207
  const ffmpeg = spawn3("ffmpeg", concatArgs);
6865
7208
  let stderr = "";
6866
7209
  ffmpeg.stderr.on("data", (d) => {
6867
7210
  stderr += d.toString();
6868
7211
  });
6869
7212
  ffmpeg.on("close", (code) => {
6870
- if (code === 0) resolve21({ success: true });
6871
- else resolve21({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
7213
+ if (code === 0) resolve22({ success: true });
7214
+ else resolve22({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
6872
7215
  });
6873
7216
  ffmpeg.on("error", (err) => {
6874
- resolve21({ success: false, error: `Chunk concat error: ${err.message}` });
7217
+ resolve22({ success: false, error: `Chunk concat error: ${err.message}` });
6875
7218
  });
6876
7219
  });
6877
7220
  if (!concatResult.success) {
@@ -6976,16 +7319,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
6976
7319
  }
6977
7320
  };
6978
7321
  return {
6979
- waitForFrame: (frame) => new Promise((resolve21) => {
6980
- waiters.push({ frame, resolve: resolve21 });
7322
+ waitForFrame: (frame) => new Promise((resolve22) => {
7323
+ waiters.push({ frame, resolve: resolve22 });
6981
7324
  resolveWaiters();
6982
7325
  }),
6983
7326
  advanceTo: (frame) => {
6984
7327
  nextFrame = frame;
6985
7328
  resolveWaiters();
6986
7329
  },
6987
- waitForAllDone: () => new Promise((resolve21) => {
6988
- waiters.push({ frame: endFrame, resolve: resolve21 });
7330
+ waitForAllDone: () => new Promise((resolve22) => {
7331
+ waiters.push({ frame: endFrame, resolve: resolve22 });
6989
7332
  resolveWaiters();
6990
7333
  })
6991
7334
  };
@@ -7085,7 +7428,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
7085
7428
  let stderr = "";
7086
7429
  let exitCode = null;
7087
7430
  let exitPromiseResolve = null;
7088
- const exitPromise = new Promise((resolve21) => exitPromiseResolve = resolve21);
7431
+ const exitPromise = new Promise((resolve22) => exitPromiseResolve = resolve22);
7089
7432
  ffmpeg.stderr?.on("data", (data) => {
7090
7433
  stderr += data.toString();
7091
7434
  });
@@ -7129,8 +7472,8 @@ Process error: ${err.message}`;
7129
7472
  clearTimeout(timer);
7130
7473
  if (signal) signal.removeEventListener("abort", onAbort);
7131
7474
  if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
7132
- await new Promise((resolve21) => {
7133
- ffmpeg.stdin.end(() => resolve21());
7475
+ await new Promise((resolve22) => {
7476
+ ffmpeg.stdin.end(() => resolve22());
7134
7477
  });
7135
7478
  }
7136
7479
  await exitPromise;
@@ -11020,8 +11363,8 @@ var init_custom_element_registry = __esm({
11020
11363
  } : (element) => element.localName === localName;
11021
11364
  registry.set(localName, { Class, check });
11022
11365
  if (waiting.has(localName)) {
11023
- for (const resolve21 of waiting.get(localName))
11024
- resolve21(Class);
11366
+ for (const resolve22 of waiting.get(localName))
11367
+ resolve22(Class);
11025
11368
  waiting.delete(localName);
11026
11369
  }
11027
11370
  ownerDocument.querySelectorAll(
@@ -11061,13 +11404,13 @@ var init_custom_element_registry = __esm({
11061
11404
  */
11062
11405
  whenDefined(localName) {
11063
11406
  const { registry, waiting } = this;
11064
- return new Promise((resolve21) => {
11407
+ return new Promise((resolve22) => {
11065
11408
  if (registry.has(localName))
11066
- resolve21(registry.get(localName).Class);
11409
+ resolve22(registry.get(localName).Class);
11067
11410
  else {
11068
11411
  if (!waiting.has(localName))
11069
11412
  waiting.set(localName, []);
11070
- waiting.get(localName).push(resolve21);
11413
+ waiting.get(localName).push(resolve22);
11071
11414
  }
11072
11415
  });
11073
11416
  }
@@ -13754,7 +14097,7 @@ var init_matches = __esm({
13754
14097
 
13755
14098
  // ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/interface/text.js
13756
14099
  var Text3;
13757
- var init_text = __esm({
14100
+ var init_text2 = __esm({
13758
14101
  "../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/interface/text.js"() {
13759
14102
  "use strict";
13760
14103
  init_constants2();
@@ -13807,7 +14150,7 @@ var init_parent_node = __esm({
13807
14150
  init_node3();
13808
14151
  init_utils();
13809
14152
  init_node2();
13810
- init_text();
14153
+ init_text2();
13811
14154
  init_node_list();
13812
14155
  init_mutation_observer();
13813
14156
  init_custom_element_registry();
@@ -14593,7 +14936,7 @@ var init_element = __esm({
14593
14936
  init_shadow_root();
14594
14937
  init_node_list();
14595
14938
  init_attr();
14596
- init_text();
14939
+ init_text2();
14597
14940
  init_text_escaper();
14598
14941
  attributesHandler = {
14599
14942
  get(target, key2) {
@@ -15157,7 +15500,7 @@ var init_facades = __esm({
15157
15500
  init_element();
15158
15501
  init_node2();
15159
15502
  init_shadow_root();
15160
- init_text();
15503
+ init_text2();
15161
15504
  init_element2();
15162
15505
  init_object();
15163
15506
  illegalConstructor = () => {
@@ -19290,7 +19633,7 @@ var init_document = __esm({
19290
19633
  init_named_node_map();
19291
19634
  init_node_list();
19292
19635
  init_range();
19293
- init_text();
19636
+ init_text2();
19294
19637
  init_tree_walker();
19295
19638
  query = (method, ownerDocument, selectors) => {
19296
19639
  let { [NEXT]: next, [END]: end } = ownerDocument;
@@ -19721,7 +20064,7 @@ var init_parse_json = __esm({
19721
20064
  init_cdata_section();
19722
20065
  init_comment();
19723
20066
  init_document_type();
19724
- init_text();
20067
+ init_text2();
19725
20068
  init_document2();
19726
20069
  init_element3();
19727
20070
  init_element2();
@@ -19770,7 +20113,7 @@ var init_esm10 = __esm({
19770
20113
  // ../engine/src/utils/ffprobe.ts
19771
20114
  import { spawn as spawn5 } from "child_process";
19772
20115
  function runFfprobe(args) {
19773
- return new Promise((resolve21, reject) => {
20116
+ return new Promise((resolve22, reject) => {
19774
20117
  const proc = spawn5("ffprobe", args);
19775
20118
  let stdout2 = "";
19776
20119
  let stderr = "";
@@ -19784,7 +20127,7 @@ function runFfprobe(args) {
19784
20127
  if (code !== 0) {
19785
20128
  reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
19786
20129
  } else {
19787
- resolve21(stdout2);
20130
+ resolve22(stdout2);
19788
20131
  }
19789
20132
  });
19790
20133
  proc.on("error", (err) => {
@@ -19949,13 +20292,13 @@ var init_ffprobe = __esm({
19949
20292
  // ../engine/src/utils/urlDownloader.ts
19950
20293
  import { createWriteStream as createWriteStream2, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
19951
20294
  import { createHash } from "crypto";
19952
- import { join as join14, extname } from "path";
20295
+ import { join as join15, extname as extname2 } from "path";
19953
20296
  import { Readable } from "stream";
19954
20297
  import { finished } from "stream/promises";
19955
20298
  function getFilenameFromUrl(url) {
19956
20299
  const hash = createHash("md5").update(url).digest("hex").slice(0, 12);
19957
20300
  const urlObj = new URL(url);
19958
- const ext = extname(urlObj.pathname) || ".mp4";
20301
+ const ext = extname2(urlObj.pathname) || ".mp4";
19959
20302
  return `download_${hash}${ext}`;
19960
20303
  }
19961
20304
  async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
@@ -19971,7 +20314,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
19971
20314
  mkdirSync10(destDir, { recursive: true });
19972
20315
  }
19973
20316
  const filename = getFilenameFromUrl(url);
19974
- const localPath = join14(destDir, filename);
20317
+ const localPath = join15(destDir, filename);
19975
20318
  if (existsSync16(localPath)) {
19976
20319
  downloadPathCache.set(url, localPath);
19977
20320
  return localPath;
@@ -20020,8 +20363,8 @@ var init_urlDownloader = __esm({
20020
20363
 
20021
20364
  // ../engine/src/services/videoFrameExtractor.ts
20022
20365
  import { spawn as spawn6 } from "child_process";
20023
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, readdirSync as readdirSync7, rmSync as rmSync4 } from "fs";
20024
- import { join as join15 } from "path";
20366
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, readdirSync as readdirSync8, rmSync as rmSync4 } from "fs";
20367
+ import { join as join16 } from "path";
20025
20368
  function parseVideoElements(html) {
20026
20369
  const videos = [];
20027
20370
  const { document: document2 } = parseHTML(html);
@@ -20066,11 +20409,11 @@ function parseVideoElements(html) {
20066
20409
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
20067
20410
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
20068
20411
  const { fps, outputDir, quality = 95, format = "jpg" } = options;
20069
- const videoOutputDir = join15(outputDir, videoId);
20412
+ const videoOutputDir = join16(outputDir, videoId);
20070
20413
  if (!existsSync17(videoOutputDir)) mkdirSync11(videoOutputDir, { recursive: true });
20071
20414
  const metadata = await extractVideoMetadata(videoPath);
20072
20415
  const framePattern = `frame_%05d.${format}`;
20073
- const outputPattern = join15(videoOutputDir, framePattern);
20416
+ const outputPattern = join16(videoOutputDir, framePattern);
20074
20417
  const args = [
20075
20418
  "-ss",
20076
20419
  String(startTime),
@@ -20085,7 +20428,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
20085
20428
  ];
20086
20429
  if (format === "png") args.push("-compression_level", "6");
20087
20430
  args.push("-y", outputPattern);
20088
- return new Promise((resolve21, reject) => {
20431
+ return new Promise((resolve22, reject) => {
20089
20432
  const ffmpeg = spawn6("ffmpeg", args);
20090
20433
  let stderr = "";
20091
20434
  const onAbort = () => {
@@ -20116,11 +20459,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
20116
20459
  return;
20117
20460
  }
20118
20461
  const framePaths = /* @__PURE__ */ new Map();
20119
- const files = readdirSync7(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
20462
+ const files = readdirSync8(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
20120
20463
  files.forEach((file, index) => {
20121
- framePaths.set(index, join15(videoOutputDir, file));
20464
+ framePaths.set(index, join16(videoOutputDir, file));
20122
20465
  });
20123
- resolve21({
20466
+ resolve22({
20124
20467
  videoId,
20125
20468
  srcPath: videoPath,
20126
20469
  outputDir: videoOutputDir,
@@ -20155,10 +20498,10 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
20155
20498
  try {
20156
20499
  let videoPath = video.src;
20157
20500
  if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
20158
- videoPath = join15(baseDir, videoPath);
20501
+ videoPath = join16(baseDir, videoPath);
20159
20502
  }
20160
20503
  if (isHttpUrl(videoPath)) {
20161
- const downloadDir = join15(options.outputDir, "_downloads");
20504
+ const downloadDir = join16(options.outputDir, "_downloads");
20162
20505
  mkdirSync11(downloadDir, { recursive: true });
20163
20506
  videoPath = await downloadToTemp(videoPath, downloadDir);
20164
20507
  }
@@ -20419,7 +20762,7 @@ var init_videoFrameInjector = __esm({
20419
20762
 
20420
20763
  // ../engine/src/services/audioMixer.ts
20421
20764
  import { existsSync as existsSync18, mkdirSync as mkdirSync12, rmSync as rmSync5 } from "fs";
20422
- import { join as join16, dirname as dirname6 } from "path";
20765
+ import { join as join17, dirname as dirname6 } from "path";
20423
20766
  function parseAudioElements(html) {
20424
20767
  const elements = [];
20425
20768
  const { document: document2 } = parseHTML(html);
@@ -20647,7 +20990,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20647
20990
  try {
20648
20991
  let srcPath = element.src;
20649
20992
  if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
20650
- srcPath = join16(baseDir, srcPath);
20993
+ srcPath = join17(baseDir, srcPath);
20651
20994
  }
20652
20995
  if (isHttpUrl(srcPath)) {
20653
20996
  try {
@@ -20670,7 +21013,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20670
21013
  }
20671
21014
  let audioSrcPath = srcPath;
20672
21015
  if (element.type === "video") {
20673
- const extractedPath = join16(workDir, `${element.id}-extracted.wav`);
21016
+ const extractedPath = join17(workDir, `${element.id}-extracted.wav`);
20674
21017
  const extractResult = await extractAudioFromVideo(
20675
21018
  srcPath,
20676
21019
  extractedPath,
@@ -20687,7 +21030,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20687
21030
  }
20688
21031
  audioSrcPath = extractedPath;
20689
21032
  } else {
20690
- const trimmedPath = join16(workDir, `${element.id}-trimmed.wav`);
21033
+ const trimmedPath = join17(workDir, `${element.id}-trimmed.wav`);
20691
21034
  const prepResult = await prepareAudioTrack(
20692
21035
  srcPath,
20693
21036
  trimmedPath,
@@ -20740,9 +21083,9 @@ var init_audioMixer = __esm({
20740
21083
 
20741
21084
  // ../engine/src/services/parallelCoordinator.ts
20742
21085
  import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
20743
- import { existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync8 } from "fs";
21086
+ import { existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync9 } from "fs";
20744
21087
  import { copyFile, rename } from "fs/promises";
20745
- import { join as join17 } from "path";
21088
+ import { join as join18 } from "path";
20746
21089
  function calculateOptimalWorkers(totalFrames, requested, config) {
20747
21090
  const effectiveMaxWorkers = (() => {
20748
21091
  const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
@@ -20785,7 +21128,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
20785
21128
  workerId: i,
20786
21129
  startFrame,
20787
21130
  endFrame,
20788
- outputDir: join17(workDir, `worker-${i}`)
21131
+ outputDir: join18(workDir, `worker-${i}`)
20789
21132
  });
20790
21133
  }
20791
21134
  return tasks;
@@ -20890,10 +21233,10 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
20890
21233
  if (!existsSync19(task.outputDir)) {
20891
21234
  continue;
20892
21235
  }
20893
- const files = readdirSync8(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
21236
+ const files = readdirSync9(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
20894
21237
  const copyTasks = files.map(async (file) => {
20895
- const sourcePath = join17(task.outputDir, file);
20896
- const targetPath = join17(outputDir, file);
21238
+ const sourcePath = join18(task.outputDir, file);
21239
+ const targetPath = join18(outputDir, file);
20897
21240
  try {
20898
21241
  await rename(sourcePath, targetPath);
20899
21242
  } catch {
@@ -20930,8 +21273,8 @@ var init_parallelCoordinator = __esm({
20930
21273
  // ../engine/src/services/fileServer.ts
20931
21274
  import { Hono as Hono2 } from "hono";
20932
21275
  import { serve } from "@hono/node-server";
20933
- import { readFileSync as readFileSync10, existsSync as existsSync20, statSync as statSync5 } from "fs";
20934
- import { join as join18, extname as extname2 } from "path";
21276
+ import { readFileSync as readFileSync11, existsSync as existsSync20, statSync as statSync5 } from "fs";
21277
+ import { join as join19, extname as extname3 } from "path";
20935
21278
  function stripEmbeddedRuntimeScripts(html) {
20936
21279
  if (!html) return html;
20937
21280
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -21000,32 +21343,32 @@ function createFileServer(options) {
21000
21343
  let requestPath = c2.req.path;
21001
21344
  if (requestPath === "/") requestPath = "/index.html";
21002
21345
  const relativePath = requestPath.replace(/^\//, "");
21003
- const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
21346
+ const compiledPath = compiledDir ? join19(compiledDir, relativePath) : null;
21004
21347
  const hasCompiledFile = Boolean(
21005
21348
  compiledPath && existsSync20(compiledPath) && statSync5(compiledPath).isFile()
21006
21349
  );
21007
- const filePath = hasCompiledFile ? compiledPath : join18(projectDir, relativePath);
21350
+ const filePath = hasCompiledFile ? compiledPath : join19(projectDir, relativePath);
21008
21351
  if (!existsSync20(filePath) || !statSync5(filePath).isFile()) {
21009
21352
  return c2.text("Not found", 404);
21010
21353
  }
21011
- const ext = extname2(filePath).toLowerCase();
21354
+ const ext = extname3(filePath).toLowerCase();
21012
21355
  const contentType = MIME_TYPES2[ext] || "application/octet-stream";
21013
21356
  if (ext === ".html") {
21014
- const rawHtml = readFileSync10(filePath, "utf-8");
21357
+ const rawHtml = readFileSync11(filePath, "utf-8");
21015
21358
  const html = relativePath === "index.html" ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
21016
21359
  return c2.text(html, 200, { "Content-Type": contentType });
21017
21360
  }
21018
- const content = readFileSync10(filePath);
21361
+ const content = readFileSync11(filePath);
21019
21362
  return new Response(content, {
21020
21363
  status: 200,
21021
21364
  headers: { "Content-Type": contentType }
21022
21365
  });
21023
21366
  });
21024
- return new Promise((resolve21) => {
21367
+ return new Promise((resolve22) => {
21025
21368
  const server = serve({ fetch: app.fetch, port }, (info) => {
21026
21369
  const actualPort = info.port;
21027
21370
  const url = `http://localhost:${actualPort}`;
21028
- resolve21({
21371
+ resolve22({
21029
21372
  url,
21030
21373
  port: actualPort,
21031
21374
  close: () => server.close()
@@ -21219,8 +21562,8 @@ var init_staticGuard = __esm({
21219
21562
  });
21220
21563
 
21221
21564
  // ../core/src/compiler/htmlBundler.ts
21222
- import { readFileSync as readFileSync11, existsSync as existsSync21 } from "fs";
21223
- import { join as join19, resolve as resolve7, isAbsolute, sep as sep2 } from "path";
21565
+ import { readFileSync as readFileSync12, existsSync as existsSync21 } from "fs";
21566
+ import { join as join20, resolve as resolve7, isAbsolute, sep as sep2 } from "path";
21224
21567
  import * as cheerio from "cheerio";
21225
21568
  import { transformSync } from "esbuild";
21226
21569
  function safePath(projectDir, relativePath) {
@@ -21284,7 +21627,7 @@ function isRelativeUrl(url) {
21284
21627
  function safeReadFile(filePath) {
21285
21628
  if (!existsSync21(filePath)) return null;
21286
21629
  try {
21287
- return readFileSync11(filePath, "utf-8");
21630
+ return readFileSync12(filePath, "utf-8");
21288
21631
  } catch {
21289
21632
  return null;
21290
21633
  }
@@ -21292,7 +21635,7 @@ function safeReadFile(filePath) {
21292
21635
  function safeReadFileBuffer(filePath) {
21293
21636
  if (!existsSync21(filePath)) return null;
21294
21637
  try {
21295
- return readFileSync11(filePath);
21638
+ return readFileSync12(filePath);
21296
21639
  } catch {
21297
21640
  return null;
21298
21641
  }
@@ -21479,9 +21822,9 @@ function stripJsCommentsParserSafe(source) {
21479
21822
  }
21480
21823
  }
21481
21824
  async function bundleToSingleHtml(projectDir, options) {
21482
- const indexPath = join19(projectDir, "index.html");
21825
+ const indexPath = join20(projectDir, "index.html");
21483
21826
  if (!existsSync21(indexPath)) throw new Error("index.html not found in project directory");
21484
- const rawHtml = readFileSync11(indexPath, "utf-8");
21827
+ const rawHtml = readFileSync12(indexPath, "utf-8");
21485
21828
  const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
21486
21829
  const staticGuard = validateHyperframeHtmlContract(compiled);
21487
21830
  if (!staticGuard.isValid) {
@@ -21586,6 +21929,64 @@ async function bundleToSingleHtml(projectDir, options) {
21586
21929
  }
21587
21930
  $(hostEl).removeAttr("data-composition-src");
21588
21931
  });
21932
+ $("template[id]").each((_2, templateEl) => {
21933
+ const templateId = $(templateEl).attr("id") || "";
21934
+ const match = templateId.match(/^(.+)-template$/);
21935
+ if (!match) return;
21936
+ const compId = match[1];
21937
+ const hostSelector = `[data-composition-id="${compId}"]:not([data-composition-src])`;
21938
+ const $candidates = $(hostSelector).filter((__, el) => $(el).parents().length > 0);
21939
+ const $host = $candidates.first();
21940
+ if ($host.length === 0) return;
21941
+ if ($host.children().length > 0) return;
21942
+ const templateHtml = $(templateEl).html() || "";
21943
+ const $inner = cheerio.load(templateHtml, { xml: false });
21944
+ const $innerRoot = $inner(`[data-composition-id="${compId}"]`).first();
21945
+ if ($innerRoot.length > 0) {
21946
+ $innerRoot.find("style").each((__, styleEl) => {
21947
+ compStyleChunks.push($inner(styleEl).html() || "");
21948
+ $inner(styleEl).remove();
21949
+ });
21950
+ $innerRoot.find("script").each((__, scriptEl) => {
21951
+ const externalSrc = ($inner(scriptEl).attr("src") || "").trim();
21952
+ if (externalSrc) {
21953
+ if (!compExternalScriptSrcs.includes(externalSrc)) {
21954
+ compExternalScriptSrcs.push(externalSrc);
21955
+ }
21956
+ } else {
21957
+ compScriptChunks.push(
21958
+ `(function(){ try { ${$inner(scriptEl).html() || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`
21959
+ );
21960
+ }
21961
+ $inner(scriptEl).remove();
21962
+ });
21963
+ const innerW = $innerRoot.attr("data-width");
21964
+ const innerH = $innerRoot.attr("data-height");
21965
+ if (innerW && !$host.attr("data-width")) $host.attr("data-width", innerW);
21966
+ if (innerH && !$host.attr("data-height")) $host.attr("data-height", innerH);
21967
+ $host.html($innerRoot.html() || "");
21968
+ } else {
21969
+ $inner("style").each((__, styleEl) => {
21970
+ compStyleChunks.push($inner(styleEl).html() || "");
21971
+ $inner(styleEl).remove();
21972
+ });
21973
+ $inner("script").each((__, scriptEl) => {
21974
+ const externalSrc = ($inner(scriptEl).attr("src") || "").trim();
21975
+ if (externalSrc) {
21976
+ if (!compExternalScriptSrcs.includes(externalSrc)) {
21977
+ compExternalScriptSrcs.push(externalSrc);
21978
+ }
21979
+ } else {
21980
+ compScriptChunks.push(
21981
+ `(function(){ try { ${$inner(scriptEl).html() || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`
21982
+ );
21983
+ }
21984
+ $inner(scriptEl).remove();
21985
+ });
21986
+ $host.html($inner.html() || "");
21987
+ }
21988
+ $(templateEl).remove();
21989
+ });
21589
21990
  for (const extSrc of compExternalScriptSrcs) {
21590
21991
  if (!$(`script[src="${extSrc}"]`).length) {
21591
21992
  $("body").append(`<script src="${extSrc}"></script>`);
@@ -21651,7 +22052,7 @@ var init_compiler = __esm({
21651
22052
 
21652
22053
  // ../producer/src/services/hyperframeRuntimeLoader.ts
21653
22054
  import { createHash as createHash2 } from "crypto";
21654
- import { existsSync as existsSync22, readFileSync as readFileSync12 } from "fs";
22055
+ import { existsSync as existsSync22, readFileSync as readFileSync13 } from "fs";
21655
22056
  import { dirname as dirname7, resolve as resolve8 } from "path";
21656
22057
  import { fileURLToPath as fileURLToPath2 } from "url";
21657
22058
  function resolveHyperframeManifestPath() {
@@ -21680,7 +22081,7 @@ function resolveVerifiedHyperframeRuntime() {
21680
22081
  `[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
21681
22082
  );
21682
22083
  }
21683
- const manifestRaw = readFileSync12(manifestPath, "utf8");
22084
+ const manifestRaw = readFileSync13(manifestPath, "utf8");
21684
22085
  const manifest = JSON.parse(manifestRaw);
21685
22086
  const runtimeFileName = manifest.artifacts?.iife;
21686
22087
  if (!runtimeFileName || !manifest.sha256) {
@@ -21692,7 +22093,7 @@ function resolveVerifiedHyperframeRuntime() {
21692
22093
  if (!existsSync22(runtimePath)) {
21693
22094
  throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
21694
22095
  }
21695
- const runtimeSource = readFileSync12(runtimePath, "utf8");
22096
+ const runtimeSource = readFileSync13(runtimePath, "utf8");
21696
22097
  const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
21697
22098
  if (runtimeSha !== manifest.sha256) {
21698
22099
  throw new Error(
@@ -21731,8 +22132,8 @@ var init_hyperframeRuntimeLoader = __esm({
21731
22132
  // ../producer/src/services/fileServer.ts
21732
22133
  import { Hono as Hono3 } from "hono";
21733
22134
  import { serve as serve2 } from "@hono/node-server";
21734
- import { readFileSync as readFileSync13, existsSync as existsSync23, statSync as statSync6 } from "fs";
21735
- import { join as join20, extname as extname3 } from "path";
22135
+ import { readFileSync as readFileSync14, existsSync as existsSync23, statSync as statSync6 } from "fs";
22136
+ import { join as join21, extname as extname4 } from "path";
21736
22137
  function stripEmbeddedRuntimeScripts3(html) {
21737
22138
  if (!html) return html;
21738
22139
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -21801,33 +22202,33 @@ function createFileServer2(options) {
21801
22202
  let requestPath = c2.req.path;
21802
22203
  if (requestPath === "/") requestPath = "/index.html";
21803
22204
  const relativePath = requestPath.replace(/^\//, "");
21804
- const compiledPath = compiledDir ? join20(compiledDir, relativePath) : null;
22205
+ const compiledPath = compiledDir ? join21(compiledDir, relativePath) : null;
21805
22206
  const hasCompiledFile = Boolean(
21806
22207
  compiledPath && existsSync23(compiledPath) && statSync6(compiledPath).isFile()
21807
22208
  );
21808
- const filePath = hasCompiledFile ? compiledPath : join20(projectDir, relativePath);
22209
+ const filePath = hasCompiledFile ? compiledPath : join21(projectDir, relativePath);
21809
22210
  if (!existsSync23(filePath) || !statSync6(filePath).isFile()) {
21810
22211
  return c2.text("Not found", 404);
21811
22212
  }
21812
- const ext = extname3(filePath).toLowerCase();
22213
+ const ext = extname4(filePath).toLowerCase();
21813
22214
  const contentType = MIME_TYPES3[ext] || "application/octet-stream";
21814
22215
  if (ext === ".html") {
21815
- const rawHtml = readFileSync13(filePath, "utf-8");
22216
+ const rawHtml = readFileSync14(filePath, "utf-8");
21816
22217
  const isIndex = relativePath === "index.html";
21817
22218
  const html = isIndex ? injectScriptsIntoHtml2(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
21818
22219
  return c2.text(html, 200, { "Content-Type": contentType });
21819
22220
  }
21820
- const content = readFileSync13(filePath);
22221
+ const content = readFileSync14(filePath);
21821
22222
  return new Response(content, {
21822
22223
  status: 200,
21823
22224
  headers: { "Content-Type": contentType }
21824
22225
  });
21825
22226
  });
21826
- return new Promise((resolve21) => {
22227
+ return new Promise((resolve22) => {
21827
22228
  const server = serve2({ fetch: app.fetch, port }, (info) => {
21828
22229
  const actualPort = info.port;
21829
22230
  const url = `http://localhost:${actualPort}`;
21830
- resolve21({
22231
+ resolve22({
21831
22232
  url,
21832
22233
  port: actualPort,
21833
22234
  close: () => server.close()
@@ -22269,8 +22670,8 @@ var init_deterministicFonts = __esm({
22269
22670
  });
22270
22671
 
22271
22672
  // ../producer/src/services/htmlCompiler.ts
22272
- import { readFileSync as readFileSync14, existsSync as existsSync24, mkdirSync as mkdirSync14 } from "fs";
22273
- import { join as join21, dirname as dirname8, resolve as resolve9 } from "path";
22673
+ import { readFileSync as readFileSync15, existsSync as existsSync24, mkdirSync as mkdirSync14 } from "fs";
22674
+ import { join as join22, dirname as dirname8, resolve as resolve9 } from "path";
22274
22675
  function dedupeElementsById(elements) {
22275
22676
  const deduped = /* @__PURE__ */ new Map();
22276
22677
  for (const element of elements) {
@@ -22288,7 +22689,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
22288
22689
  return { duration: 0, resolvedPath: src };
22289
22690
  }
22290
22691
  } else if (!filePath.startsWith("/")) {
22291
- filePath = join21(baseDir, filePath);
22692
+ filePath = join22(baseDir, filePath);
22292
22693
  }
22293
22694
  if (!existsSync24(filePath)) {
22294
22695
  return { duration: 0, resolvedPath: filePath };
@@ -22361,7 +22762,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
22361
22762
  if (!existsSync24(filePath)) {
22362
22763
  continue;
22363
22764
  }
22364
- const rawSubHtml = readFileSync14(filePath, "utf-8");
22765
+ const rawSubHtml = readFileSync15(filePath, "utf-8");
22365
22766
  const nestedVisited = new Set(visited);
22366
22767
  nestedVisited.add(filePath);
22367
22768
  workItems.push({ srcPath, absoluteStart, absoluteEnd, filePath, rawSubHtml, nestedVisited });
@@ -22556,7 +22957,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
22556
22957
  if (!compHtml) {
22557
22958
  const filePath = resolve9(projectDir, srcPath);
22558
22959
  if (existsSync24(filePath)) {
22559
- compHtml = readFileSync14(filePath, "utf-8");
22960
+ compHtml = readFileSync15(filePath, "utf-8");
22560
22961
  }
22561
22962
  }
22562
22963
  if (!compHtml) {
@@ -22676,7 +23077,7 @@ ${html}
22676
23077
  </html>`;
22677
23078
  }
22678
23079
  async function compileForRender(projectDir, htmlPath, downloadDir) {
22679
- const rawHtml = readFileSync14(htmlPath, "utf-8");
23080
+ const rawHtml = readFileSync15(htmlPath, "utf-8");
22680
23081
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
22681
23082
  rawHtml,
22682
23083
  projectDir,
@@ -22905,12 +23306,12 @@ import {
22905
23306
  existsSync as existsSync25,
22906
23307
  mkdirSync as mkdirSync15,
22907
23308
  rmSync as rmSync6,
22908
- readFileSync as readFileSync15,
22909
- writeFileSync as writeFileSync6,
23309
+ readFileSync as readFileSync16,
23310
+ writeFileSync as writeFileSync7,
22910
23311
  copyFileSync as copyFileSync2,
22911
23312
  appendFileSync
22912
23313
  } from "fs";
22913
- import { join as join22, dirname as dirname9, resolve as resolve10 } from "path";
23314
+ import { join as join23, dirname as dirname9, resolve as resolve10 } from "path";
22914
23315
  import { randomUUID as randomUUID2 } from "crypto";
22915
23316
  import { freemem as freemem2 } from "os";
22916
23317
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -22966,13 +23367,13 @@ function installDebugLogger(logPath, log = defaultLogger) {
22966
23367
  };
22967
23368
  }
22968
23369
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
22969
- const compileDir = join22(workDir, "compiled");
23370
+ const compileDir = join23(workDir, "compiled");
22970
23371
  mkdirSync15(compileDir, { recursive: true });
22971
- writeFileSync6(join22(compileDir, "index.html"), compiled.html, "utf-8");
23372
+ writeFileSync7(join23(compileDir, "index.html"), compiled.html, "utf-8");
22972
23373
  for (const [srcPath, html] of compiled.subCompositions) {
22973
- const outPath = join22(compileDir, srcPath);
23374
+ const outPath = join23(compileDir, srcPath);
22974
23375
  mkdirSync15(dirname9(outPath), { recursive: true });
22975
- writeFileSync6(outPath, html, "utf-8");
23376
+ writeFileSync7(outPath, html, "utf-8");
22976
23377
  }
22977
23378
  if (includeSummary) {
22978
23379
  const summary = {
@@ -22995,7 +23396,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
22995
23396
  })),
22996
23397
  subCompositions: Array.from(compiled.subCompositions.keys())
22997
23398
  };
22998
- writeFileSync6(join22(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
23399
+ writeFileSync7(join23(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
22999
23400
  }
23000
23401
  }
23001
23402
  function createRenderJob(config) {
@@ -23040,8 +23441,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
23040
23441
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
23041
23442
  const moduleDir = dirname9(fileURLToPath3(import.meta.url));
23042
23443
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve10(process.env.PRODUCER_RENDERS_DIR, "..") : resolve10(moduleDir, "../..");
23043
- const debugDir = join22(producerRoot, ".debug");
23044
- const workDir = job.config.debug ? join22(debugDir, job.id) : join22(dirname9(outputPath), `work-${job.id}`);
23444
+ const debugDir = join23(producerRoot, ".debug");
23445
+ const workDir = job.config.debug ? join23(debugDir, job.id) : join23(dirname9(outputPath), `work-${job.id}`);
23045
23446
  const pipelineStart = Date.now();
23046
23447
  const log = job.config.logger ?? defaultLogger;
23047
23448
  let fileServer = null;
@@ -23049,7 +23450,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23049
23450
  let lastBrowserConsole = [];
23050
23451
  let restoreLogger = null;
23051
23452
  const perfStages = {};
23052
- const perfOutputPath = join22(workDir, "perf-summary.json");
23453
+ const perfOutputPath = join23(workDir, "perf-summary.json");
23053
23454
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
23054
23455
  const outputFormat = job.config.format ?? "mp4";
23055
23456
  const isWebm = outputFormat === "webm";
@@ -23069,26 +23470,26 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23069
23470
  assertNotAborted();
23070
23471
  if (!existsSync25(workDir)) mkdirSync15(workDir, { recursive: true });
23071
23472
  if (job.config.debug) {
23072
- const logPath = join22(workDir, "render.log");
23473
+ const logPath = join23(workDir, "render.log");
23073
23474
  restoreLogger = installDebugLogger(logPath, log);
23074
23475
  }
23075
23476
  const entryFile = job.config.entryFile || "index.html";
23076
- let htmlPath = join22(projectDir, entryFile);
23477
+ let htmlPath = join23(projectDir, entryFile);
23077
23478
  if (!existsSync25(htmlPath)) {
23078
23479
  throw new Error(`Entry file not found: ${htmlPath}`);
23079
23480
  }
23080
23481
  assertNotAborted();
23081
- const rawEntry = readFileSync15(htmlPath, "utf-8");
23482
+ const rawEntry = readFileSync16(htmlPath, "utf-8");
23082
23483
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
23083
- const wrapperPath = join22(workDir, "standalone-entry.html");
23084
- const projectIndexPath = join22(projectDir, "index.html");
23484
+ const wrapperPath = join23(workDir, "standalone-entry.html");
23485
+ const projectIndexPath = join23(projectDir, "index.html");
23085
23486
  if (!existsSync25(projectIndexPath)) {
23086
23487
  throw new Error(
23087
23488
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
23088
23489
  );
23089
23490
  }
23090
23491
  const standaloneHtml = extractStandaloneEntryFromIndex(
23091
- readFileSync15(projectIndexPath, "utf-8"),
23492
+ readFileSync16(projectIndexPath, "utf-8"),
23092
23493
  entryFile
23093
23494
  );
23094
23495
  if (!standaloneHtml) {
@@ -23096,7 +23497,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23096
23497
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
23097
23498
  );
23098
23499
  }
23099
- writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
23500
+ writeFileSync7(wrapperPath, standaloneHtml, "utf-8");
23100
23501
  htmlPath = wrapperPath;
23101
23502
  log.info("Extracted standalone entry from index.html host context", {
23102
23503
  entryFile
@@ -23105,7 +23506,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23105
23506
  const stage1Start = Date.now();
23106
23507
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
23107
23508
  const compileStart = Date.now();
23108
- let compiled = await compileForRender(projectDir, htmlPath, join22(workDir, "downloads"));
23509
+ let compiled = await compileForRender(projectDir, htmlPath, join23(workDir, "downloads"));
23109
23510
  assertNotAborted();
23110
23511
  perfStages.compileOnlyMs = Date.now() - compileStart;
23111
23512
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
@@ -23134,7 +23535,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23134
23535
  reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
23135
23536
  fileServer = await createFileServer2({
23136
23537
  projectDir,
23137
- compiledDir: join22(workDir, "compiled"),
23538
+ compiledDir: join23(workDir, "compiled"),
23138
23539
  port: 0
23139
23540
  });
23140
23541
  assertNotAborted();
@@ -23147,7 +23548,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23147
23548
  };
23148
23549
  probeSession = await createCaptureSession(
23149
23550
  fileServer.url,
23150
- join22(workDir, "probe"),
23551
+ join23(workDir, "probe"),
23151
23552
  captureOpts,
23152
23553
  null,
23153
23554
  cfg
@@ -23179,7 +23580,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23179
23580
  compiled,
23180
23581
  resolutions,
23181
23582
  projectDir,
23182
- join22(workDir, "downloads")
23583
+ join23(workDir, "downloads")
23183
23584
  );
23184
23585
  assertNotAborted();
23185
23586
  composition.videos = compiled.videos;
@@ -23276,7 +23677,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23276
23677
  const extractionResult = await extractAllVideoFrames(
23277
23678
  composition.videos,
23278
23679
  projectDir,
23279
- { fps: job.config.fps, outputDir: join22(workDir, "video-frames") },
23680
+ { fps: job.config.fps, outputDir: join23(workDir, "video-frames") },
23280
23681
  abortSignal
23281
23682
  );
23282
23683
  assertNotAborted();
@@ -23308,13 +23709,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23308
23709
  }
23309
23710
  const stage3Start = Date.now();
23310
23711
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
23311
- const audioOutputPath = join22(workDir, "audio.aac");
23712
+ const audioOutputPath = join23(workDir, "audio.aac");
23312
23713
  let hasAudio = false;
23313
23714
  if (composition.audios.length > 0) {
23314
23715
  const audioResult = await processCompositionAudio(
23315
23716
  composition.audios,
23316
23717
  projectDir,
23317
- join22(workDir, "audio-work"),
23718
+ join23(workDir, "audio-work"),
23318
23719
  audioOutputPath,
23319
23720
  job.duration,
23320
23721
  abortSignal
@@ -23330,12 +23731,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23330
23731
  if (!fileServer) {
23331
23732
  fileServer = await createFileServer2({
23332
23733
  projectDir,
23333
- compiledDir: join22(workDir, "compiled"),
23734
+ compiledDir: join23(workDir, "compiled"),
23334
23735
  port: 0
23335
23736
  });
23336
23737
  assertNotAborted();
23337
23738
  }
23338
- const framesDir = join22(workDir, "captured-frames");
23739
+ const framesDir = join23(workDir, "captured-frames");
23339
23740
  if (!existsSync25(framesDir)) mkdirSync15(framesDir, { recursive: true });
23340
23741
  const captureOptions = {
23341
23742
  width,
@@ -23346,7 +23747,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23346
23747
  };
23347
23748
  const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
23348
23749
  const videoExt = isWebm ? ".webm" : ".mp4";
23349
- const videoOnlyPath = join22(workDir, `video-only${videoExt}`);
23750
+ const videoOnlyPath = join23(workDir, `video-only${videoExt}`);
23350
23751
  const preset = getEncoderPreset(job.config.quality, outputFormat);
23351
23752
  job.framesRendered = 0;
23352
23753
  let streamingEncoder = null;
@@ -23615,7 +24016,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23615
24016
  job.perfSummary = perfSummary;
23616
24017
  if (job.config.debug) {
23617
24018
  try {
23618
- writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
24019
+ writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
23619
24020
  } catch (err) {
23620
24021
  log.debug("Failed to write perf summary", {
23621
24022
  perfOutputPath,
@@ -23625,7 +24026,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
23625
24026
  }
23626
24027
  if (job.config.debug) {
23627
24028
  if (existsSync25(outputPath)) {
23628
- const debugOutput = join22(workDir, isWebm ? "output.webm" : "output.mp4");
24029
+ const debugOutput = join23(workDir, isWebm ? "output.webm" : "output.mp4");
23629
24030
  copyFileSync2(outputPath, debugOutput);
23630
24031
  }
23631
24032
  } else {
@@ -23762,8 +24163,8 @@ var init_config3 = __esm({
23762
24163
  });
23763
24164
 
23764
24165
  // ../producer/src/services/hyperframeLint.ts
23765
- import { existsSync as existsSync26, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
23766
- import { resolve as resolve11, join as join23 } from "path";
24166
+ import { existsSync as existsSync26, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
24167
+ import { resolve as resolve11, join as join24 } from "path";
23767
24168
  function isStringRecord(value) {
23768
24169
  if (!value || typeof value !== "object" || Array.isArray(value)) {
23769
24170
  return false;
@@ -23805,13 +24206,13 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
23805
24206
  if (existsSync26(absoluteEntryPath) && statSync7(absoluteEntryPath).isFile()) {
23806
24207
  return {
23807
24208
  entryFile,
23808
- html: readFileSync16(absoluteEntryPath, "utf-8"),
24209
+ html: readFileSync17(absoluteEntryPath, "utf-8"),
23809
24210
  source: "projectDir"
23810
24211
  };
23811
24212
  }
23812
24213
  }
23813
24214
  return {
23814
- error: `No HTML entry file found in project directory: ${join23(absProjectDir, preferredEntryFile || "index.html")}`
24215
+ error: `No HTML entry file found in project directory: ${join24(absProjectDir, preferredEntryFile || "index.html")}`
23815
24216
  };
23816
24217
  }
23817
24218
  function prepareHyperframeLintBody(body) {
@@ -23858,11 +24259,11 @@ var init_hyperframeLint = __esm({
23858
24259
  });
23859
24260
 
23860
24261
  // ../producer/src/utils/paths.ts
23861
- import { resolve as resolve12, basename, join as join24 } from "path";
24262
+ import { resolve as resolve12, basename, join as join25 } from "path";
23862
24263
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
23863
24264
  const absoluteProjectDir = resolve12(projectDir);
23864
24265
  const projectName = basename(absoluteProjectDir);
23865
- const resolvedOutputPath = outputPath ?? join24(rendersDir, `${projectName}.mp4`);
24266
+ const resolvedOutputPath = outputPath ?? join25(rendersDir, `${projectName}.mp4`);
23866
24267
  const absoluteOutputPath = resolve12(resolvedOutputPath);
23867
24268
  return { absoluteProjectDir, absoluteOutputPath };
23868
24269
  }
@@ -23880,11 +24281,11 @@ import {
23880
24281
  mkdirSync as mkdirSync16,
23881
24282
  statSync as statSync8,
23882
24283
  mkdtempSync,
23883
- writeFileSync as writeFileSync7,
24284
+ writeFileSync as writeFileSync8,
23884
24285
  rmSync as rmSync7,
23885
24286
  createReadStream
23886
24287
  } from "fs";
23887
- import { resolve as resolve13, dirname as dirname10, join as join25 } from "path";
24288
+ import { resolve as resolve13, dirname as dirname10, join as join26 } from "path";
23888
24289
  import { tmpdir } from "os";
23889
24290
  import { parseArgs as parseArgs2 } from "util";
23890
24291
  import crypto from "crypto";
@@ -23936,8 +24337,8 @@ async function prepareRenderBody(body) {
23936
24337
  }
23937
24338
  }
23938
24339
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir();
23939
- const tempProjectDir = mkdtempSync(join25(tempRoot, "producer-project-"));
23940
- writeFileSync7(join25(tempProjectDir, "index.html"), htmlContent, "utf-8");
24340
+ const tempProjectDir = mkdtempSync(join26(tempRoot, "producer-project-"));
24341
+ writeFileSync8(join26(tempProjectDir, "index.html"), htmlContent, "utf-8");
23941
24342
  return {
23942
24343
  prepared: {
23943
24344
  input: {
@@ -24390,8 +24791,8 @@ __export(studioServer_exports, {
24390
24791
  });
24391
24792
  import { Hono as Hono5 } from "hono";
24392
24793
  import { streamSSE as streamSSE3 } from "hono/streaming";
24393
- import { existsSync as existsSync28, readFileSync as readFileSync17, writeFileSync as writeFileSync8, statSync as statSync9 } from "fs";
24394
- import { resolve as resolve14, join as join26, basename as basename2 } from "path";
24794
+ import { existsSync as existsSync28, readFileSync as readFileSync18, writeFileSync as writeFileSync9, statSync as statSync9 } from "fs";
24795
+ import { resolve as resolve14, join as join27, basename as basename2 } from "path";
24395
24796
  function resolveDistDir() {
24396
24797
  const builtPath = resolve14(__dirname, "studio");
24397
24798
  if (existsSync28(resolve14(builtPath, "index.html"))) return builtPath;
@@ -24472,7 +24873,7 @@ function createStudioServer(options) {
24472
24873
  return lintHyperframeHtml2(html, opts);
24473
24874
  },
24474
24875
  runtimeUrl: "/api/runtime.js",
24475
- rendersDir: () => join26(projectDir, "renders"),
24876
+ rendersDir: () => join27(projectDir, "renders"),
24476
24877
  startRender(opts) {
24477
24878
  const state = {
24478
24879
  id: opts.jobId,
@@ -24505,7 +24906,7 @@ function createStudioServer(options) {
24505
24906
  state.status = "complete";
24506
24907
  state.progress = 100;
24507
24908
  const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
24508
- writeFileSync8(
24909
+ writeFileSync9(
24509
24910
  metaPath,
24510
24911
  JSON.stringify({ status: "complete", durationMs: Date.now() - startTime })
24511
24912
  );
@@ -24514,7 +24915,7 @@ function createStudioServer(options) {
24514
24915
  state.error = err instanceof Error ? err.message : String(err);
24515
24916
  try {
24516
24917
  const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
24517
- writeFileSync8(metaPath, JSON.stringify({ status: "failed" }));
24918
+ writeFileSync9(metaPath, JSON.stringify({ status: "failed" }));
24518
24919
  } catch {
24519
24920
  }
24520
24921
  }
@@ -24555,7 +24956,7 @@ function createStudioServer(options) {
24555
24956
  const app = new Hono5();
24556
24957
  app.get("/api/runtime.js", (c2) => {
24557
24958
  if (!existsSync28(runtimePath)) return c2.text("runtime not built", 404);
24558
- return c2.body(readFileSync17(runtimePath, "utf-8"), 200, {
24959
+ return c2.body(readFileSync18(runtimePath, "utf-8"), 200, {
24559
24960
  "Content-Type": "text/javascript",
24560
24961
  "Cache-Control": "no-store"
24561
24962
  });
@@ -24588,7 +24989,7 @@ function createStudioServer(options) {
24588
24989
  app.get("/assets/*", (c2) => {
24589
24990
  const filePath = resolve14(studioDir, c2.req.path.slice(1));
24590
24991
  if (!existsSync28(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
24591
- const content = readFileSync17(filePath);
24992
+ const content = readFileSync18(filePath);
24592
24993
  return new Response(content, {
24593
24994
  headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
24594
24995
  });
@@ -24596,7 +24997,7 @@ function createStudioServer(options) {
24596
24997
  app.get("/icons/*", (c2) => {
24597
24998
  const filePath = resolve14(studioDir, c2.req.path.slice(1));
24598
24999
  if (!existsSync28(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
24599
- const content = readFileSync17(filePath);
25000
+ const content = readFileSync18(filePath);
24600
25001
  return new Response(content, {
24601
25002
  headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
24602
25003
  });
@@ -24606,7 +25007,7 @@ function createStudioServer(options) {
24606
25007
  if (!existsSync28(indexPath)) {
24607
25008
  return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
24608
25009
  }
24609
- return c2.html(readFileSync17(indexPath, "utf-8"));
25010
+ return c2.html(readFileSync18(indexPath, "utf-8"));
24610
25011
  });
24611
25012
  return { app, watcher };
24612
25013
  }
@@ -24628,7 +25029,7 @@ __export(dev_exports, {
24628
25029
  });
24629
25030
  import { spawn as spawn7 } from "child_process";
24630
25031
  import { existsSync as existsSync29, lstatSync, symlinkSync, unlinkSync as unlinkSync2, readlinkSync, mkdirSync as mkdirSync17 } from "fs";
24631
- import { resolve as resolve15, dirname as dirname11, basename as basename3, join as join27 } from "path";
25032
+ import { resolve as resolve15, dirname as dirname11, basename as basename3, join as join28 } from "path";
24632
25033
  import { fileURLToPath as fileURLToPath4 } from "url";
24633
25034
  import { createRequire } from "module";
24634
25035
  async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
@@ -24667,9 +25068,9 @@ async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
24667
25068
  async function runDevMode(dir, projectName) {
24668
25069
  const thisFile = fileURLToPath4(import.meta.url);
24669
25070
  const repoRoot = resolve15(dirname11(thisFile), "..", "..", "..", "..");
24670
- const projectsDir = join27(repoRoot, "packages", "studio", "data", "projects");
25071
+ const projectsDir = join28(repoRoot, "packages", "studio", "data", "projects");
24671
25072
  const pName = projectName ?? basename3(dir);
24672
- const symlinkPath = join27(projectsDir, pName);
25073
+ const symlinkPath = join28(projectsDir, pName);
24673
25074
  mkdirSync17(projectsDir, { recursive: true });
24674
25075
  let createdSymlink = false;
24675
25076
  if (dir !== symlinkPath) {
@@ -24693,7 +25094,7 @@ async function runDevMode(dir, projectName) {
24693
25094
  Wt2(c.bold("hyperframes dev"));
24694
25095
  const s = be();
24695
25096
  s.start("Starting studio...");
24696
- const studioPkgDir = join27(repoRoot, "packages", "studio");
25097
+ const studioPkgDir = join28(repoRoot, "packages", "studio");
24697
25098
  const child = spawn7("pnpm", ["exec", "vite"], {
24698
25099
  cwd: studioPkgDir,
24699
25100
  stdio: ["ignore", "pipe", "pipe"]
@@ -24732,13 +25133,13 @@ async function runDevMode(dir, projectName) {
24732
25133
  }
24733
25134
  });
24734
25135
  }
24735
- return new Promise((resolve21) => {
24736
- child.on("close", () => resolve21());
25136
+ return new Promise((resolve22) => {
25137
+ child.on("close", () => resolve22());
24737
25138
  });
24738
25139
  }
24739
25140
  function hasLocalStudio(dir) {
24740
25141
  try {
24741
- const req = createRequire(join27(dir, "package.json"));
25142
+ const req = createRequire(join28(dir, "package.json"));
24742
25143
  req.resolve("@hyperframes/studio/package.json");
24743
25144
  return true;
24744
25145
  } catch {
@@ -24746,11 +25147,11 @@ function hasLocalStudio(dir) {
24746
25147
  }
24747
25148
  }
24748
25149
  async function runLocalStudioMode(dir, projectName) {
24749
- const req = createRequire(join27(dir, "package.json"));
25150
+ const req = createRequire(join28(dir, "package.json"));
24750
25151
  const studioPkgPath = dirname11(req.resolve("@hyperframes/studio/package.json"));
24751
25152
  const pName = projectName ?? basename3(dir);
24752
- const projectsDir = join27(studioPkgPath, "data", "projects");
24753
- const symlinkPath = join27(projectsDir, pName);
25153
+ const projectsDir = join28(studioPkgPath, "data", "projects");
25154
+ const symlinkPath = join28(projectsDir, pName);
24754
25155
  mkdirSync17(projectsDir, { recursive: true });
24755
25156
  let createdSymlink = false;
24756
25157
  if (dir !== symlinkPath) {
@@ -24803,8 +25204,8 @@ async function runLocalStudioMode(dir, projectName) {
24803
25204
  }
24804
25205
  });
24805
25206
  }
24806
- return new Promise((resolve21) => {
24807
- child.on("close", () => resolve21());
25207
+ return new Promise((resolve22) => {
25208
+ child.on("close", () => resolve22());
24808
25209
  });
24809
25210
  }
24810
25211
  async function runEmbeddedMode(dir, startPort, projectName) {
@@ -24867,7 +25268,7 @@ var init_dev = __esm({
24867
25268
  const startPort = parseInt(args.port ?? "3002", 10);
24868
25269
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
24869
25270
  const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
24870
- const indexPath = join27(dir, "index.html");
25271
+ const indexPath = join28(dir, "index.html");
24871
25272
  if (existsSync29(indexPath)) {
24872
25273
  const project = { dir, name: projectName, indexPath };
24873
25274
  const lintResult = lintProject(project);
@@ -25023,7 +25424,7 @@ __export(render_exports, {
25023
25424
  });
25024
25425
  import { existsSync as existsSync31, mkdirSync as mkdirSync18, statSync as statSync11 } from "fs";
25025
25426
  import { cpus as cpus3, freemem as freemem3 } from "os";
25026
- import { resolve as resolve17, dirname as dirname12, join as join28 } from "path";
25427
+ import { resolve as resolve17, dirname as dirname12, join as join29 } from "path";
25027
25428
  function defaultWorkerCount() {
25028
25429
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
25029
25430
  }
@@ -25240,7 +25641,7 @@ Examples:
25240
25641
  const now = /* @__PURE__ */ new Date();
25241
25642
  const datePart = now.toISOString().slice(0, 10);
25242
25643
  const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
25243
- const outputPath = args.output ? resolve17(args.output) : join28(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
25644
+ const outputPath = args.output ? resolve17(args.output) : join29(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
25244
25645
  mkdirSync18(dirname12(outputPath), { recursive: true });
25245
25646
  const useDocker = args.docker ?? false;
25246
25647
  const useGpu = args.gpu ?? false;
@@ -25350,17 +25751,17 @@ __export(transcribe_exports, {
25350
25751
  transcribe: () => transcribe
25351
25752
  });
25352
25753
  import { execFileSync as execFileSync3 } from "child_process";
25353
- import { existsSync as existsSync32, readFileSync as readFileSync18, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
25354
- import { join as join29, extname as extname4 } from "path";
25754
+ import { existsSync as existsSync32, readFileSync as readFileSync19, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
25755
+ import { join as join30, extname as extname5 } from "path";
25355
25756
  import { tmpdir as tmpdir2 } from "os";
25356
25757
  function isAudioFile(filePath) {
25357
- return AUDIO_EXTENSIONS.has(extname4(filePath).toLowerCase());
25758
+ return AUDIO_EXTENSIONS.has(extname5(filePath).toLowerCase());
25358
25759
  }
25359
25760
  function isVideoFile(filePath) {
25360
- return VIDEO_EXTENSIONS.has(extname4(filePath).toLowerCase());
25761
+ return VIDEO_EXTENSIONS.has(extname5(filePath).toLowerCase());
25361
25762
  }
25362
25763
  function extractAudio(videoPath) {
25363
- const wavPath = join29(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
25764
+ const wavPath = join30(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
25364
25765
  execFileSync3(
25365
25766
  "ffmpeg",
25366
25767
  ["-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -25383,10 +25784,10 @@ function isWav16kMono(filePath) {
25383
25784
  }
25384
25785
  }
25385
25786
  function prepareAudio(audioPath) {
25386
- if (extname4(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
25787
+ if (extname5(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
25387
25788
  return audioPath;
25388
25789
  }
25389
- const wavPath = join29(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
25790
+ const wavPath = join30(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
25390
25791
  execFileSync3(
25391
25792
  "ffmpeg",
25392
25793
  ["-i", audioPath, "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -25403,7 +25804,7 @@ async function transcribe(inputPath, outputDir, options) {
25403
25804
  onProgress: options?.onProgress
25404
25805
  });
25405
25806
  let wavPath;
25406
- const ext = extname4(inputPath).toLowerCase();
25807
+ const ext = extname5(inputPath).toLowerCase();
25407
25808
  if (isAudioFile(inputPath)) {
25408
25809
  options?.onProgress?.("Preparing audio...");
25409
25810
  wavPath = prepareAudio(inputPath);
@@ -25419,28 +25820,28 @@ async function transcribe(inputPath, outputDir, options) {
25419
25820
  throw new Error(`Unsupported file type: ${ext}`);
25420
25821
  }
25421
25822
  options?.onProgress?.("Transcribing...");
25422
- const outputBase = join29(outputDir, "transcript");
25823
+ const outputBase = join30(outputDir, "transcript");
25423
25824
  mkdirSync19(outputDir, { recursive: true });
25424
- execFileSync3(
25425
- whisper.executablePath,
25426
- [
25427
- "--model",
25428
- modelPath,
25429
- "--output-json-full",
25430
- "--output-file",
25431
- outputBase,
25432
- "--dtw",
25433
- model,
25434
- "--suppress-nst",
25435
- wavPath
25436
- ],
25437
- { stdio: "ignore", timeout: 3e5 }
25438
- );
25825
+ const whisperArgs = [
25826
+ "--model",
25827
+ modelPath,
25828
+ "--output-json-full",
25829
+ "--output-file",
25830
+ outputBase,
25831
+ "--dtw",
25832
+ model,
25833
+ "--suppress-nst"
25834
+ ];
25835
+ if (options?.language) {
25836
+ whisperArgs.push("--language", options.language);
25837
+ }
25838
+ whisperArgs.push(wavPath);
25839
+ execFileSync3(whisper.executablePath, whisperArgs, { stdio: "ignore", timeout: 3e5 });
25439
25840
  const transcriptPath = `${outputBase}.json`;
25440
25841
  if (!existsSync32(transcriptPath)) {
25441
25842
  throw new Error("Whisper did not produce output. Check the input file.");
25442
25843
  }
25443
- const transcript = JSON.parse(readFileSync18(transcriptPath, "utf-8"));
25844
+ const transcript = JSON.parse(readFileSync19(transcriptPath, "utf-8"));
25444
25845
  const segments = transcript.transcription ?? [];
25445
25846
  let wordCount = 0;
25446
25847
  let maxEnd = 0;
@@ -25483,11 +25884,11 @@ import {
25483
25884
  mkdirSync as mkdirSync20,
25484
25885
  copyFileSync as copyFileSync3,
25485
25886
  cpSync as cpSync2,
25486
- writeFileSync as writeFileSync9,
25487
- readFileSync as readFileSync19,
25488
- readdirSync as readdirSync9
25887
+ writeFileSync as writeFileSync10,
25888
+ readFileSync as readFileSync20,
25889
+ readdirSync as readdirSync10
25489
25890
  } from "fs";
25490
- import { resolve as resolve18, basename as basename5, join as join30, dirname as dirname13 } from "path";
25891
+ import { resolve as resolve18, basename as basename5, join as join31, dirname as dirname13 } from "path";
25491
25892
  import { fileURLToPath as fileURLToPath5 } from "url";
25492
25893
  import { execFileSync as execFileSync4, spawn as spawn8 } from "child_process";
25493
25894
  async function installSkills(interactive) {
@@ -25622,9 +26023,9 @@ function getBundledSkillsDir() {
25622
26023
  return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
25623
26024
  }
25624
26025
  function patchVideoSrc(dir, videoFilename, durationSeconds) {
25625
- const htmlFiles = readdirSync9(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join30(e.parentPath ?? e.path, e.name));
26026
+ const htmlFiles = readdirSync10(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join31(e.parentPath ?? e.path, e.name));
25626
26027
  for (const file of htmlFiles) {
25627
- let content = readFileSync19(file, "utf-8");
26028
+ let content = readFileSync20(file, "utf-8");
25628
26029
  if (videoFilename) {
25629
26030
  content = content.replaceAll("__VIDEO_SRC__", videoFilename);
25630
26031
  } else {
@@ -25635,49 +26036,14 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
25635
26036
  }
25636
26037
  const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
25637
26038
  content = content.replaceAll("__VIDEO_DURATION__", dur);
25638
- writeFileSync9(file, content, "utf-8");
26039
+ writeFileSync10(file, content, "utf-8");
25639
26040
  }
25640
26041
  }
25641
- function patchTranscript(dir, transcriptPath) {
25642
- const raw = JSON.parse(readFileSync19(transcriptPath, "utf-8"));
25643
- const words = [];
25644
- for (const seg of raw.transcription ?? []) {
25645
- for (const token of seg.tokens ?? []) {
25646
- const text = (token.text ?? "").trim();
25647
- if (!text || text.startsWith("[_") || text.startsWith("[BLANK")) continue;
25648
- const isPunctuation = /^[.,!?;:'")\]}>…–—-]+$/.test(text);
25649
- const lastWord = words[words.length - 1];
25650
- if (isPunctuation && lastWord) {
25651
- lastWord.text += text;
25652
- lastWord.end = Math.round((token.offsets?.to ?? 0) / 1e3 * 1e3) / 1e3;
25653
- continue;
25654
- }
25655
- words.push({
25656
- text,
25657
- start: Math.round((token.offsets?.from ?? 0) / 1e3 * 1e3) / 1e3,
25658
- end: Math.round((token.offsets?.to ?? 0) / 1e3 * 1e3) / 1e3
25659
- });
25660
- }
25661
- }
26042
+ async function patchTranscript(dir, transcriptPath) {
26043
+ const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
26044
+ const { words } = loadTranscript2(transcriptPath);
25662
26045
  if (words.length === 0) return;
25663
- const wordsJson = JSON.stringify(words, null, 10).replace(/^\[/, "[").replace(/\n {10}/g, "\n ");
25664
- const htmlFiles = readdirSync9(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join30(e.parentPath ?? e.path, e.name));
25665
- for (const file of htmlFiles) {
25666
- let content = readFileSync19(file, "utf-8");
25667
- const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
25668
- let scriptMatch = null;
25669
- let transcriptMatch = null;
25670
- for (const block of scriptBlocks) {
25671
- scriptMatch = scriptMatch ?? block.match(/const script = \[[\s\S]*?\];/);
25672
- transcriptMatch = transcriptMatch ?? block.match(/const TRANSCRIPT = \[[\s\S]*?\];/);
25673
- }
25674
- const match = scriptMatch ?? transcriptMatch;
25675
- if (match) {
25676
- const varName = scriptMatch ? "script" : "TRANSCRIPT";
25677
- content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
25678
- writeFileSync9(file, content, "utf-8");
25679
- }
25680
- }
26046
+ patchCaptionHtml2(dir, words);
25681
26047
  }
25682
26048
  async function handleVideoFile(videoPath, destDir, interactive) {
25683
26049
  const probed = probeVideo(videoPath);
@@ -25766,7 +26132,7 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
25766
26132
  const templateDir = getStaticTemplateDir(templateId);
25767
26133
  cpSync2(templateDir, destDir, { recursive: true });
25768
26134
  patchVideoSrc(destDir, localVideoName, durationSeconds);
25769
- writeFileSync9(
26135
+ writeFileSync10(
25770
26136
  resolve18(destDir, "meta.json"),
25771
26137
  JSON.stringify(
25772
26138
  {
@@ -25781,8 +26147,8 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
25781
26147
  );
25782
26148
  const sharedDir = getSharedTemplateDir();
25783
26149
  if (existsSync33(sharedDir)) {
25784
- for (const entry of readdirSync9(sharedDir, { withFileTypes: true })) {
25785
- const src = join30(sharedDir, entry.name);
26150
+ for (const entry of readdirSync10(sharedDir, { withFileTypes: true })) {
26151
+ const src = join31(sharedDir, entry.name);
25786
26152
  const dest = resolve18(destDir, entry.name);
25787
26153
  if (entry.isFile() || entry.isSymbolicLink()) {
25788
26154
  copyFileSync3(src, dest);
@@ -25793,7 +26159,7 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
25793
26159
  if (existsSync33(skillsSrcDir)) {
25794
26160
  const projectSkills = ["hyperframes-compose", "hyperframes-captions"];
25795
26161
  for (const skill of projectSkills) {
25796
- const src = join30(skillsSrcDir, skill);
26162
+ const src = join31(skillsSrcDir, skill);
25797
26163
  if (existsSync33(src)) {
25798
26164
  const dest = resolve18(destDir, ".claude", "skills", skill);
25799
26165
  mkdirSync20(dest, { recursive: true });
@@ -25890,6 +26256,14 @@ Examples:
25890
26256
  type: "boolean",
25891
26257
  description: "Skip whisper transcription"
25892
26258
  },
26259
+ model: {
26260
+ type: "string",
26261
+ description: "Whisper model for transcription (e.g. tiny.en, base.en, small.en, medium.en, large)"
26262
+ },
26263
+ language: {
26264
+ type: "string",
26265
+ description: "Language code for transcription (e.g. en, es, ja). Filters out non-target speech."
26266
+ },
25893
26267
  "non-interactive": {
25894
26268
  type: "boolean",
25895
26269
  description: "Disable interactive prompts (for CI/agents)"
@@ -25902,6 +26276,8 @@ Examples:
25902
26276
  const skipSkills = args["skip-skills"] === true;
25903
26277
  const skipTranscribe = args["skip-transcribe"] === true;
25904
26278
  const nonInteractive = args["non-interactive"] === true;
26279
+ const modelFlag = args.model;
26280
+ const languageFlag = args.language;
25905
26281
  const interactive = !nonInteractive && process.stdout.isTTY === true;
25906
26282
  if (!interactive) {
25907
26283
  const resolvedTemplate = templateFlag ?? "blank";
@@ -25913,7 +26289,7 @@ Examples:
25913
26289
  const templateId2 = resolvedTemplate;
25914
26290
  const name2 = args.name ?? "my-video";
25915
26291
  const destDir2 = resolve18(name2);
25916
- if (existsSync33(destDir2) && readdirSync9(destDir2).length > 0) {
26292
+ if (existsSync33(destDir2) && readdirSync10(destDir2).length > 0) {
25917
26293
  console.error(c.error(`Directory already exists and is not empty: ${name2}`));
25918
26294
  process.exit(1);
25919
26295
  }
@@ -25949,10 +26325,13 @@ Examples:
25949
26325
  try {
25950
26326
  const { ensureWhisper: ensureWhisper2, ensureModel: ensureModel2 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
25951
26327
  await ensureWhisper2();
25952
- await ensureModel2();
26328
+ await ensureModel2(modelFlag);
25953
26329
  console.log("Transcribing...");
25954
26330
  const { transcribe: runTranscribe } = await Promise.resolve().then(() => (init_transcribe(), transcribe_exports));
25955
- const result = await runTranscribe(sourceFilePath2, destDir2);
26331
+ const result = await runTranscribe(sourceFilePath2, destDir2, {
26332
+ model: modelFlag,
26333
+ language: languageFlag
26334
+ });
25956
26335
  console.log(
25957
26336
  `Transcribed: ${result.wordCount} words (${result.durationSeconds.toFixed(1)}s)`
25958
26337
  );
@@ -25965,13 +26344,13 @@ Examples:
25965
26344
  trackInitTemplate(templateId2);
25966
26345
  const transcriptFile2 = resolve18(destDir2, "transcript.json");
25967
26346
  if (existsSync33(transcriptFile2)) {
25968
- patchTranscript(destDir2, transcriptFile2);
26347
+ await patchTranscript(destDir2, transcriptFile2);
25969
26348
  }
25970
26349
  if (!skipSkills) {
25971
26350
  await installSkills(false);
25972
26351
  }
25973
26352
  console.log(c.success(`Created ${c.accent(name2 + "/")}`));
25974
- for (const f of readdirSync9(destDir2).filter((f2) => !f2.startsWith("."))) {
26353
+ for (const f of readdirSync10(destDir2).filter((f2) => !f2.startsWith("."))) {
25975
26354
  console.log(` ${c.accent(f)}`);
25976
26355
  }
25977
26356
  console.log();
@@ -26012,7 +26391,7 @@ Examples:
26012
26391
  name = nameResult;
26013
26392
  }
26014
26393
  const destDir = resolve18(name);
26015
- if (existsSync33(destDir) && readdirSync9(destDir).length > 0) {
26394
+ if (existsSync33(destDir) && readdirSync10(destDir).length > 0) {
26016
26395
  const overwrite = await Rt({
26017
26396
  message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
26018
26397
  initialValue: false
@@ -26105,12 +26484,14 @@ Examples:
26105
26484
  await ensureWhisper2({
26106
26485
  onProgress: (msg) => spin.message(msg)
26107
26486
  });
26108
- await ensureModel2(void 0, {
26487
+ await ensureModel2(modelFlag, {
26109
26488
  onProgress: (msg) => spin.message(msg)
26110
26489
  });
26111
26490
  spin.message("Transcribing audio...");
26112
26491
  const { transcribe: runTranscribe } = await Promise.resolve().then(() => (init_transcribe(), transcribe_exports));
26113
26492
  const transcribeResult = await runTranscribe(sourceFilePath, destDir, {
26493
+ model: modelFlag,
26494
+ language: languageFlag,
26114
26495
  onProgress: (msg) => spin.message(msg)
26115
26496
  });
26116
26497
  spin.stop(
@@ -26155,7 +26536,7 @@ Examples:
26155
26536
  if (!skipSkills) {
26156
26537
  await installSkills(true);
26157
26538
  }
26158
- const files = readdirSync9(destDir);
26539
+ const files = readdirSync10(destDir);
26159
26540
  Vt2(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
26160
26541
  R2.message(
26161
26542
  `${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.
@@ -26178,40 +26559,87 @@ var init_lint3 = __esm({
26178
26559
  "use strict";
26179
26560
  init_dist();
26180
26561
  init_colors();
26181
- init_project();
26182
- init_lintProject();
26183
26562
  init_lintFormat();
26563
+ init_lintProject();
26564
+ init_project();
26184
26565
  init_updateCheck();
26185
26566
  lint_default = defineCommand({
26186
- meta: { name: "lint", description: "Validate a composition for common mistakes" },
26567
+ meta: {
26568
+ name: "lint",
26569
+ description: "Validate a composition for common mistakes"
26570
+ },
26187
26571
  args: {
26188
- dir: { type: "positional", description: "Project directory", required: false },
26189
- json: { type: "boolean", description: "Output findings as JSON", default: false }
26572
+ dir: {
26573
+ type: "positional",
26574
+ description: "Project directory",
26575
+ required: false
26576
+ },
26577
+ json: {
26578
+ type: "boolean",
26579
+ description: "Output findings as JSON",
26580
+ default: false
26581
+ },
26582
+ verbose: {
26583
+ type: "boolean",
26584
+ description: "Show info-level findings (hidden by default)",
26585
+ default: false
26586
+ }
26190
26587
  },
26191
26588
  async run({ args }) {
26192
- const project = resolveProject(args.dir);
26193
- const lintResult = lintProject(project);
26194
- if (args.json) {
26195
- const combined = {
26196
- ok: lintResult.totalErrors === 0,
26197
- errorCount: lintResult.totalErrors,
26198
- warningCount: lintResult.totalWarnings,
26199
- findings: lintResult.results.flatMap((r) => r.result.findings)
26200
- };
26201
- console.log(JSON.stringify(withMeta(combined), null, 2));
26202
- process.exit(combined.ok ? 0 : 1);
26203
- }
26204
- const fileCount = lintResult.results.length;
26205
- const fileLabel = fileCount === 1 ? lintResult.results[0].file : `${fileCount} files`;
26206
- console.log(`${c.accent("\u25C6")} Linting ${c.accent(project.name + "/" + fileLabel)}`);
26207
- console.log();
26208
- if (lintResult.totalErrors === 0 && lintResult.totalWarnings === 0) {
26209
- console.log(`${c.success("\u25C7")} ${c.success("0 errors, 0 warnings")}`);
26210
- return;
26589
+ try {
26590
+ const project = resolveProject(args.dir);
26591
+ const lintResult = lintProject(project);
26592
+ if (args.json) {
26593
+ const allFindings = lintResult.results.flatMap((r) => r.result.findings);
26594
+ const combined = {
26595
+ ok: lintResult.totalErrors === 0,
26596
+ errorCount: lintResult.totalErrors,
26597
+ warningCount: lintResult.totalWarnings,
26598
+ infoCount: lintResult.totalInfos,
26599
+ findings: args.verbose ? allFindings : allFindings.filter((f) => f.severity !== "info"),
26600
+ filesScanned: lintResult.results.length
26601
+ };
26602
+ console.log(JSON.stringify(withMeta(combined), null, 2));
26603
+ process.exit(combined.ok ? 0 : 1);
26604
+ }
26605
+ const fileCount = lintResult.results.length;
26606
+ const fileLabel = fileCount === 1 ? lintResult.results[0]?.file ?? "index.html" : `${fileCount} files`;
26607
+ console.log(`${c.accent("\u25C6")} Linting ${c.accent(`${project.name}/${fileLabel}`)}`);
26608
+ console.log();
26609
+ if (lintResult.totalErrors === 0 && lintResult.totalWarnings === 0) {
26610
+ console.log(`${c.success("\u25C7")} ${c.success("0 errors, 0 warnings")}`);
26611
+ return;
26612
+ }
26613
+ const lines = formatLintFindings(lintResult, {
26614
+ showElementId: true,
26615
+ showSummary: true,
26616
+ verbose: args.verbose
26617
+ });
26618
+ for (const line of lines) console.log(line);
26619
+ process.exit(lintResult.totalErrors > 0 ? 1 : 0);
26620
+ } catch (err) {
26621
+ const message = err instanceof Error ? err.message : String(err);
26622
+ if (args.json) {
26623
+ console.log(
26624
+ JSON.stringify(
26625
+ withMeta({
26626
+ ok: false,
26627
+ error: message,
26628
+ findings: [],
26629
+ errorCount: 0,
26630
+ warningCount: 0,
26631
+ infoCount: 0,
26632
+ filesScanned: 0
26633
+ }),
26634
+ null,
26635
+ 2
26636
+ )
26637
+ );
26638
+ process.exit(1);
26639
+ }
26640
+ console.error(message);
26641
+ process.exit(1);
26211
26642
  }
26212
- const lines = formatLintFindings(lintResult, { showElementId: true, showSummary: true });
26213
- for (const line of lines) console.log(line);
26214
- process.exit(lintResult.totalErrors > 0 ? 1 : 0);
26215
26643
  }
26216
26644
  });
26217
26645
  }
@@ -26235,12 +26663,12 @@ var info_exports = {};
26235
26663
  __export(info_exports, {
26236
26664
  default: () => info_default
26237
26665
  });
26238
- import { readFileSync as readFileSync20, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
26239
- import { join as join31 } from "path";
26666
+ import { readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync12 } from "fs";
26667
+ import { join as join32 } from "path";
26240
26668
  function totalSize(dir) {
26241
26669
  let total = 0;
26242
- for (const entry of readdirSync10(dir, { withFileTypes: true })) {
26243
- const path = join31(dir, entry.name);
26670
+ for (const entry of readdirSync11(dir, { withFileTypes: true })) {
26671
+ const path = join32(dir, entry.name);
26244
26672
  if (entry.isDirectory()) {
26245
26673
  total += totalSize(path);
26246
26674
  } else {
@@ -26268,7 +26696,7 @@ var init_info = __esm({
26268
26696
  },
26269
26697
  async run({ args }) {
26270
26698
  const project = resolveProject(args.dir);
26271
- const html = readFileSync20(project.indexPath, "utf-8");
26699
+ const html = readFileSync21(project.indexPath, "utf-8");
26272
26700
  ensureDOMParser();
26273
26701
  const parsed = parseHtml(html);
26274
26702
  const tracks = new Set(parsed.elements.map((el) => el.zIndex));
@@ -26319,7 +26747,7 @@ var compositions_exports = {};
26319
26747
  __export(compositions_exports, {
26320
26748
  default: () => compositions_default
26321
26749
  });
26322
- import { readFileSync as readFileSync21 } from "fs";
26750
+ import { readFileSync as readFileSync22 } from "fs";
26323
26751
  function parseCompositions(html) {
26324
26752
  const parser = new DOMParser();
26325
26753
  const doc = parser.parseFromString(html, "text/html");
@@ -26376,7 +26804,7 @@ var init_compositions = __esm({
26376
26804
  },
26377
26805
  async run({ args }) {
26378
26806
  const project = resolveProject(args.dir);
26379
- const html = readFileSync21(project.indexPath, "utf-8");
26807
+ const html = readFileSync22(project.indexPath, "utf-8");
26380
26808
  ensureDOMParser();
26381
26809
  const compositions = parseCompositions(html);
26382
26810
  if (compositions.length === 0) {
@@ -26413,7 +26841,7 @@ __export(benchmark_exports, {
26413
26841
  default: () => benchmark_default
26414
26842
  });
26415
26843
  import { existsSync as existsSync34, statSync as statSync13 } from "fs";
26416
- import { resolve as resolve19, join as join32 } from "path";
26844
+ import { resolve as resolve19, join as join33 } from "path";
26417
26845
  var DEFAULT_CONFIGS, benchmark_default;
26418
26846
  var init_benchmark = __esm({
26419
26847
  "src/commands/benchmark.ts"() {
@@ -26483,7 +26911,7 @@ var init_benchmark = __esm({
26483
26911
  s?.start(`Benchmarking ${config.label}...`);
26484
26912
  for (let i = 0; i < runsPerConfig; i++) {
26485
26913
  s?.message(`${config.label} \u2014 run ${i + 1}/${runsPerConfig}`);
26486
- const outputPath = join32(
26914
+ const outputPath = join33(
26487
26915
  benchDir,
26488
26916
  `${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i}.mp4`
26489
26917
  );
@@ -26701,20 +27129,151 @@ Run ${c.accent("hyperframes browser --help")} for usage.`
26701
27129
  }
26702
27130
  });
26703
27131
 
27132
+ // src/commands/transcribe.ts
27133
+ var transcribe_exports2 = {};
27134
+ __export(transcribe_exports2, {
27135
+ default: () => transcribe_default
27136
+ });
27137
+ import { existsSync as existsSync35, writeFileSync as writeFileSync11 } from "fs";
27138
+ import { resolve as resolve20, join as join34, extname as extname6 } from "path";
27139
+ async function importTranscript(inputPath, dir, json) {
27140
+ const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
27141
+ const { words, format } = loadTranscript2(inputPath);
27142
+ if (words.length === 0) {
27143
+ console.error(c.error("No words found in transcript."));
27144
+ process.exit(1);
27145
+ }
27146
+ const outPath = join34(dir, "transcript.json");
27147
+ writeFileSync11(outPath, JSON.stringify(words, null, 2));
27148
+ patchCaptionHtml2(dir, words);
27149
+ if (json) {
27150
+ console.log(
27151
+ JSON.stringify({ ok: true, format, wordCount: words.length, transcriptPath: outPath })
27152
+ );
27153
+ } else {
27154
+ console.log(
27155
+ `${c.success("\u25C7")} Imported ${c.accent(String(words.length))} words from ${c.accent(format)} format \u2192 ${c.accent("transcript.json")}`
27156
+ );
27157
+ }
27158
+ }
27159
+ async function transcribeAudio(inputPath, dir, opts) {
27160
+ const { transcribe: transcribe2 } = await Promise.resolve().then(() => (init_transcribe(), transcribe_exports));
27161
+ const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
27162
+ const model = opts.model ?? DEFAULT_MODEL;
27163
+ const spin = opts.json ? null : be();
27164
+ spin?.start(`Transcribing with ${c.accent(model)}...`);
27165
+ try {
27166
+ const result = await transcribe2(inputPath, dir, {
27167
+ model,
27168
+ language: opts.language,
27169
+ onProgress: spin ? (msg) => spin.message(msg) : void 0
27170
+ });
27171
+ const { words } = loadTranscript2(result.transcriptPath);
27172
+ writeFileSync11(result.transcriptPath, JSON.stringify(words, null, 2));
27173
+ patchCaptionHtml2(dir, words);
27174
+ if (opts.json) {
27175
+ console.log(
27176
+ JSON.stringify({
27177
+ ok: true,
27178
+ model,
27179
+ wordCount: words.length,
27180
+ durationSeconds: result.durationSeconds,
27181
+ transcriptPath: result.transcriptPath
27182
+ })
27183
+ );
27184
+ } else {
27185
+ spin.stop(
27186
+ c.success(
27187
+ `Transcribed ${c.accent(String(words.length))} words (${result.durationSeconds.toFixed(1)}s)`
27188
+ )
27189
+ );
27190
+ }
27191
+ } catch (err) {
27192
+ const message = err instanceof Error ? err.message : String(err);
27193
+ if (opts.json) {
27194
+ console.log(JSON.stringify({ ok: false, error: message }));
27195
+ } else {
27196
+ spin.stop(c.error(`Transcription failed: ${message}`));
27197
+ }
27198
+ process.exit(1);
27199
+ }
27200
+ }
27201
+ var transcribe_default;
27202
+ var init_transcribe2 = __esm({
27203
+ "src/commands/transcribe.ts"() {
27204
+ "use strict";
27205
+ init_dist();
27206
+ init_dist3();
27207
+ init_colors();
27208
+ init_manager();
27209
+ transcribe_default = defineCommand({
27210
+ meta: {
27211
+ name: "transcribe",
27212
+ description: "Transcribe audio/video to word-level timestamps, or import an existing transcript"
27213
+ },
27214
+ args: {
27215
+ input: {
27216
+ type: "positional",
27217
+ description: "Audio/video file to transcribe, or transcript file to import (.json, .srt, .vtt)",
27218
+ required: true
27219
+ },
27220
+ dir: {
27221
+ type: "string",
27222
+ description: "Project directory (default: current directory)",
27223
+ alias: "d"
27224
+ },
27225
+ model: {
27226
+ type: "string",
27227
+ description: `Whisper model (default: ${DEFAULT_MODEL}). Options: tiny.en, base.en, small.en, medium.en, large-v3`,
27228
+ alias: "m"
27229
+ },
27230
+ language: {
27231
+ type: "string",
27232
+ description: "Language code (e.g. en, es, ja). Filters out non-target language speech.",
27233
+ alias: "l"
27234
+ },
27235
+ json: {
27236
+ type: "boolean",
27237
+ description: "Output result as JSON",
27238
+ default: false
27239
+ }
27240
+ },
27241
+ async run({ args }) {
27242
+ const inputPath = resolve20(args.input);
27243
+ if (!existsSync35(inputPath)) {
27244
+ console.error(c.error(`File not found: ${args.input}`));
27245
+ process.exit(1);
27246
+ }
27247
+ const dir = resolve20(args.dir ?? ".");
27248
+ const ext = extname6(inputPath).toLowerCase();
27249
+ const isImport = ext === ".json" || ext === ".srt" || ext === ".vtt";
27250
+ if (isImport) {
27251
+ return importTranscript(inputPath, dir, args.json);
27252
+ }
27253
+ return transcribeAudio(inputPath, dir, {
27254
+ model: args.model,
27255
+ language: args.language,
27256
+ json: args.json
27257
+ });
27258
+ }
27259
+ });
27260
+ }
27261
+ });
27262
+
26704
27263
  // src/commands/docs.ts
26705
27264
  var docs_exports = {};
26706
27265
  __export(docs_exports, {
26707
27266
  default: () => docs_default
26708
27267
  });
26709
- import { readFileSync as readFileSync22, existsSync as existsSync35 } from "fs";
26710
- import { resolve as resolve20, dirname as dirname14, join as join33 } from "path";
27268
+ import { readFileSync as readFileSync23, existsSync as existsSync36 } from "fs";
27269
+ import { resolve as resolve21, dirname as dirname14, join as join35 } from "path";
26711
27270
  import { fileURLToPath as fileURLToPath6 } from "url";
26712
27271
  function docsDir() {
26713
27272
  const thisFile = fileURLToPath6(import.meta.url);
26714
27273
  const dir = dirname14(thisFile);
26715
- const devPath = resolve20(dir, "..", "docs");
26716
- const builtPath = resolve20(dir, "docs");
26717
- return existsSync35(devPath) ? devPath : builtPath;
27274
+ const devPath = resolve21(dir, "..", "docs");
27275
+ const builtPath = resolve21(dir, "docs");
27276
+ return existsSync36(devPath) ? devPath : builtPath;
26718
27277
  }
26719
27278
  function formatInlineCode(line) {
26720
27279
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -26805,12 +27364,12 @@ var init_docs = __esm({
26805
27364
  }
26806
27365
  process.exit(1);
26807
27366
  }
26808
- const filePath = join33(docsDir(), entry.file);
26809
- if (!existsSync35(filePath)) {
27367
+ const filePath = join35(docsDir(), entry.file);
27368
+ if (!existsSync36(filePath)) {
26810
27369
  console.error(c.error(`Doc file not found: ${filePath}`));
26811
27370
  process.exit(1);
26812
27371
  }
26813
- const content = readFileSync22(filePath, "utf-8");
27372
+ const content = readFileSync23(filePath, "utf-8");
26814
27373
  console.log();
26815
27374
  renderMarkdown(content);
26816
27375
  }
@@ -27211,6 +27770,7 @@ var subCommands = {
27211
27770
  benchmark: () => Promise.resolve().then(() => (init_benchmark(), benchmark_exports)).then((m) => m.default),
27212
27771
  browser: () => Promise.resolve().then(() => (init_browser(), browser_exports)).then((m) => m.default),
27213
27772
  skills: () => Promise.resolve().then(() => (init_install_skills(), install_skills_exports)).then((m) => m.default),
27773
+ transcribe: () => Promise.resolve().then(() => (init_transcribe2(), transcribe_exports2)).then((m) => m.default),
27214
27774
  docs: () => Promise.resolve().then(() => (init_docs(), docs_exports)).then((m) => m.default),
27215
27775
  doctor: () => Promise.resolve().then(() => (init_doctor(), doctor_exports)).then((m) => m.default),
27216
27776
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),