hyperframes 0.1.9 → 0.1.11

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.
Files changed (27) hide show
  1. package/dist/cli.js +1557 -804
  2. package/dist/skills/{captions → hyperframes-captions}/SKILL.md +1 -1
  3. package/dist/skills/{compose-video → hyperframes-compose}/SKILL.md +1 -1
  4. package/dist/studio/assets/index-Bj0pPj_X.js +92 -0
  5. package/dist/studio/assets/index-BnvciBdD.css +1 -0
  6. package/dist/studio/index.html +2 -2
  7. package/dist/templates/_shared/CLAUDE.md +21 -9
  8. package/dist/templates/play-mode/index.html +1 -1
  9. package/dist/templates/swiss-grid/compositions/graphics.html +1 -1
  10. package/dist/templates/swiss-grid/index.html +1 -1
  11. package/dist/templates/vignelli/index.html +1 -1
  12. package/dist/templates/warm-grain/index.html +1 -1
  13. package/package.json +2 -2
  14. package/dist/studio/assets/index-Df6fO-S6.js +0 -78
  15. package/dist/studio/assets/index-KoBceNoU.css +0 -1
  16. /package/dist/skills/{compose-video → hyperframes-compose}/data-in-motion.md +0 -0
  17. /package/dist/skills/{compose-video → hyperframes-compose}/house-style.md +0 -0
  18. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/bold-energetic.md +0 -0
  19. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/clean-corporate.md +0 -0
  20. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/dark-premium.md +0 -0
  21. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/jewel-rich.md +0 -0
  22. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/monochrome.md +0 -0
  23. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/nature-earth.md +0 -0
  24. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/neon-electric.md +0 -0
  25. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/pastel-soft.md +0 -0
  26. /package/dist/skills/{compose-video → hyperframes-compose}/palettes/warm-editorial.md +0 -0
  27. /package/dist/skills/{compose-video → hyperframes-compose}/patterns.md +0 -0
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.9" : "0.0.0-dev";
425
+ VERSION = true ? "0.1.11" : "0.0.0-dev";
426
426
  }
427
427
  });
428
428
 
@@ -599,6 +599,90 @@ var init_env = __esm({
599
599
  }
600
600
  });
601
601
 
602
+ // src/telemetry/system.ts
603
+ import { cpus, totalmem, platform, release } from "os";
604
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statfsSync } from "fs";
605
+ function bytesToMb(bytes) {
606
+ return Math.trunc(bytes / (1024 * 1024));
607
+ }
608
+ function getSystemMeta() {
609
+ if (cached) return cached;
610
+ const cpuInfo = cpus();
611
+ const firstCpu = cpuInfo[0] ?? null;
612
+ cached = {
613
+ os_release: release(),
614
+ cpu_count: cpuInfo.length,
615
+ cpu_model: firstCpu?.model?.trim() ?? null,
616
+ cpu_speed: firstCpu?.speed ?? null,
617
+ memory_total_mb: bytesToMb(totalmem()),
618
+ is_docker: detectDocker(),
619
+ is_ci: detectCI(),
620
+ ci_name: getCIName(),
621
+ is_wsl: detectWSL(),
622
+ is_tty: Boolean(process.stdout?.isTTY)
623
+ };
624
+ return cached;
625
+ }
626
+ function detectDocker() {
627
+ try {
628
+ if (existsSync2("/.dockerenv")) return true;
629
+ if (platform() === "linux") {
630
+ const cgroup = readFileSync2("/proc/1/cgroup", "utf-8");
631
+ if (cgroup.includes("docker") || cgroup.includes("containerd")) return true;
632
+ }
633
+ } catch {
634
+ }
635
+ return false;
636
+ }
637
+ function detectCI() {
638
+ return process.env["CI"] === "true" || process.env["CI"] === "1" || process.env["CONTINUOUS_INTEGRATION"] === "true" || process.env["GITHUB_ACTIONS"] === "true" || process.env["GITLAB_CI"] === "true" || process.env["CIRCLECI"] === "true" || process.env["JENKINS_URL"] != null || process.env["BUILDKITE"] === "true" || process.env["TRAVIS"] === "true" || false;
639
+ }
640
+ function getCIName() {
641
+ if (process.env["GITHUB_ACTIONS"] === "true") return "github_actions";
642
+ if (process.env["GITLAB_CI"] === "true") return "gitlab_ci";
643
+ if (process.env["CIRCLECI"] === "true") return "circleci";
644
+ if (process.env["JENKINS_URL"] != null) return "jenkins";
645
+ if (process.env["BUILDKITE"] === "true") return "buildkite";
646
+ if (process.env["TRAVIS"] === "true") return "travis";
647
+ if (detectCI()) return "unknown";
648
+ return null;
649
+ }
650
+ function detectWSL() {
651
+ if (platform() !== "linux") return false;
652
+ try {
653
+ const osRelease = release().toLowerCase();
654
+ if (osRelease.includes("microsoft") || osRelease.includes("wsl")) return true;
655
+ const procVersion = readFileSync2("/proc/version", "utf-8").toLowerCase();
656
+ return procVersion.includes("microsoft") || procVersion.includes("wsl");
657
+ } catch {
658
+ return false;
659
+ }
660
+ }
661
+ function getShmSizeMb() {
662
+ if (platform() !== "linux") return null;
663
+ try {
664
+ const stats = statfsSync("/dev/shm");
665
+ return bytesToMb(stats.bsize * stats.blocks);
666
+ } catch {
667
+ return null;
668
+ }
669
+ }
670
+ function getFreeDiskMb(path = ".") {
671
+ try {
672
+ const stats = statfsSync(path);
673
+ return bytesToMb(stats.bsize * stats.bavail);
674
+ } catch {
675
+ return null;
676
+ }
677
+ }
678
+ var cached;
679
+ var init_system = __esm({
680
+ "src/telemetry/system.ts"() {
681
+ "use strict";
682
+ cached = null;
683
+ }
684
+ });
685
+
602
686
  // src/telemetry/client.ts
603
687
  function shouldTrack() {
604
688
  if (telemetryEnabled !== null) return telemetryEnabled;
@@ -624,6 +708,7 @@ function shouldTrack() {
624
708
  }
625
709
  function trackEvent(event, properties = {}) {
626
710
  if (!shouldTrack()) return;
711
+ const sys = getSystemMeta();
627
712
  eventQueue.push({
628
713
  event,
629
714
  properties: {
@@ -631,7 +716,17 @@ function trackEvent(event, properties = {}) {
631
716
  cli_version: VERSION,
632
717
  os: process.platform,
633
718
  arch: process.arch,
634
- node_version: process.version
719
+ node_version: process.version,
720
+ os_release: sys.os_release,
721
+ cpu_count: sys.cpu_count,
722
+ cpu_model: sys.cpu_model ?? void 0,
723
+ cpu_speed: sys.cpu_speed ?? void 0,
724
+ memory_total_mb: sys.memory_total_mb,
725
+ is_docker: sys.is_docker,
726
+ is_ci: sys.is_ci,
727
+ ci_name: sys.ci_name ?? void 0,
728
+ is_wsl: sys.is_wsl,
729
+ is_tty: sys.is_tty
635
730
  },
636
731
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
637
732
  });
@@ -655,7 +750,7 @@ async function flush() {
655
750
  try {
656
751
  await fetch(`${POSTHOG_HOST}/batch/`, {
657
752
  method: "POST",
658
- headers: { "Content-Type": "application/json" },
753
+ headers: { "Content-Type": "application/json", Connection: "close" },
659
754
  body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),
660
755
  signal: controller.signal
661
756
  });
@@ -713,6 +808,7 @@ var init_client = __esm({
713
808
  init_version();
714
809
  init_colors();
715
810
  init_env();
811
+ init_system();
716
812
  POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
717
813
  POSTHOG_HOST = "https://us.i.posthog.com";
718
814
  FLUSH_TIMEOUT_MS = 5e3;
@@ -732,14 +828,30 @@ function trackRenderComplete(props) {
732
828
  quality: props.quality,
733
829
  workers: props.workers,
734
830
  docker: props.docker,
735
- gpu: props.gpu
831
+ gpu: props.gpu,
832
+ composition_duration_ms: props.compositionDurationMs,
833
+ composition_width: props.compositionWidth,
834
+ composition_height: props.compositionHeight,
835
+ total_frames: props.totalFrames,
836
+ speed_ratio: props.speedRatio,
837
+ capture_avg_ms: props.captureAvgMs,
838
+ capture_peak_ms: props.capturePeakMs,
839
+ peak_memory_mb: props.peakMemoryMb,
840
+ memory_free_mb: props.memoryFreeMb
736
841
  });
737
842
  }
738
843
  function trackRenderError(props) {
739
844
  trackEvent("render_error", {
740
845
  fps: props.fps,
741
846
  quality: props.quality,
742
- docker: props.docker
847
+ docker: props.docker,
848
+ workers: props.workers,
849
+ gpu: props.gpu,
850
+ failed_stage: props.failedStage,
851
+ error_message: props.errorMessage,
852
+ elapsed_ms: props.elapsedMs,
853
+ peak_memory_mb: props.peakMemoryMb,
854
+ memory_free_mb: props.memoryFreeMb
743
855
  });
744
856
  }
745
857
  function trackInitTemplate(templateId) {
@@ -772,7 +884,10 @@ async function checkForUpdate(force) {
772
884
  try {
773
885
  const controller = new AbortController();
774
886
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
775
- const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });
887
+ const res = await fetch(NPM_REGISTRY_URL, {
888
+ signal: controller.signal,
889
+ headers: { Connection: "close" }
890
+ });
776
891
  clearTimeout(timeout);
777
892
  if (!res.ok) return fallbackResult(config.latestVersion);
778
893
  const data = await res.json();
@@ -2536,7 +2651,15 @@ var init_generators = __esm({
2536
2651
  { id: "warm-grain", label: "Warm Grain", hint: "Cream aesthetic with grain texture" },
2537
2652
  { id: "play-mode", label: "Play Mode", hint: "Playful elastic animations" },
2538
2653
  { id: "swiss-grid", label: "Swiss Grid", hint: "Structured grid layout" },
2539
- { id: "vignelli", label: "Vignelli", hint: "Bold typography with red accents" }
2654
+ { id: "vignelli", label: "Vignelli", hint: "Bold typography with red accents" },
2655
+ { id: "decision-tree", label: "Decision Tree", hint: "Animated flowchart with branching paths" },
2656
+ { id: "kinetic-type", label: "Kinetic Type", hint: "Bold kinetic typography promo" },
2657
+ {
2658
+ id: "product-promo",
2659
+ label: "Product Promo",
2660
+ hint: "Multi-scene product showcase with SVG assets"
2661
+ },
2662
+ { id: "nyt-graph", label: "NYT Graph", hint: "Animated data chart in print editorial style" }
2540
2663
  ];
2541
2664
  }
2542
2665
  });
@@ -2553,13 +2676,13 @@ __export(manager_exports, {
2553
2676
  hasFFmpeg: () => hasFFmpeg
2554
2677
  });
2555
2678
  import { execFileSync } from "child_process";
2556
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, createWriteStream, rmSync } from "fs";
2557
- import { homedir as homedir2, platform } from "os";
2679
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, createWriteStream, rmSync } from "fs";
2680
+ import { homedir as homedir2, platform as platform2 } from "os";
2558
2681
  import { join as join2 } from "path";
2559
2682
  import { get as httpsGet } from "https";
2560
2683
  import { pipeline } from "stream/promises";
2561
2684
  function downloadFile(url, dest) {
2562
- return new Promise((resolve17, reject) => {
2685
+ return new Promise((resolve20, reject) => {
2563
2686
  const follow = (u) => {
2564
2687
  httpsGet(u, (res) => {
2565
2688
  if (res.statusCode === 301 || res.statusCode === 302) {
@@ -2574,7 +2697,7 @@ function downloadFile(url, dest) {
2574
2697
  return;
2575
2698
  }
2576
2699
  const file = createWriteStream(dest);
2577
- pipeline(res, file).then(resolve17).catch(reject);
2700
+ pipeline(res, file).then(resolve20).catch(reject);
2578
2701
  }).on("error", reject);
2579
2702
  };
2580
2703
  follow(url);
@@ -2597,7 +2720,7 @@ function whichBinary(name) {
2597
2720
  }
2598
2721
  function findFromEnv() {
2599
2722
  const envPath = process.env["HYPERFRAMES_WHISPER_PATH"];
2600
- if (envPath && existsSync2(envPath)) {
2723
+ if (envPath && existsSync3(envPath)) {
2601
2724
  return { executablePath: envPath, source: "env" };
2602
2725
  }
2603
2726
  return void 0;
@@ -2607,9 +2730,9 @@ function findFromSystem() {
2607
2730
  const path = whichBinary(name);
2608
2731
  if (path) return { executablePath: path, source: "system" };
2609
2732
  }
2610
- if (platform() === "darwin") {
2733
+ if (platform2() === "darwin") {
2611
2734
  for (const p of ["/opt/homebrew/bin/whisper-cli", "/usr/local/bin/whisper-cli"]) {
2612
- if (existsSync2(p)) return { executablePath: p, source: "system" };
2735
+ if (existsSync3(p)) return { executablePath: p, source: "system" };
2613
2736
  }
2614
2737
  }
2615
2738
  return void 0;
@@ -2619,15 +2742,15 @@ function findBuiltBinary() {
2619
2742
  join2(BUILD_DIR, "build", "bin", "whisper-cli"),
2620
2743
  join2(BUILD_DIR, "build", "whisper-cli")
2621
2744
  ]) {
2622
- if (existsSync2(p)) return { executablePath: p, source: "build" };
2745
+ if (existsSync3(p)) return { executablePath: p, source: "build" };
2623
2746
  }
2624
2747
  return void 0;
2625
2748
  }
2626
2749
  function buildFromSource(onProgress) {
2627
- if (existsSync2(BUILD_DIR) && !findBuiltBinary()) {
2750
+ if (existsSync3(BUILD_DIR) && !findBuiltBinary()) {
2628
2751
  rmSync(BUILD_DIR, { recursive: true, force: true });
2629
2752
  }
2630
- if (!existsSync2(BUILD_DIR)) {
2753
+ if (!existsSync3(BUILD_DIR)) {
2631
2754
  onProgress?.("Downloading whisper.cpp...");
2632
2755
  mkdirSync2(join2(homedir2(), ".cache", "hyperframes", "whisper"), {
2633
2756
  recursive: true
@@ -2670,7 +2793,7 @@ function findWhisper() {
2670
2793
  return findFromEnv() ?? findFromSystem() ?? findBuiltBinary();
2671
2794
  }
2672
2795
  function getInstallInstructions() {
2673
- if (platform() === "darwin") {
2796
+ if (platform2() === "darwin") {
2674
2797
  return "brew install whisper-cpp";
2675
2798
  }
2676
2799
  return "See https://github.com/ggml-org/whisper.cpp#building";
@@ -2687,7 +2810,7 @@ function hasCmake() {
2687
2810
  async function ensureWhisper(options) {
2688
2811
  const existing = findWhisper();
2689
2812
  if (existing) return existing;
2690
- if (platform() === "darwin" && hasBrew()) {
2813
+ if (platform2() === "darwin" && hasBrew()) {
2691
2814
  options?.onProgress?.("Installing whisper-cpp via Homebrew...");
2692
2815
  try {
2693
2816
  execFileSync("brew", ["install", "whisper-cpp"], {
@@ -2709,11 +2832,11 @@ async function ensureWhisper(options) {
2709
2832
  }
2710
2833
  async function ensureModel(model = DEFAULT_MODEL, options) {
2711
2834
  const modelPath = join2(MODELS_DIR, `ggml-${model}.bin`);
2712
- if (existsSync2(modelPath)) return modelPath;
2835
+ if (existsSync3(modelPath)) return modelPath;
2713
2836
  mkdirSync2(MODELS_DIR, { recursive: true });
2714
2837
  options?.onProgress?.(`Downloading model ${model}...`);
2715
2838
  await downloadFile(getModelUrl(model), modelPath);
2716
- if (!existsSync2(modelPath)) {
2839
+ if (!existsSync3(modelPath)) {
2717
2840
  throw new Error(`Model download failed: ${model}`);
2718
2841
  }
2719
2842
  return modelPath;
@@ -2744,7 +2867,7 @@ __export(install_skills_exports, {
2744
2867
  default: () => install_skills_default,
2745
2868
  installAllSkills: () => installAllSkills
2746
2869
  });
2747
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, rmSync as rmSync2, cpSync } from "fs";
2870
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, rmSync as rmSync2, cpSync } from "fs";
2748
2871
  import { join as join3, dirname } from "path";
2749
2872
  import { homedir as homedir3 } from "os";
2750
2873
  import { execFileSync as execFileSync2 } from "child_process";
@@ -2784,7 +2907,7 @@ function gitClone(repo, dest) {
2784
2907
  }
2785
2908
  function fetchRepo(source) {
2786
2909
  const gitUrl = `https://github.com/${source.repo}.git`;
2787
- if (existsSync3(source.cache)) {
2910
+ if (existsSync4(source.cache)) {
2788
2911
  try {
2789
2912
  execFileSync2("git", ["pull", "--ff-only"], {
2790
2913
  cwd: source.cache,
@@ -2794,7 +2917,7 @@ function fetchRepo(source) {
2794
2917
  });
2795
2918
  } catch {
2796
2919
  const skillsDir2 = join3(source.cache, source.skillsPath);
2797
- if (existsSync3(skillsDir2)) return skillsDir2;
2920
+ if (existsSync4(skillsDir2)) return skillsDir2;
2798
2921
  rmSync2(source.cache, { recursive: true, force: true });
2799
2922
  gitClone(gitUrl, source.cache);
2800
2923
  }
@@ -2803,18 +2926,18 @@ function fetchRepo(source) {
2803
2926
  gitClone(gitUrl, source.cache);
2804
2927
  }
2805
2928
  const skillsDir = join3(source.cache, source.skillsPath);
2806
- return existsSync3(skillsDir) ? skillsDir : void 0;
2929
+ return existsSync4(skillsDir) ? skillsDir : void 0;
2807
2930
  }
2808
2931
  function installSkillsFromDir(sourceDir, targetDir, sourceName) {
2809
2932
  const installed = [];
2810
- if (!existsSync3(sourceDir)) return installed;
2933
+ if (!existsSync4(sourceDir)) return installed;
2811
2934
  const entries2 = readdirSync(sourceDir, { withFileTypes: true });
2812
2935
  for (const entry of entries2) {
2813
2936
  if (!entry.isDirectory()) continue;
2814
2937
  const skillFile = join3(sourceDir, entry.name, "SKILL.md");
2815
- if (!existsSync3(skillFile)) continue;
2938
+ if (!existsSync4(skillFile)) continue;
2816
2939
  const destDir = join3(targetDir, entry.name);
2817
- if (existsSync3(destDir)) rmSync2(destDir, { recursive: true, force: true });
2940
+ if (existsSync4(destDir)) rmSync2(destDir, { recursive: true, force: true });
2818
2941
  mkdirSync3(destDir, { recursive: true });
2819
2942
  cpSync(join3(sourceDir, entry.name), destDir, { recursive: true });
2820
2943
  installed.push({ name: entry.name, source: sourceName });
@@ -3063,6 +3186,553 @@ var init_fileWatcher = __esm({
3063
3186
  }
3064
3187
  });
3065
3188
 
3189
+ // ../core/src/studio-api/helpers/safePath.ts
3190
+ import { resolve, sep, join as join4 } from "path";
3191
+ import { readdirSync as readdirSync2 } from "fs";
3192
+ function isSafePath(base, resolved) {
3193
+ const norm = resolve(base) + sep;
3194
+ return resolved.startsWith(norm) || resolved === resolve(base);
3195
+ }
3196
+ function walkDir(dir, prefix = "") {
3197
+ const files = [];
3198
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
3199
+ if (IGNORE_DIRS.has(entry.name)) continue;
3200
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
3201
+ if (entry.isDirectory()) {
3202
+ files.push(...walkDir(join4(dir, entry.name), rel));
3203
+ } else {
3204
+ files.push(rel);
3205
+ }
3206
+ }
3207
+ return files;
3208
+ }
3209
+ var IGNORE_DIRS;
3210
+ var init_safePath = __esm({
3211
+ "../core/src/studio-api/helpers/safePath.ts"() {
3212
+ "use strict";
3213
+ IGNORE_DIRS = /* @__PURE__ */ new Set([".thumbnails", "node_modules", ".git"]);
3214
+ }
3215
+ });
3216
+
3217
+ // ../core/src/studio-api/routes/projects.ts
3218
+ function registerProjectRoutes(api, adapter2) {
3219
+ api.get("/projects", async (c2) => {
3220
+ const projects = await adapter2.listProjects();
3221
+ return c2.json({ projects });
3222
+ });
3223
+ api.get("/resolve-session/:sessionId", async (c2) => {
3224
+ if (!adapter2.resolveSession) {
3225
+ return c2.json({ error: "not available" }, 404);
3226
+ }
3227
+ const { sessionId } = c2.req.param();
3228
+ const result = await adapter2.resolveSession(sessionId);
3229
+ if (!result) return c2.json({ error: "Session not found" }, 404);
3230
+ return c2.json(result);
3231
+ });
3232
+ api.get("/projects/:id", async (c2) => {
3233
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3234
+ if (!project) return c2.json({ error: "not found" }, 404);
3235
+ const files = walkDir(project.dir);
3236
+ return c2.json({ id: project.id, files });
3237
+ });
3238
+ }
3239
+ var init_projects = __esm({
3240
+ "../core/src/studio-api/routes/projects.ts"() {
3241
+ "use strict";
3242
+ init_safePath();
3243
+ }
3244
+ });
3245
+
3246
+ // ../core/src/studio-api/routes/files.ts
3247
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
3248
+ import { resolve as resolve2, dirname as dirname2 } from "path";
3249
+ function registerFileRoutes(api, adapter2) {
3250
+ api.get("/projects/:id/files/*", async (c2) => {
3251
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3252
+ if (!project) return c2.json({ error: "not found" }, 404);
3253
+ const filePath = decodeURIComponent(c2.req.path.replace(`/projects/${project.id}/files/`, ""));
3254
+ const file = resolve2(project.dir, filePath);
3255
+ if (!isSafePath(project.dir, file) || !existsSync5(file)) {
3256
+ return c2.text("not found", 404);
3257
+ }
3258
+ const content = readFileSync3(file, "utf-8");
3259
+ return c2.json({ filename: filePath, content });
3260
+ });
3261
+ api.put("/projects/:id/files/*", async (c2) => {
3262
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3263
+ if (!project) return c2.json({ error: "not found" }, 404);
3264
+ const filePath = decodeURIComponent(c2.req.path.replace(`/projects/${project.id}/files/`, ""));
3265
+ const file = resolve2(project.dir, filePath);
3266
+ if (!isSafePath(project.dir, file)) {
3267
+ return c2.json({ error: "forbidden" }, 403);
3268
+ }
3269
+ const dir = dirname2(file);
3270
+ if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
3271
+ const body = await c2.req.text();
3272
+ writeFileSync2(file, body, "utf-8");
3273
+ return c2.json({ ok: true });
3274
+ });
3275
+ }
3276
+ var init_files = __esm({
3277
+ "../core/src/studio-api/routes/files.ts"() {
3278
+ "use strict";
3279
+ init_safePath();
3280
+ }
3281
+ });
3282
+
3283
+ // ../core/src/studio-api/helpers/mime.ts
3284
+ function getMimeType(path) {
3285
+ const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
3286
+ return MIME_TYPES[ext] || "application/octet-stream";
3287
+ }
3288
+ var MIME_TYPES;
3289
+ var init_mime = __esm({
3290
+ "../core/src/studio-api/helpers/mime.ts"() {
3291
+ "use strict";
3292
+ MIME_TYPES = {
3293
+ ".html": "text/html",
3294
+ ".css": "text/css",
3295
+ ".js": "text/javascript",
3296
+ ".mjs": "text/javascript",
3297
+ ".json": "application/json",
3298
+ ".svg": "image/svg+xml",
3299
+ ".png": "image/png",
3300
+ ".jpg": "image/jpeg",
3301
+ ".jpeg": "image/jpeg",
3302
+ ".gif": "image/gif",
3303
+ ".webp": "image/webp",
3304
+ ".ico": "image/x-icon",
3305
+ ".mp4": "video/mp4",
3306
+ ".webm": "video/webm",
3307
+ ".mp3": "audio/mpeg",
3308
+ ".wav": "audio/wav",
3309
+ ".ogg": "audio/ogg",
3310
+ ".m4a": "audio/mp4",
3311
+ ".woff": "font/woff",
3312
+ ".woff2": "font/woff2",
3313
+ ".ttf": "font/ttf",
3314
+ ".otf": "font/otf",
3315
+ ".txt": "text/plain",
3316
+ ".md": "text/markdown"
3317
+ };
3318
+ }
3319
+ });
3320
+
3321
+ // ../core/src/studio-api/helpers/subComposition.ts
3322
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
3323
+ import { join as join5 } from "path";
3324
+ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref) {
3325
+ const compFile = join5(projectDir, compPath);
3326
+ if (!existsSync6(compFile)) return null;
3327
+ const rawComp = readFileSync4(compFile, "utf-8");
3328
+ const templateMatch = rawComp.match(/<template[^>]*>([\s\S]*)<\/template>/i);
3329
+ const content = templateMatch?.[1] ?? rawComp;
3330
+ const indexPath = join5(projectDir, "index.html");
3331
+ let headContent = "";
3332
+ if (existsSync6(indexPath)) {
3333
+ const indexHtml = readFileSync4(indexPath, "utf-8");
3334
+ const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
3335
+ headContent = headMatch?.[1] ?? "";
3336
+ }
3337
+ if (baseHref && !headContent.includes("<base")) {
3338
+ headContent = `<base href="${baseHref}">
3339
+ ${headContent}`;
3340
+ }
3341
+ if (!headContent.includes("hyperframe.runtime") && !headContent.includes("hyperframes-preview-runtime")) {
3342
+ headContent += `
3343
+ <script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>`;
3344
+ }
3345
+ if (!headContent.includes("gsap")) {
3346
+ headContent += `
3347
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>`;
3348
+ }
3349
+ return `<!DOCTYPE html>
3350
+ <html>
3351
+ <head>
3352
+ ${headContent}
3353
+ </head>
3354
+ <body>
3355
+ <script>window.__timelines=window.__timelines||{};</script>
3356
+ ${content}
3357
+ </body>
3358
+ </html>`;
3359
+ }
3360
+ var init_subComposition = __esm({
3361
+ "../core/src/studio-api/helpers/subComposition.ts"() {
3362
+ "use strict";
3363
+ }
3364
+ });
3365
+
3366
+ // ../core/src/studio-api/routes/preview.ts
3367
+ import { existsSync as existsSync7, readFileSync as readFileSync5, statSync } from "fs";
3368
+ import { resolve as resolve3 } from "path";
3369
+ function registerPreviewRoutes(api, adapter2) {
3370
+ api.get("/projects/:id/preview", async (c2) => {
3371
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3372
+ if (!project) return c2.json({ error: "not found" }, 404);
3373
+ try {
3374
+ let bundled = await adapter2.bundle(project.dir);
3375
+ if (!bundled) {
3376
+ const indexPath = resolve3(project.dir, "index.html");
3377
+ if (!existsSync7(indexPath)) return c2.text("not found", 404);
3378
+ bundled = readFileSync5(indexPath, "utf-8");
3379
+ }
3380
+ if (!bundled.includes("hyperframe.runtime") && !bundled.includes("hyperframes-preview-runtime")) {
3381
+ const runtimeTag = `<script src="${adapter2.runtimeUrl}"></script>`;
3382
+ bundled = bundled.includes("</body>") ? bundled.replace("</body>", `${runtimeTag}
3383
+ </body>`) : bundled + `
3384
+ ${runtimeTag}`;
3385
+ }
3386
+ const baseHref = `/api/projects/${project.id}/preview/`;
3387
+ if (!bundled.includes("<base")) {
3388
+ bundled = bundled.replace(/<head>/i, `<head><base href="${baseHref}">`);
3389
+ }
3390
+ return c2.html(bundled);
3391
+ } catch {
3392
+ const file = resolve3(project.dir, "index.html");
3393
+ if (existsSync7(file)) return c2.html(readFileSync5(file, "utf-8"));
3394
+ return c2.text("not found", 404);
3395
+ }
3396
+ });
3397
+ api.get("/projects/:id/preview/comp/*", async (c2) => {
3398
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3399
+ if (!project) return c2.json({ error: "not found" }, 404);
3400
+ const compPath = decodeURIComponent(
3401
+ c2.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? ""
3402
+ );
3403
+ const compFile = resolve3(project.dir, compPath);
3404
+ if (!isSafePath(project.dir, compFile) || !existsSync7(compFile) || !statSync(compFile).isFile()) {
3405
+ return c2.text("not found", 404);
3406
+ }
3407
+ const baseHref = `/api/projects/${project.id}/preview/`;
3408
+ const html = buildSubCompositionHtml(project.dir, compPath, adapter2.runtimeUrl, baseHref);
3409
+ if (!html) return c2.text("not found", 404);
3410
+ return c2.html(html);
3411
+ });
3412
+ api.get("/projects/:id/preview/*", async (c2) => {
3413
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3414
+ if (!project) return c2.json({ error: "not found" }, 404);
3415
+ const subPath = decodeURIComponent(
3416
+ c2.req.path.replace(`/projects/${project.id}/preview/`, "").split("?")[0] ?? ""
3417
+ );
3418
+ const file = resolve3(project.dir, subPath);
3419
+ if (!isSafePath(project.dir, file) || !existsSync7(file) || !statSync(file).isFile()) {
3420
+ return c2.text("not found", 404);
3421
+ }
3422
+ const contentType = getMimeType(subPath);
3423
+ const isText2 = /\.(html|css|js|json|svg|txt|md)$/i.test(subPath);
3424
+ const content = readFileSync5(file, isText2 ? "utf-8" : void 0);
3425
+ return new Response(content, {
3426
+ headers: { "Content-Type": contentType }
3427
+ });
3428
+ });
3429
+ }
3430
+ var init_preview = __esm({
3431
+ "../core/src/studio-api/routes/preview.ts"() {
3432
+ "use strict";
3433
+ init_safePath();
3434
+ init_mime();
3435
+ init_subComposition();
3436
+ }
3437
+ });
3438
+
3439
+ // ../core/src/studio-api/routes/lint.ts
3440
+ import { readFileSync as readFileSync6 } from "fs";
3441
+ import { join as join6 } from "path";
3442
+ function registerLintRoutes(api, adapter2) {
3443
+ api.get("/projects/:id/lint", async (c2) => {
3444
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3445
+ if (!project) return c2.json({ error: "not found" }, 404);
3446
+ try {
3447
+ const htmlFiles = walkDir(project.dir).filter((f) => f.endsWith(".html"));
3448
+ const allFindings = [];
3449
+ for (const file of htmlFiles) {
3450
+ const content = readFileSync6(join6(project.dir, file), "utf-8");
3451
+ const result = await adapter2.lint(content, { filePath: file });
3452
+ if (result?.findings) {
3453
+ for (const f of result.findings) {
3454
+ allFindings.push({ ...f, file });
3455
+ }
3456
+ }
3457
+ }
3458
+ return c2.json({ findings: allFindings });
3459
+ } catch (err) {
3460
+ const msg = err instanceof Error ? err.message : String(err);
3461
+ return c2.json({ error: `Lint failed: ${msg}` }, 500);
3462
+ }
3463
+ });
3464
+ }
3465
+ var init_lint = __esm({
3466
+ "../core/src/studio-api/routes/lint.ts"() {
3467
+ "use strict";
3468
+ init_safePath();
3469
+ }
3470
+ });
3471
+
3472
+ // ../core/src/studio-api/routes/render.ts
3473
+ import { streamSSE } from "hono/streaming";
3474
+ import { existsSync as existsSync8, readFileSync as readFileSync7, mkdirSync as mkdirSync5, unlinkSync, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
3475
+ import { join as join7 } from "path";
3476
+ function registerRenderRoutes(api, adapter2) {
3477
+ const renderJobs = /* @__PURE__ */ new Map();
3478
+ const TTL_MS = 3e5;
3479
+ const CLEANUP_INTERVAL_MS = 6e4;
3480
+ let cleanupTimer = null;
3481
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !process.argv.includes("build")) {
3482
+ cleanupTimer = setInterval(() => {
3483
+ const now = Date.now();
3484
+ for (const [key2, job] of renderJobs) {
3485
+ if ((job.status === "complete" || job.status === "failed") && now - job.createdAt > TTL_MS) {
3486
+ renderJobs.delete(key2);
3487
+ }
3488
+ }
3489
+ if (renderJobs.size === 0 && cleanupTimer) {
3490
+ clearInterval(cleanupTimer);
3491
+ cleanupTimer = null;
3492
+ }
3493
+ }, CLEANUP_INTERVAL_MS);
3494
+ if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
3495
+ cleanupTimer.unref();
3496
+ }
3497
+ }
3498
+ api.post("/projects/:id/render", async (c2) => {
3499
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3500
+ if (!project) return c2.json({ error: "not found" }, 404);
3501
+ const body = await c2.req.json().catch(() => ({}));
3502
+ const format = body.format === "webm" ? "webm" : "mp4";
3503
+ const fps = body.fps === 24 || body.fps === 60 ? body.fps : 30;
3504
+ const quality = ["draft", "standard", "high"].includes(body.quality ?? "") ? body.quality : "standard";
3505
+ const now = /* @__PURE__ */ new Date();
3506
+ const datePart = now.toISOString().slice(0, 10);
3507
+ const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
3508
+ const jobId = `${project.id}_${datePart}_${timePart}`;
3509
+ const rendersDir = adapter2.rendersDir(project);
3510
+ if (!existsSync8(rendersDir)) mkdirSync5(rendersDir, { recursive: true });
3511
+ const ext = format === "webm" ? ".webm" : ".mp4";
3512
+ const outputPath = join7(rendersDir, `${jobId}${ext}`);
3513
+ const jobState = adapter2.startRender({
3514
+ project,
3515
+ outputPath,
3516
+ format,
3517
+ fps,
3518
+ quality,
3519
+ jobId
3520
+ });
3521
+ renderJobs.set(jobId, { ...jobState, createdAt: Date.now() });
3522
+ if (!cleanupTimer && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
3523
+ cleanupTimer = setInterval(() => {
3524
+ const now2 = Date.now();
3525
+ for (const [key2, job] of renderJobs) {
3526
+ if ((job.status === "complete" || job.status === "failed") && now2 - job.createdAt > TTL_MS) {
3527
+ renderJobs.delete(key2);
3528
+ }
3529
+ }
3530
+ if (renderJobs.size === 0 && cleanupTimer) {
3531
+ clearInterval(cleanupTimer);
3532
+ cleanupTimer = null;
3533
+ }
3534
+ }, CLEANUP_INTERVAL_MS);
3535
+ if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
3536
+ cleanupTimer.unref();
3537
+ }
3538
+ }
3539
+ return c2.json({ jobId, status: "rendering" });
3540
+ });
3541
+ api.get("/render/:jobId/progress", (c2) => {
3542
+ const { jobId } = c2.req.param();
3543
+ const job = renderJobs.get(jobId);
3544
+ if (!job) return c2.json({ error: "not found" }, 404);
3545
+ return streamSSE(c2, async (stream) => {
3546
+ while (true) {
3547
+ const current = renderJobs.get(jobId);
3548
+ if (!current) break;
3549
+ await stream.writeSSE({
3550
+ event: "progress",
3551
+ data: JSON.stringify({
3552
+ progress: current.progress,
3553
+ status: current.status,
3554
+ stage: current.stage,
3555
+ error: current.error
3556
+ })
3557
+ });
3558
+ if (current.status === "complete" || current.status === "failed") break;
3559
+ await stream.sleep(500);
3560
+ }
3561
+ });
3562
+ });
3563
+ api.get("/render/:jobId/download", (c2) => {
3564
+ const { jobId } = c2.req.param();
3565
+ const job = renderJobs.get(jobId);
3566
+ if (!job?.outputPath || !existsSync8(job.outputPath)) {
3567
+ return c2.json({ error: "not found" }, 404);
3568
+ }
3569
+ const isWebm = job.outputPath.endsWith(".webm");
3570
+ const contentType = isWebm ? "video/webm" : "video/mp4";
3571
+ const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
3572
+ const content = readFileSync7(job.outputPath);
3573
+ return new Response(content, {
3574
+ headers: {
3575
+ "Content-Type": contentType,
3576
+ "Content-Disposition": `attachment; filename="${filename}"`
3577
+ }
3578
+ });
3579
+ });
3580
+ api.delete("/render/:jobId", (c2) => {
3581
+ const { jobId } = c2.req.param();
3582
+ for (const [, state] of renderJobs) {
3583
+ if (state.id === jobId && state.outputPath) {
3584
+ const dir = state.outputPath.replace(/\/[^/]+$/, "");
3585
+ for (const ext of [".mp4", ".webm", ".meta.json"]) {
3586
+ const fp = join7(dir, `${jobId}${ext}`);
3587
+ if (existsSync8(fp)) unlinkSync(fp);
3588
+ }
3589
+ break;
3590
+ }
3591
+ }
3592
+ renderJobs.delete(jobId);
3593
+ return c2.json({ deleted: true });
3594
+ });
3595
+ api.get("/projects/:id/renders", async (c2) => {
3596
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3597
+ if (!project) return c2.json({ error: "not found" }, 404);
3598
+ const rendersDir = adapter2.rendersDir(project);
3599
+ if (!existsSync8(rendersDir)) return c2.json({ renders: [] });
3600
+ const files = readdirSync3(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm")).map((f) => {
3601
+ const fp = join7(rendersDir, f);
3602
+ const stat = statSync2(fp);
3603
+ const rid = f.replace(/\.(mp4|webm)$/, "");
3604
+ const metaPath = join7(rendersDir, `${rid}.meta.json`);
3605
+ let status = "complete";
3606
+ let durationMs;
3607
+ if (existsSync8(metaPath)) {
3608
+ try {
3609
+ const meta = JSON.parse(readFileSync7(metaPath, "utf-8"));
3610
+ if (meta.status === "failed") status = "failed";
3611
+ if (meta.durationMs) durationMs = meta.durationMs;
3612
+ } catch {
3613
+ }
3614
+ }
3615
+ return {
3616
+ id: rid,
3617
+ filename: f,
3618
+ size: stat.size,
3619
+ createdAt: stat.mtimeMs,
3620
+ status,
3621
+ durationMs
3622
+ };
3623
+ }).sort((a, b) => b.createdAt - a.createdAt);
3624
+ return c2.json({ renders: files });
3625
+ });
3626
+ }
3627
+ var init_render = __esm({
3628
+ "../core/src/studio-api/routes/render.ts"() {
3629
+ "use strict";
3630
+ }
3631
+ });
3632
+
3633
+ // ../core/src/studio-api/routes/thumbnail.ts
3634
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
3635
+ import { join as join8 } from "path";
3636
+ function registerThumbnailRoutes(api, adapter2) {
3637
+ api.get("/projects/:id/thumbnail/*", async (c2) => {
3638
+ if (!adapter2.generateThumbnail) {
3639
+ return c2.json({ error: "Thumbnails not available" }, 501);
3640
+ }
3641
+ const project = await adapter2.resolveProject(c2.req.param("id"));
3642
+ if (!project) return c2.json({ error: "not found" }, 404);
3643
+ let compPath = decodeURIComponent(
3644
+ c2.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
3645
+ );
3646
+ if (compPath && !compPath.includes(".")) compPath += ".html";
3647
+ const url = new URL(c2.req.url, `http://${c2.req.header("host") || "localhost"}`);
3648
+ const seekTime = parseFloat(url.searchParams.get("t") || "0.5") || 0.5;
3649
+ const vpWidth = parseInt(url.searchParams.get("w") || "0") || 0;
3650
+ const vpHeight = parseInt(url.searchParams.get("h") || "0") || 0;
3651
+ let compW = vpWidth || 1920;
3652
+ let compH = vpHeight || 1080;
3653
+ if (!vpWidth) {
3654
+ const htmlFile = join8(project.dir, compPath);
3655
+ if (existsSync9(htmlFile)) {
3656
+ const html = readFileSync8(htmlFile, "utf-8");
3657
+ const wMatch = html.match(/data-width=["'](\d+)["']/);
3658
+ const hMatch = html.match(/data-height=["'](\d+)["']/);
3659
+ if (wMatch?.[1]) compW = parseInt(wMatch[1]);
3660
+ if (hMatch?.[1]) compH = parseInt(hMatch[1]);
3661
+ }
3662
+ }
3663
+ 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}`;
3664
+ const cacheDir = join8(project.dir, ".thumbnails");
3665
+ const cacheKey = `${compPath.replace(/\//g, "_")}_${seekTime.toFixed(2)}.jpg`;
3666
+ const cachePath = join8(cacheDir, cacheKey);
3667
+ if (existsSync9(cachePath)) {
3668
+ return new Response(new Uint8Array(readFileSync8(cachePath)), {
3669
+ headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
3670
+ });
3671
+ }
3672
+ try {
3673
+ const buffer = await adapter2.generateThumbnail({
3674
+ project,
3675
+ compPath,
3676
+ seekTime,
3677
+ width: compW,
3678
+ height: compH,
3679
+ previewUrl
3680
+ });
3681
+ if (!buffer) {
3682
+ return c2.json({ error: "Thumbnail generation returned null" }, 500);
3683
+ }
3684
+ if (!existsSync9(cacheDir)) mkdirSync6(cacheDir, { recursive: true });
3685
+ writeFileSync3(cachePath, buffer);
3686
+ return new Response(new Uint8Array(buffer), {
3687
+ headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
3688
+ });
3689
+ } catch (err) {
3690
+ const msg = err instanceof Error ? err.message : String(err);
3691
+ return c2.json({ error: `Thumbnail generation failed: ${msg}` }, 500);
3692
+ }
3693
+ });
3694
+ }
3695
+ var init_thumbnail = __esm({
3696
+ "../core/src/studio-api/routes/thumbnail.ts"() {
3697
+ "use strict";
3698
+ }
3699
+ });
3700
+
3701
+ // ../core/src/studio-api/createStudioApi.ts
3702
+ import { Hono } from "hono";
3703
+ function createStudioApi(adapter2) {
3704
+ const api = new Hono();
3705
+ registerProjectRoutes(api, adapter2);
3706
+ registerFileRoutes(api, adapter2);
3707
+ registerPreviewRoutes(api, adapter2);
3708
+ registerLintRoutes(api, adapter2);
3709
+ registerRenderRoutes(api, adapter2);
3710
+ registerThumbnailRoutes(api, adapter2);
3711
+ return api;
3712
+ }
3713
+ var init_createStudioApi = __esm({
3714
+ "../core/src/studio-api/createStudioApi.ts"() {
3715
+ "use strict";
3716
+ init_projects();
3717
+ init_files();
3718
+ init_preview();
3719
+ init_lint();
3720
+ init_render();
3721
+ init_thumbnail();
3722
+ }
3723
+ });
3724
+
3725
+ // ../core/src/studio-api/index.ts
3726
+ var init_studio_api = __esm({
3727
+ "../core/src/studio-api/index.ts"() {
3728
+ "use strict";
3729
+ init_createStudioApi();
3730
+ init_safePath();
3731
+ init_mime();
3732
+ init_subComposition();
3733
+ }
3734
+ });
3735
+
3066
3736
  // ../core/src/compiler/timingCompiler.ts
3067
3737
  function getAttr(tag, attr) {
3068
3738
  const match = tag.match(new RegExp(`${attr}=["']([^"']+)["']`));
@@ -3199,9 +3869,9 @@ var init_timingCompiler = __esm({
3199
3869
  });
3200
3870
 
3201
3871
  // ../core/src/compiler/htmlCompiler.ts
3202
- import { resolve } from "path";
3872
+ import { resolve as resolve4 } from "path";
3203
3873
  function resolveMediaSrc(src, projectDir) {
3204
- return src.startsWith("http://") || src.startsWith("https://") ? src : resolve(projectDir, src);
3874
+ return src.startsWith("http://") || src.startsWith("https://") ? src : resolve4(projectDir, src);
3205
3875
  }
3206
3876
  async function compileHtml(rawHtml, projectDir, probeMediaDuration) {
3207
3877
  const { html: staticCompiled, unresolved } = compileTimingAttrs(rawHtml);
@@ -3447,7 +4117,9 @@ var init_gsapParser = __esm({
3447
4117
 
3448
4118
  // ../core/src/lint/hyperframeLinter.ts
3449
4119
  function lintHyperframeHtml(html, options = {}) {
3450
- const source = html || "";
4120
+ let source = html || "";
4121
+ const templateMatch = source.match(/<template[^>]*>([\s\S]*)<\/template>/i);
4122
+ if (templateMatch?.[1]) source = templateMatch[1];
3451
4123
  const filePath = options.filePath;
3452
4124
  const findings = [];
3453
4125
  const seen = /* @__PURE__ */ new Set();
@@ -3804,18 +4476,20 @@ ${right.raw}`)
3804
4476
  for (const tag of tags) {
3805
4477
  if (tag.name === "audio" || tag.name === "script" || tag.name === "style") continue;
3806
4478
  if (!readAttr(tag.raw, "data-start")) continue;
4479
+ if (readAttr(tag.raw, "data-composition-id")) continue;
4480
+ if (readAttr(tag.raw, "data-composition-src")) continue;
3807
4481
  const classAttr = readAttr(tag.raw, "class") || "";
3808
4482
  const styleAttr = readAttr(tag.raw, "style") || "";
3809
4483
  const hasClip = classAttr.split(/\s+/).includes("clip");
3810
- const hasHiddenStyle = /visibility\s*:\s*hidden/i.test(styleAttr);
4484
+ const hasHiddenStyle = /visibility\s*:\s*hidden/i.test(styleAttr) || /opacity\s*:\s*0/i.test(styleAttr);
3811
4485
  if (!hasClip && !hasHiddenStyle) {
3812
4486
  const elementId = readAttr(tag.raw, "id") || void 0;
3813
4487
  pushFinding({
3814
4488
  code: "timed_element_missing_visibility_hidden",
3815
- severity: "warning",
3816
- message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has data-start but no class="clip" or visibility:hidden. The framework needs elements to start hidden so it can manage their lifecycle.`,
4489
+ severity: "info",
4490
+ message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has data-start but no class="clip", visibility:hidden, or opacity:0. Consider adding initial hidden state if the element should not be visible before its start time.`,
3817
4491
  elementId,
3818
- fixHint: 'Add class="clip" to the element (with CSS: .clip { visibility: hidden; }).',
4492
+ fixHint: 'Add class="clip" (with CSS: .clip { visibility: hidden; }) or style="opacity:0" if the element should start hidden.',
3819
4493
  snippet: truncateSnippet(tag.raw)
3820
4494
  });
3821
4495
  }
@@ -3844,6 +4518,76 @@ ${right.raw}`)
3844
4518
  });
3845
4519
  }
3846
4520
  }
4521
+ for (const script of scripts) {
4522
+ const templateLiteralSelectorPattern = /(?:querySelector|querySelectorAll)\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`\s*\)/g;
4523
+ let tlMatch;
4524
+ while ((tlMatch = templateLiteralSelectorPattern.exec(script.content)) !== null) {
4525
+ pushFinding({
4526
+ code: "template_literal_selector",
4527
+ severity: "error",
4528
+ message: "querySelector uses a template literal variable (e.g. `${compId}`). The HTML bundler's CSS parser crashes on these. Use a hardcoded string instead.",
4529
+ file: filePath,
4530
+ fixHint: "Replace the template literal variable with a hardcoded string. The bundler's CSS parser cannot handle interpolated variables in script content.",
4531
+ snippet: truncateSnippet(tlMatch[0])
4532
+ });
4533
+ }
4534
+ }
4535
+ {
4536
+ const cssTranslateSelectors = /* @__PURE__ */ new Map();
4537
+ const cssScaleSelectors = /* @__PURE__ */ new Map();
4538
+ for (const style of styles) {
4539
+ for (const [, selector, body] of style.content.matchAll(
4540
+ /([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
4541
+ )) {
4542
+ const tMatch = body?.match(/transform\s*:\s*([^;]+)/);
4543
+ if (!tMatch || !tMatch[1]) continue;
4544
+ const transformVal = tMatch[1].trim();
4545
+ if (/translate/i.test(transformVal)) {
4546
+ cssTranslateSelectors.set((selector ?? "").trim(), transformVal);
4547
+ }
4548
+ if (/scale/i.test(transformVal)) {
4549
+ cssScaleSelectors.set((selector ?? "").trim(), transformVal);
4550
+ }
4551
+ }
4552
+ }
4553
+ if (cssTranslateSelectors.size > 0 || cssScaleSelectors.size > 0) {
4554
+ for (const script of scripts) {
4555
+ if (!/gsap\.timeline/.test(script.content)) continue;
4556
+ const windows = extractGsapWindows(script.content);
4557
+ const conflicts = /* @__PURE__ */ new Map();
4558
+ for (const win of windows) {
4559
+ if (win.method === "fromTo") continue;
4560
+ const sel = win.targetSelector;
4561
+ const cssKey = sel.startsWith("#") || sel.startsWith(".") ? sel : `#${sel}`;
4562
+ const translateProps = win.properties.filter(
4563
+ (p) => ["x", "y", "xPercent", "yPercent"].includes(p)
4564
+ );
4565
+ const scaleProps = win.properties.filter((p) => p === "scale");
4566
+ const cssFromTranslate = translateProps.length > 0 ? cssTranslateSelectors.get(cssKey) : void 0;
4567
+ const cssFromScale = scaleProps.length > 0 ? cssScaleSelectors.get(cssKey) : void 0;
4568
+ if (!cssFromTranslate && !cssFromScale) continue;
4569
+ const existing = conflicts.get(sel) ?? {
4570
+ cssTransform: [cssFromTranslate, cssFromScale].filter(Boolean).join(" "),
4571
+ props: /* @__PURE__ */ new Set(),
4572
+ raw: win.raw
4573
+ };
4574
+ for (const p of [...translateProps, ...scaleProps]) existing.props.add(p);
4575
+ conflicts.set(sel, existing);
4576
+ }
4577
+ for (const [sel, { cssTransform, props, raw }] of conflicts) {
4578
+ const propList = [...props].join("/");
4579
+ pushFinding({
4580
+ code: "gsap_css_transform_conflict",
4581
+ severity: "warning",
4582
+ message: `"${sel}" has CSS \`transform: ${cssTransform}\` and a GSAP tween animates ${propList}. GSAP will overwrite the full CSS transform, discarding any translateX(-50%) centering or CSS scale value.`,
4583
+ selector: sel,
4584
+ fixHint: `Remove the transform from CSS and use tl.fromTo('${sel}', { xPercent: -50, x: -1000 }, { xPercent: -50, x: 0 }) so GSAP owns the full transform state. tl.fromTo is exempt from this rule.`,
4585
+ snippet: truncateSnippet(raw)
4586
+ });
4587
+ }
4588
+ }
4589
+ }
4590
+ }
3847
4591
  const errorCount = findings.filter((finding) => finding.severity === "error").length;
3848
4592
  const warningCount = findings.length - errorCount;
3849
4593
  return {
@@ -3987,6 +4731,7 @@ function extractGsapWindows(script) {
3987
4731
  end: animation.position + meta.effectiveDuration,
3988
4732
  properties: meta.properties.length > 0 ? meta.properties : Object.keys(animation.properties),
3989
4733
  overwriteAuto: meta.overwriteAuto,
4734
+ method: match[1] ?? "to",
3990
4735
  raw
3991
4736
  });
3992
4737
  }
@@ -4127,6 +4872,72 @@ function truncateSnippet(value, maxLength = 220) {
4127
4872
  }
4128
4873
  return `${normalized.slice(0, maxLength - 3)}...`;
4129
4874
  }
4875
+ function extractMediaUrls(html) {
4876
+ const results = [];
4877
+ const tagRe = /<(video|audio|img|source)\b[^>]*>/gi;
4878
+ let match;
4879
+ while ((match = tagRe.exec(html)) !== null) {
4880
+ const tagName19 = (match[1] ?? "").toLowerCase();
4881
+ const raw = match[0];
4882
+ const src = readAttr(raw, "src");
4883
+ if (!src) continue;
4884
+ if (/^https?:\/\//i.test(src)) {
4885
+ results.push({
4886
+ url: src,
4887
+ tagName: tagName19,
4888
+ elementId: readAttr(raw, "id") || void 0,
4889
+ snippet: raw.length > 120 ? raw.slice(0, 117) + "..." : raw
4890
+ });
4891
+ }
4892
+ }
4893
+ return results;
4894
+ }
4895
+ async function lintMediaUrls(html, options = {}) {
4896
+ const urls = extractMediaUrls(html);
4897
+ if (urls.length === 0) return [];
4898
+ const timeout = options.timeoutMs ?? 8e3;
4899
+ const findings = [];
4900
+ const seen = /* @__PURE__ */ new Set();
4901
+ const unique = urls.filter((u) => {
4902
+ if (seen.has(u.url)) return false;
4903
+ seen.add(u.url);
4904
+ return true;
4905
+ });
4906
+ const checks = unique.map(async ({ url, tagName: tagName19, elementId, snippet }) => {
4907
+ try {
4908
+ const controller = new AbortController();
4909
+ const timer = setTimeout(() => controller.abort(), timeout);
4910
+ const resp = await fetch(url, {
4911
+ method: "HEAD",
4912
+ signal: controller.signal,
4913
+ redirect: "follow"
4914
+ });
4915
+ clearTimeout(timer);
4916
+ if (!resp.ok) {
4917
+ findings.push({
4918
+ code: "inaccessible_media_url",
4919
+ severity: "error",
4920
+ message: `<${tagName19}${elementId ? ` id="${elementId}"` : ""}> references a URL that returned HTTP ${resp.status}: ${url.slice(0, 100)}`,
4921
+ elementId,
4922
+ fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.",
4923
+ snippet
4924
+ });
4925
+ }
4926
+ } catch (err) {
4927
+ const reason = err instanceof Error ? err.name : "unknown";
4928
+ findings.push({
4929
+ code: "inaccessible_media_url",
4930
+ severity: "error",
4931
+ message: `<${tagName19}${elementId ? ` id="${elementId}"` : ""}> references an unreachable URL (${reason}): ${url.slice(0, 100)}`,
4932
+ elementId,
4933
+ fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.",
4934
+ snippet
4935
+ });
4936
+ }
4937
+ });
4938
+ await Promise.all(checks);
4939
+ return findings;
4940
+ }
4130
4941
  var TAG_PATTERN, STYLE_BLOCK_PATTERN, SCRIPT_BLOCK_PATTERN, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, META_GSAP_KEYS;
4131
4942
  var init_hyperframeLinter = __esm({
4132
4943
  "../core/src/lint/hyperframeLinter.ts"() {
@@ -4172,14 +4983,14 @@ var init_staticGuard = __esm({
4172
4983
  });
4173
4984
 
4174
4985
  // ../core/src/compiler/htmlBundler.ts
4175
- import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
4176
- import { join as join4, resolve as resolve2, isAbsolute, sep } from "path";
4986
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
4987
+ import { join as join9, resolve as resolve5, isAbsolute, sep as sep2 } from "path";
4177
4988
  import * as cheerio from "cheerio";
4178
4989
  import { transformSync } from "esbuild";
4179
4990
  function safePath(projectDir, relativePath) {
4180
- const resolved = resolve2(projectDir, relativePath);
4181
- const normalizedBase = resolve2(projectDir) + sep;
4182
- if (!resolved.startsWith(normalizedBase) && resolved !== resolve2(projectDir)) return null;
4991
+ const resolved = resolve5(projectDir, relativePath);
4992
+ const normalizedBase = resolve5(projectDir) + sep2;
4993
+ if (!resolved.startsWith(normalizedBase) && resolved !== resolve5(projectDir)) return null;
4183
4994
  return resolved;
4184
4995
  }
4185
4996
  function stripEmbeddedRuntimeScripts(html) {
@@ -4235,17 +5046,17 @@ function isRelativeUrl(url) {
4235
5046
  return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute(url);
4236
5047
  }
4237
5048
  function safeReadFile(filePath) {
4238
- if (!existsSync4(filePath)) return null;
5049
+ if (!existsSync10(filePath)) return null;
4239
5050
  try {
4240
- return readFileSync2(filePath, "utf-8");
5051
+ return readFileSync9(filePath, "utf-8");
4241
5052
  } catch {
4242
5053
  return null;
4243
5054
  }
4244
5055
  }
4245
5056
  function safeReadFileBuffer(filePath) {
4246
- if (!existsSync4(filePath)) return null;
5057
+ if (!existsSync10(filePath)) return null;
4247
5058
  try {
4248
- return readFileSync2(filePath);
5059
+ return readFileSync9(filePath);
4249
5060
  } catch {
4250
5061
  return null;
4251
5062
  }
@@ -4432,9 +5243,9 @@ function stripJsCommentsParserSafe(source) {
4432
5243
  }
4433
5244
  }
4434
5245
  async function bundleToSingleHtml(projectDir, options) {
4435
- const indexPath = join4(projectDir, "index.html");
4436
- if (!existsSync4(indexPath)) throw new Error("index.html not found in project directory");
4437
- const rawHtml = readFileSync2(indexPath, "utf-8");
5246
+ const indexPath = join9(projectDir, "index.html");
5247
+ if (!existsSync10(indexPath)) throw new Error("index.html not found in project directory");
5248
+ const rawHtml = readFileSync9(indexPath, "utf-8");
4438
5249
  const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
4439
5250
  const staticGuard = validateHyperframeHtmlContract(compiled);
4440
5251
  if (!staticGuard.isValid) {
@@ -4589,6 +5400,19 @@ var init_compiler = __esm({
4589
5400
  }
4590
5401
  });
4591
5402
 
5403
+ // ../core/src/lint/index.ts
5404
+ var lint_exports = {};
5405
+ __export(lint_exports, {
5406
+ lintHyperframeHtml: () => lintHyperframeHtml,
5407
+ lintMediaUrls: () => lintMediaUrls
5408
+ });
5409
+ var init_lint2 = __esm({
5410
+ "../core/src/lint/index.ts"() {
5411
+ "use strict";
5412
+ init_hyperframeLinter();
5413
+ }
5414
+ });
5415
+
4592
5416
  // ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/symbols.js
4593
5417
  var CHANGED, CLASS_LIST, CUSTOM_ELEMENTS, CONTENT, DATASET, DOCTYPE, DOM_PARSER, END, EVENT_TARGET, GLOBALS, IMAGE, MIME, MUTATION_OBSERVER, NEXT, OWNER_ELEMENT, PREV, PRIVATE, SHEET, START, STYLE, UPGRADE, VALUE;
4594
5418
  var init_symbols = __esm({
@@ -8443,8 +9267,8 @@ var init_custom_element_registry = __esm({
8443
9267
  } : (element) => element.localName === localName;
8444
9268
  registry.set(localName, { Class, check });
8445
9269
  if (waiting.has(localName)) {
8446
- for (const resolve17 of waiting.get(localName))
8447
- resolve17(Class);
9270
+ for (const resolve20 of waiting.get(localName))
9271
+ resolve20(Class);
8448
9272
  waiting.delete(localName);
8449
9273
  }
8450
9274
  ownerDocument.querySelectorAll(
@@ -8484,13 +9308,13 @@ var init_custom_element_registry = __esm({
8484
9308
  */
8485
9309
  whenDefined(localName) {
8486
9310
  const { registry, waiting } = this;
8487
- return new Promise((resolve17) => {
9311
+ return new Promise((resolve20) => {
8488
9312
  if (registry.has(localName))
8489
- resolve17(registry.get(localName).Class);
9313
+ resolve20(registry.get(localName).Class);
8490
9314
  else {
8491
9315
  if (!waiting.has(localName))
8492
9316
  waiting.set(localName, []);
8493
- waiting.get(localName).push(resolve17);
9317
+ waiting.get(localName).push(resolve20);
8494
9318
  }
8495
9319
  });
8496
9320
  }
@@ -16427,7 +17251,7 @@ var init_html_classes = __esm({
16427
17251
 
16428
17252
  // ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js
16429
17253
  var voidElements2, Mime;
16430
- var init_mime = __esm({
17254
+ var init_mime2 = __esm({
16431
17255
  "../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js"() {
16432
17256
  "use strict";
16433
17257
  voidElements2 = { test: () => true };
@@ -16692,7 +17516,7 @@ var init_document = __esm({
16692
17516
  init_symbols();
16693
17517
  init_facades();
16694
17518
  init_html_classes();
16695
- init_mime();
17519
+ init_mime2();
16696
17520
  init_utils();
16697
17521
  init_object();
16698
17522
  init_non_element_parent_node();
@@ -17298,8 +18122,8 @@ var init_config2 = __esm({
17298
18122
  });
17299
18123
 
17300
18124
  // ../engine/src/services/browserManager.ts
17301
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
17302
- import { join as join5 } from "path";
18125
+ import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
18126
+ import { join as join10 } from "path";
17303
18127
  import { homedir as homedir4 } from "os";
17304
18128
  async function getPuppeteer() {
17305
18129
  if (_puppeteer) return _puppeteer;
@@ -17320,19 +18144,19 @@ function resolveHeadlessShellPath(config) {
17320
18144
  if (process.env.PRODUCER_HEADLESS_SHELL_PATH) {
17321
18145
  return process.env.PRODUCER_HEADLESS_SHELL_PATH;
17322
18146
  }
17323
- const baseDir = join5(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
17324
- if (!existsSync5(baseDir)) return void 0;
18147
+ const baseDir = join10(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
18148
+ if (!existsSync11(baseDir)) return void 0;
17325
18149
  try {
17326
- const versions = readdirSync2(baseDir).sort().reverse();
18150
+ const versions = readdirSync4(baseDir).sort().reverse();
17327
18151
  for (const version of versions) {
17328
18152
  const candidates = [
17329
- join5(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
17330
- join5(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
17331
- join5(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
17332
- join5(baseDir, version, "chrome-headless-shell-win64", "chrome-headless-shell.exe")
18153
+ join10(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
18154
+ join10(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
18155
+ join10(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
18156
+ join10(baseDir, version, "chrome-headless-shell-win64", "chrome-headless-shell.exe")
17333
18157
  ];
17334
18158
  for (const binary of candidates) {
17335
- if (existsSync5(binary)) return binary;
18159
+ if (existsSync11(binary)) return binary;
17336
18160
  }
17337
18161
  }
17338
18162
  } catch {
@@ -17906,7 +18730,7 @@ var init_hyperframes = __esm({
17906
18730
 
17907
18731
  // ../core/src/inline-scripts/hyperframesRuntime.engine.ts
17908
18732
  import { buildSync } from "esbuild";
17909
- import { dirname as dirname2, resolve as resolve3 } from "path";
18733
+ import { dirname as dirname3, resolve as resolve6 } from "path";
17910
18734
  import { fileURLToPath } from "url";
17911
18735
  var init_hyperframesRuntime_engine = __esm({
17912
18736
  "../core/src/inline-scripts/hyperframesRuntime.engine.ts"() {
@@ -18025,9 +18849,9 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
18025
18849
  buffer = Buffer.from(result.screenshotData, "base64");
18026
18850
  lastFrameCache.set(page, buffer);
18027
18851
  } else {
18028
- const cached = lastFrameCache.get(page);
18029
- if (cached) {
18030
- buffer = cached;
18852
+ const cached2 = lastFrameCache.get(page);
18853
+ if (cached2) {
18854
+ buffer = cached2;
18031
18855
  } else {
18032
18856
  const retry = await client.send("HeadlessExperimental.beginFrame", {
18033
18857
  frameTimeTicks: frameTimeTicks + 1e-3,
@@ -18163,10 +18987,10 @@ var init_screenshotService = __esm({
18163
18987
  });
18164
18988
 
18165
18989
  // ../engine/src/services/frameCapture.ts
18166
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
18167
- import { join as join6 } from "path";
18990
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
18991
+ import { join as join11 } from "path";
18168
18992
  async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
18169
- if (!existsSync6(outputDir)) mkdirSync4(outputDir, { recursive: true });
18993
+ if (!existsSync12(outputDir)) mkdirSync7(outputDir, { recursive: true });
18170
18994
  const headlessShell = resolveHeadlessShellPath(config);
18171
18995
  const isLinux = process.platform === "linux";
18172
18996
  const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG2.forceScreenshot;
@@ -18325,13 +19149,13 @@ async function initializeSession(session) {
18325
19149
  }
18326
19150
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
18327
19151
  try {
18328
- const diagnosticsDir = join6(session.outputDir, "diagnostics");
18329
- if (!existsSync6(diagnosticsDir)) mkdirSync4(diagnosticsDir, { recursive: true });
18330
- const base = join6(diagnosticsDir, `frame-error-${frameIndex}`);
19152
+ const diagnosticsDir = join11(session.outputDir, "diagnostics");
19153
+ if (!existsSync12(diagnosticsDir)) mkdirSync7(diagnosticsDir, { recursive: true });
19154
+ const base = join11(diagnosticsDir, `frame-error-${frameIndex}`);
18331
19155
  await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
18332
19156
  const html = await session.page.content();
18333
- writeFileSync2(`${base}.html`, html, "utf-8");
18334
- writeFileSync2(
19157
+ writeFileSync4(`${base}.html`, html, "utf-8");
19158
+ writeFileSync4(
18335
19159
  `${base}.json`,
18336
19160
  JSON.stringify(
18337
19161
  {
@@ -18425,8 +19249,8 @@ async function captureFrame(session, frameIndex, time) {
18425
19249
  );
18426
19250
  const ext = options.format === "png" ? "png" : "jpg";
18427
19251
  const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
18428
- const framePath = join6(outputDir, frameName);
18429
- writeFileSync2(framePath, buffer);
19252
+ const framePath = join11(outputDir, frameName);
19253
+ writeFileSync4(framePath, buffer);
18430
19254
  return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
18431
19255
  }
18432
19256
  async function captureFrameToBuffer(session, frameIndex, time) {
@@ -18440,8 +19264,8 @@ async function closeCaptureSession(session) {
18440
19264
  session.isInitialized = false;
18441
19265
  }
18442
19266
  function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
18443
- if (!existsSync6(outputDir)) {
18444
- mkdirSync4(outputDir, { recursive: true });
19267
+ if (!existsSync12(outputDir)) {
19268
+ mkdirSync7(outputDir, { recursive: true });
18445
19269
  }
18446
19270
  session.outputDir = outputDir;
18447
19271
  session.onBeforeCapture = onBeforeCapture;
@@ -18486,7 +19310,7 @@ var init_frameCapture = __esm({
18486
19310
  // ../engine/src/utils/gpuEncoder.ts
18487
19311
  import { spawn } from "child_process";
18488
19312
  async function detectGpuEncoder() {
18489
- return new Promise((resolve17) => {
19313
+ return new Promise((resolve20) => {
18490
19314
  const ffmpeg = spawn("ffmpeg", ["-encoders"], {
18491
19315
  stdio: ["pipe", "pipe", "pipe"]
18492
19316
  });
@@ -18495,13 +19319,13 @@ async function detectGpuEncoder() {
18495
19319
  stdout2 += data.toString();
18496
19320
  });
18497
19321
  ffmpeg.on("close", () => {
18498
- if (stdout2.includes("h264_nvenc")) resolve17("nvenc");
18499
- else if (stdout2.includes("h264_videotoolbox")) resolve17("videotoolbox");
18500
- else if (stdout2.includes("h264_vaapi")) resolve17("vaapi");
18501
- else if (stdout2.includes("h264_qsv")) resolve17("qsv");
18502
- else resolve17(null);
19322
+ if (stdout2.includes("h264_nvenc")) resolve20("nvenc");
19323
+ else if (stdout2.includes("h264_videotoolbox")) resolve20("videotoolbox");
19324
+ else if (stdout2.includes("h264_vaapi")) resolve20("vaapi");
19325
+ else if (stdout2.includes("h264_qsv")) resolve20("qsv");
19326
+ else resolve20(null);
18503
19327
  });
18504
- ffmpeg.on("error", () => resolve17(null));
19328
+ ffmpeg.on("error", () => resolve20(null));
18505
19329
  });
18506
19330
  }
18507
19331
  async function getCachedGpuEncoder() {
@@ -18540,7 +19364,7 @@ async function runFfmpeg(args, opts) {
18540
19364
  const signal = opts?.signal;
18541
19365
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
18542
19366
  const onStderr = opts?.onStderr;
18543
- return new Promise((resolve17) => {
19367
+ return new Promise((resolve20) => {
18544
19368
  const ffmpeg = spawn2("ffmpeg", args);
18545
19369
  let stderr = "";
18546
19370
  const onAbort = () => {
@@ -18566,7 +19390,7 @@ async function runFfmpeg(args, opts) {
18566
19390
  ffmpeg.on("close", (code) => {
18567
19391
  clearTimeout(timer);
18568
19392
  if (signal) signal.removeEventListener("abort", onAbort);
18569
- resolve17({
19393
+ resolve20({
18570
19394
  success: !signal?.aborted && code === 0,
18571
19395
  exitCode: code,
18572
19396
  stderr,
@@ -18576,7 +19400,7 @@ async function runFfmpeg(args, opts) {
18576
19400
  ffmpeg.on("error", (err) => {
18577
19401
  clearTimeout(timer);
18578
19402
  if (signal) signal.removeEventListener("abort", onAbort);
18579
- resolve17({
19403
+ resolve20({
18580
19404
  success: false,
18581
19405
  exitCode: null,
18582
19406
  stderr: err.message,
@@ -18595,8 +19419,8 @@ var init_runFfmpeg = __esm({
18595
19419
 
18596
19420
  // ../engine/src/services/chunkEncoder.ts
18597
19421
  import { spawn as spawn3 } from "child_process";
18598
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
18599
- import { join as join7, dirname as dirname3 } from "path";
19422
+ import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
19423
+ import { join as join12, dirname as dirname4 } from "path";
18600
19424
  function getEncoderPreset(quality, format = "mp4") {
18601
19425
  const base = ENCODER_PRESETS[quality];
18602
19426
  if (format === "webm") {
@@ -18677,9 +19501,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
18677
19501
  }
18678
19502
  async function encodeFramesFromDir(framesDir, framePattern, outputPath, options, signal, config) {
18679
19503
  const startTime = Date.now();
18680
- const outputDir = dirname3(outputPath);
18681
- if (!existsSync7(outputDir)) mkdirSync5(outputDir, { recursive: true });
18682
- const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
19504
+ const outputDir = dirname4(outputPath);
19505
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
19506
+ const files = readdirSync5(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
18683
19507
  const frameCount = files.length;
18684
19508
  if (frameCount === 0) {
18685
19509
  return {
@@ -18695,10 +19519,10 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18695
19519
  if (options.useGpu) {
18696
19520
  gpuEncoder = await getCachedGpuEncoder();
18697
19521
  }
18698
- const inputPath = join7(framesDir, framePattern);
19522
+ const inputPath = join12(framesDir, framePattern);
18699
19523
  const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
18700
19524
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
18701
- return new Promise((resolve17) => {
19525
+ return new Promise((resolve20) => {
18702
19526
  const ffmpeg = spawn3("ffmpeg", args);
18703
19527
  let stderr = "";
18704
19528
  const onAbort = () => {
@@ -18723,7 +19547,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18723
19547
  if (signal) signal.removeEventListener("abort", onAbort);
18724
19548
  const durationMs = Date.now() - startTime;
18725
19549
  if (signal?.aborted) {
18726
- resolve17({
19550
+ resolve20({
18727
19551
  success: false,
18728
19552
  outputPath,
18729
19553
  durationMs,
@@ -18734,7 +19558,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18734
19558
  return;
18735
19559
  }
18736
19560
  if (code !== 0) {
18737
- resolve17({
19561
+ resolve20({
18738
19562
  success: false,
18739
19563
  outputPath,
18740
19564
  durationMs,
@@ -18744,13 +19568,13 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18744
19568
  });
18745
19569
  return;
18746
19570
  }
18747
- const fileSize = existsSync7(outputPath) ? statSync(outputPath).size : 0;
18748
- resolve17({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
19571
+ const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
19572
+ resolve20({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
18749
19573
  });
18750
19574
  ffmpeg.on("error", (err) => {
18751
19575
  clearTimeout(timer);
18752
19576
  if (signal) signal.removeEventListener("abort", onAbort);
18753
- resolve17({
19577
+ resolve20({
18754
19578
  success: false,
18755
19579
  outputPath,
18756
19580
  durationMs: Date.now() - startTime,
@@ -18763,7 +19587,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18763
19587
  }
18764
19588
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal) {
18765
19589
  const start = Date.now();
18766
- const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
19590
+ const files = readdirSync5(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
18767
19591
  if (files.length === 0) {
18768
19592
  return {
18769
19593
  success: false,
@@ -18776,8 +19600,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18776
19600
  }
18777
19601
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
18778
19602
  const chunkCount = Math.ceil(files.length / chunkSize);
18779
- const chunkDir = join7(dirname3(outputPath), "chunk-encode");
18780
- if (!existsSync7(chunkDir)) mkdirSync5(chunkDir, { recursive: true });
19603
+ const chunkDir = join12(dirname4(outputPath), "chunk-encode");
19604
+ if (!existsSync13(chunkDir)) mkdirSync8(chunkDir, { recursive: true });
18781
19605
  const chunkPaths = [];
18782
19606
  for (let i = 0; i < chunkCount; i++) {
18783
19607
  if (signal?.aborted) {
@@ -18793,8 +19617,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18793
19617
  const startNumber = i * chunkSize;
18794
19618
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
18795
19619
  const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
18796
- const chunkPath = join7(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
18797
- const inputPath = join7(framesDir, framePattern);
19620
+ const chunkPath = join12(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
19621
+ const inputPath = join12(framesDir, framePattern);
18798
19622
  const inputArgs = [
18799
19623
  "-framerate",
18800
19624
  String(options.fps),
@@ -18808,18 +19632,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18808
19632
  let gpuEncoder = null;
18809
19633
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
18810
19634
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
18811
- const chunkResult = await new Promise((resolve17) => {
19635
+ const chunkResult = await new Promise((resolve20) => {
18812
19636
  const ffmpeg = spawn3("ffmpeg", args);
18813
19637
  let stderr = "";
18814
19638
  ffmpeg.stderr.on("data", (d) => {
18815
19639
  stderr += d.toString();
18816
19640
  });
18817
19641
  ffmpeg.on("close", (code) => {
18818
- if (code === 0) resolve17({ success: true });
18819
- else resolve17({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
19642
+ if (code === 0) resolve20({ success: true });
19643
+ else resolve20({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
18820
19644
  });
18821
19645
  ffmpeg.on("error", (err) => {
18822
- resolve17({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
19646
+ resolve20({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
18823
19647
  });
18824
19648
  });
18825
19649
  if (!chunkResult.success) {
@@ -18834,9 +19658,9 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18834
19658
  }
18835
19659
  chunkPaths.push(chunkPath);
18836
19660
  }
18837
- const concatListPath = join7(chunkDir, "concat-list.txt");
19661
+ const concatListPath = join12(chunkDir, "concat-list.txt");
18838
19662
  const concatInput = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
18839
- writeFileSync3(concatListPath, concatInput, "utf-8");
19663
+ writeFileSync5(concatListPath, concatInput, "utf-8");
18840
19664
  const concatArgs = [
18841
19665
  "-f",
18842
19666
  "concat",
@@ -18849,18 +19673,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18849
19673
  "-y",
18850
19674
  outputPath
18851
19675
  ];
18852
- const concatResult = await new Promise((resolve17) => {
19676
+ const concatResult = await new Promise((resolve20) => {
18853
19677
  const ffmpeg = spawn3("ffmpeg", concatArgs);
18854
19678
  let stderr = "";
18855
19679
  ffmpeg.stderr.on("data", (d) => {
18856
19680
  stderr += d.toString();
18857
19681
  });
18858
19682
  ffmpeg.on("close", (code) => {
18859
- if (code === 0) resolve17({ success: true });
18860
- else resolve17({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
19683
+ if (code === 0) resolve20({ success: true });
19684
+ else resolve20({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
18861
19685
  });
18862
19686
  ffmpeg.on("error", (err) => {
18863
- resolve17({ success: false, error: `Chunk concat error: ${err.message}` });
19687
+ resolve20({ success: false, error: `Chunk concat error: ${err.message}` });
18864
19688
  });
18865
19689
  });
18866
19690
  if (!concatResult.success) {
@@ -18873,7 +19697,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18873
19697
  error: concatResult.error
18874
19698
  };
18875
19699
  }
18876
- const fileSize = existsSync7(outputPath) ? statSync(outputPath).size : 0;
19700
+ const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
18877
19701
  return {
18878
19702
  success: true,
18879
19703
  outputPath,
@@ -18883,8 +19707,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18883
19707
  };
18884
19708
  }
18885
19709
  async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, config) {
18886
- const outputDir = dirname3(outputPath);
18887
- if (!existsSync7(outputDir)) mkdirSync5(outputDir, { recursive: true });
19710
+ const outputDir = dirname4(outputPath);
19711
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
18888
19712
  const isWebm = outputPath.endsWith(".webm");
18889
19713
  const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
18890
19714
  if (isWebm) {
@@ -18951,8 +19775,8 @@ var init_chunkEncoder = __esm({
18951
19775
 
18952
19776
  // ../engine/src/services/streamingEncoder.ts
18953
19777
  import { spawn as spawn4 } from "child_process";
18954
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, statSync as statSync2 } from "fs";
18955
- import { dirname as dirname4 } from "path";
19778
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, statSync as statSync4 } from "fs";
19779
+ import { dirname as dirname5 } from "path";
18956
19780
  function createFrameReorderBuffer(startFrame, endFrame) {
18957
19781
  let nextFrame = startFrame;
18958
19782
  let waiters = [];
@@ -18965,16 +19789,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
18965
19789
  }
18966
19790
  };
18967
19791
  return {
18968
- waitForFrame: (frame) => new Promise((resolve17) => {
18969
- waiters.push({ frame, resolve: resolve17 });
19792
+ waitForFrame: (frame) => new Promise((resolve20) => {
19793
+ waiters.push({ frame, resolve: resolve20 });
18970
19794
  resolveWaiters();
18971
19795
  }),
18972
19796
  advanceTo: (frame) => {
18973
19797
  nextFrame = frame;
18974
19798
  resolveWaiters();
18975
19799
  },
18976
- waitForAllDone: () => new Promise((resolve17) => {
18977
- waiters.push({ frame: endFrame, resolve: resolve17 });
19800
+ waitForAllDone: () => new Promise((resolve20) => {
19801
+ waiters.push({ frame: endFrame, resolve: resolve20 });
18978
19802
  resolveWaiters();
18979
19803
  })
18980
19804
  };
@@ -19059,8 +19883,8 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
19059
19883
  return args;
19060
19884
  }
19061
19885
  async function spawnStreamingEncoder(outputPath, options, signal, config) {
19062
- const outputDir = dirname4(outputPath);
19063
- if (!existsSync8(outputDir)) mkdirSync6(outputDir, { recursive: true });
19886
+ const outputDir = dirname5(outputPath);
19887
+ if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
19064
19888
  let gpuEncoder = null;
19065
19889
  if (options.useGpu) {
19066
19890
  gpuEncoder = await getCachedGpuEncoder();
@@ -19074,7 +19898,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
19074
19898
  let stderr = "";
19075
19899
  let exitCode = null;
19076
19900
  let exitPromiseResolve = null;
19077
- const exitPromise = new Promise((resolve17) => exitPromiseResolve = resolve17);
19901
+ const exitPromise = new Promise((resolve20) => exitPromiseResolve = resolve20);
19078
19902
  ffmpeg.stderr?.on("data", (data) => {
19079
19903
  stderr += data.toString();
19080
19904
  });
@@ -19118,8 +19942,8 @@ Process error: ${err.message}`;
19118
19942
  clearTimeout(timer);
19119
19943
  if (signal) signal.removeEventListener("abort", onAbort);
19120
19944
  if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
19121
- await new Promise((resolve17) => {
19122
- ffmpeg.stdin.end(() => resolve17());
19945
+ await new Promise((resolve20) => {
19946
+ ffmpeg.stdin.end(() => resolve20());
19123
19947
  });
19124
19948
  }
19125
19949
  await exitPromise;
@@ -19140,7 +19964,7 @@ Process error: ${err.message}`;
19140
19964
  error: `FFmpeg exited with code ${exitCode}`
19141
19965
  };
19142
19966
  }
19143
- const fileSize = existsSync8(outputPath) ? statSync2(outputPath).size : 0;
19967
+ const fileSize = existsSync14(outputPath) ? statSync4(outputPath).size : 0;
19144
19968
  return { success: true, durationMs, fileSize };
19145
19969
  },
19146
19970
  getExitStatus: () => exitStatus
@@ -19168,11 +19992,11 @@ function parseFrameRate(frameRateStr) {
19168
19992
  return parseFloat(frameRateStr) || 0;
19169
19993
  }
19170
19994
  async function extractVideoMetadata(filePath) {
19171
- const cached = videoMetadataCache.get(filePath);
19172
- if (cached) {
19173
- return cached;
19995
+ const cached2 = videoMetadataCache.get(filePath);
19996
+ if (cached2) {
19997
+ return cached2;
19174
19998
  }
19175
- const probePromise = new Promise((resolve17, reject) => {
19999
+ const probePromise = new Promise((resolve20, reject) => {
19176
20000
  const args = [
19177
20001
  "-v",
19178
20002
  "quiet",
@@ -19214,7 +20038,7 @@ async function extractVideoMetadata(filePath) {
19214
20038
  videoCodec: videoStream.codec_name || "unknown",
19215
20039
  hasAudio
19216
20040
  };
19217
- resolve17(metadata);
20041
+ resolve20(metadata);
19218
20042
  } catch (parseError) {
19219
20043
  reject(
19220
20044
  new Error(
@@ -19240,11 +20064,11 @@ async function extractVideoMetadata(filePath) {
19240
20064
  return probePromise;
19241
20065
  }
19242
20066
  async function extractAudioMetadata(filePath) {
19243
- const cached = audioMetadataCache.get(filePath);
19244
- if (cached) {
19245
- return cached;
20067
+ const cached2 = audioMetadataCache.get(filePath);
20068
+ if (cached2) {
20069
+ return cached2;
19246
20070
  }
19247
- const probePromise = new Promise((resolve17, reject) => {
20071
+ const probePromise = new Promise((resolve20, reject) => {
19248
20072
  const args = [
19249
20073
  "-v",
19250
20074
  "quiet",
@@ -19283,7 +20107,7 @@ async function extractAudioMetadata(filePath) {
19283
20107
  audioCodec: audioStream.codec_name || "unknown",
19284
20108
  bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
19285
20109
  };
19286
- resolve17(metadata);
20110
+ resolve20(metadata);
19287
20111
  } catch (parseError) {
19288
20112
  reject(
19289
20113
  new Error(
@@ -19318,9 +20142,9 @@ var init_ffprobe = __esm({
19318
20142
  });
19319
20143
 
19320
20144
  // ../engine/src/utils/urlDownloader.ts
19321
- import { createWriteStream as createWriteStream2, existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
20145
+ import { createWriteStream as createWriteStream2, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
19322
20146
  import { createHash } from "crypto";
19323
- import { join as join8, extname } from "path";
20147
+ import { join as join13, extname } from "path";
19324
20148
  import { Readable } from "stream";
19325
20149
  import { finished } from "stream/promises";
19326
20150
  function getFilenameFromUrl(url) {
@@ -19331,19 +20155,19 @@ function getFilenameFromUrl(url) {
19331
20155
  }
19332
20156
  async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
19333
20157
  const cachedPath = downloadPathCache.get(url);
19334
- if (cachedPath && existsSync9(cachedPath)) {
20158
+ if (cachedPath && existsSync15(cachedPath)) {
19335
20159
  return cachedPath;
19336
20160
  }
19337
20161
  const inFlight = inFlightDownloads.get(url);
19338
20162
  if (inFlight) {
19339
20163
  return inFlight;
19340
20164
  }
19341
- if (!existsSync9(destDir)) {
19342
- mkdirSync7(destDir, { recursive: true });
20165
+ if (!existsSync15(destDir)) {
20166
+ mkdirSync10(destDir, { recursive: true });
19343
20167
  }
19344
20168
  const filename = getFilenameFromUrl(url);
19345
- const localPath = join8(destDir, filename);
19346
- if (existsSync9(localPath)) {
20169
+ const localPath = join13(destDir, filename);
20170
+ if (existsSync15(localPath)) {
19347
20171
  downloadPathCache.set(url, localPath);
19348
20172
  return localPath;
19349
20173
  }
@@ -19391,8 +20215,8 @@ var init_urlDownloader = __esm({
19391
20215
 
19392
20216
  // ../engine/src/services/videoFrameExtractor.ts
19393
20217
  import { spawn as spawn6 } from "child_process";
19394
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync4, rmSync as rmSync3 } from "fs";
19395
- import { join as join9 } from "path";
20218
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, rmSync as rmSync3 } from "fs";
20219
+ import { join as join14 } from "path";
19396
20220
  function parseVideoElements(html) {
19397
20221
  const videos = [];
19398
20222
  const { document: document2 } = parseHTML(html);
@@ -19419,11 +20243,11 @@ function parseVideoElements(html) {
19419
20243
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
19420
20244
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
19421
20245
  const { fps, outputDir, quality = 95, format = "jpg" } = options;
19422
- const videoOutputDir = join9(outputDir, videoId);
19423
- if (!existsSync10(videoOutputDir)) mkdirSync8(videoOutputDir, { recursive: true });
20246
+ const videoOutputDir = join14(outputDir, videoId);
20247
+ if (!existsSync16(videoOutputDir)) mkdirSync11(videoOutputDir, { recursive: true });
19424
20248
  const metadata = await extractVideoMetadata(videoPath);
19425
20249
  const framePattern = `frame_%05d.${format}`;
19426
- const outputPattern = join9(videoOutputDir, framePattern);
20250
+ const outputPattern = join14(videoOutputDir, framePattern);
19427
20251
  const args = [
19428
20252
  "-ss",
19429
20253
  String(startTime),
@@ -19438,7 +20262,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
19438
20262
  ];
19439
20263
  if (format === "png") args.push("-compression_level", "6");
19440
20264
  args.push("-y", outputPattern);
19441
- return new Promise((resolve17, reject) => {
20265
+ return new Promise((resolve20, reject) => {
19442
20266
  const ffmpeg = spawn6("ffmpeg", args);
19443
20267
  let stderr = "";
19444
20268
  const onAbort = () => {
@@ -19469,11 +20293,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
19469
20293
  return;
19470
20294
  }
19471
20295
  const framePaths = /* @__PURE__ */ new Map();
19472
- const files = readdirSync4(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
20296
+ const files = readdirSync6(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
19473
20297
  files.forEach((file, index) => {
19474
- framePaths.set(index, join9(videoOutputDir, file));
20298
+ framePaths.set(index, join14(videoOutputDir, file));
19475
20299
  });
19476
- resolve17({
20300
+ resolve20({
19477
20301
  videoId,
19478
20302
  srcPath: videoPath,
19479
20303
  outputDir: videoOutputDir,
@@ -19508,14 +20332,14 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
19508
20332
  try {
19509
20333
  let videoPath = video.src;
19510
20334
  if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
19511
- videoPath = join9(baseDir, videoPath);
20335
+ videoPath = join14(baseDir, videoPath);
19512
20336
  }
19513
20337
  if (isHttpUrl(videoPath)) {
19514
- const downloadDir = join9(options.outputDir, "_downloads");
19515
- mkdirSync8(downloadDir, { recursive: true });
20338
+ const downloadDir = join14(options.outputDir, "_downloads");
20339
+ mkdirSync11(downloadDir, { recursive: true });
19516
20340
  videoPath = await downloadToTemp(videoPath, downloadDir);
19517
20341
  }
19518
- if (!existsSync10(videoPath)) {
20342
+ if (!existsSync16(videoPath)) {
19519
20343
  return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
19520
20344
  }
19521
20345
  let videoDuration = video.end - video.start;
@@ -19669,7 +20493,7 @@ var init_videoFrameExtractor = __esm({
19669
20493
  }
19670
20494
  cleanup() {
19671
20495
  for (const video of this.videos.values()) {
19672
- if (existsSync10(video.extracted.outputDir)) {
20496
+ if (existsSync16(video.extracted.outputDir)) {
19673
20497
  rmSync3(video.extracted.outputDir, { recursive: true, force: true });
19674
20498
  }
19675
20499
  }
@@ -19700,10 +20524,10 @@ function createFrameDataUriCache(cacheLimit) {
19700
20524
  return dataUri;
19701
20525
  }
19702
20526
  async function get(framePath) {
19703
- const cached = cache.get(framePath);
19704
- if (cached) {
19705
- remember(framePath, cached);
19706
- return cached;
20527
+ const cached2 = cache.get(framePath);
20528
+ if (cached2) {
20529
+ remember(framePath, cached2);
20530
+ return cached2;
19707
20531
  }
19708
20532
  const existing = inFlight.get(framePath);
19709
20533
  if (existing) {
@@ -19771,8 +20595,8 @@ var init_videoFrameInjector = __esm({
19771
20595
  });
19772
20596
 
19773
20597
  // ../engine/src/services/audioMixer.ts
19774
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, rmSync as rmSync4 } from "fs";
19775
- import { join as join10, dirname as dirname5 } from "path";
20598
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, rmSync as rmSync4 } from "fs";
20599
+ import { join as join15, dirname as dirname6 } from "path";
19776
20600
  function parseAudioElements(html) {
19777
20601
  const elements = [];
19778
20602
  const { document: document2 } = parseHTML(html);
@@ -19822,8 +20646,8 @@ function parseAudioElements(html) {
19822
20646
  }
19823
20647
  async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
19824
20648
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
19825
- const outputDir = dirname5(outputPath);
19826
- if (!existsSync11(outputDir)) mkdirSync9(outputDir, { recursive: true });
20649
+ const outputDir = dirname6(outputPath);
20650
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
19827
20651
  const args = ["-i", videoPath];
19828
20652
  if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
19829
20653
  if (options?.duration !== void 0) args.push("-t", String(options.duration));
@@ -19849,8 +20673,8 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
19849
20673
  }
19850
20674
  async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, signal, config) {
19851
20675
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
19852
- const outputDir = dirname5(outputPath);
19853
- if (!existsSync11(outputDir)) mkdirSync9(outputDir, { recursive: true });
20676
+ const outputDir = dirname6(outputPath);
20677
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
19854
20678
  const args = [
19855
20679
  "-ss",
19856
20680
  String(mediaStart),
@@ -19885,8 +20709,8 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, sign
19885
20709
  }
19886
20710
  async function generateSilence(outputPath, duration, signal, config) {
19887
20711
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
19888
- const outputDir = dirname5(outputPath);
19889
- if (!existsSync11(outputDir)) mkdirSync9(outputDir, { recursive: true });
20712
+ const outputDir = dirname6(outputPath);
20713
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
19890
20714
  const args = [
19891
20715
  "-f",
19892
20716
  "lavfi",
@@ -19928,8 +20752,8 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
19928
20752
  error: result2.error
19929
20753
  };
19930
20754
  }
19931
- const outputDir = dirname5(outputPath);
19932
- if (!existsSync11(outputDir)) mkdirSync9(outputDir, { recursive: true });
20755
+ const outputDir = dirname6(outputPath);
20756
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
19933
20757
  const inputs = [];
19934
20758
  const filterParts = [];
19935
20759
  tracks.forEach((track, i) => {
@@ -19990,7 +20814,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
19990
20814
  const startMs = Date.now();
19991
20815
  const tracks = [];
19992
20816
  const errors = [];
19993
- if (!existsSync11(workDir)) mkdirSync9(workDir, { recursive: true });
20817
+ if (!existsSync17(workDir)) mkdirSync12(workDir, { recursive: true });
19994
20818
  await Promise.all(
19995
20819
  elements.map(async (element) => {
19996
20820
  if (signal?.aborted) {
@@ -20000,7 +20824,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20000
20824
  try {
20001
20825
  let srcPath = element.src;
20002
20826
  if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
20003
- srcPath = join10(baseDir, srcPath);
20827
+ srcPath = join15(baseDir, srcPath);
20004
20828
  }
20005
20829
  if (isHttpUrl(srcPath)) {
20006
20830
  try {
@@ -20012,7 +20836,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20012
20836
  return;
20013
20837
  }
20014
20838
  }
20015
- if (!existsSync11(srcPath)) {
20839
+ if (!existsSync17(srcPath)) {
20016
20840
  errors.push(`Source not found: ${element.id}`);
20017
20841
  return;
20018
20842
  }
@@ -20023,7 +20847,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20023
20847
  }
20024
20848
  let audioSrcPath = srcPath;
20025
20849
  if (element.type === "video") {
20026
- const extractedPath = join10(workDir, `${element.id}-extracted.wav`);
20850
+ const extractedPath = join15(workDir, `${element.id}-extracted.wav`);
20027
20851
  const extractResult = await extractAudioFromVideo(
20028
20852
  srcPath,
20029
20853
  extractedPath,
@@ -20040,7 +20864,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20040
20864
  }
20041
20865
  audioSrcPath = extractedPath;
20042
20866
  } else {
20043
- const trimmedPath = join10(workDir, `${element.id}-trimmed.wav`);
20867
+ const trimmedPath = join15(workDir, `${element.id}-trimmed.wav`);
20044
20868
  const prepResult = await prepareAudioTrack(
20045
20869
  srcPath,
20046
20870
  trimmedPath,
@@ -20092,10 +20916,10 @@ var init_audioMixer = __esm({
20092
20916
  });
20093
20917
 
20094
20918
  // ../engine/src/services/parallelCoordinator.ts
20095
- import { cpus, freemem, totalmem } from "os";
20096
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, readdirSync as readdirSync5 } from "fs";
20919
+ import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
20920
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync7 } from "fs";
20097
20921
  import { copyFile, rename } from "fs/promises";
20098
- import { join as join11 } from "path";
20922
+ import { join as join16 } from "path";
20099
20923
  function calculateOptimalWorkers(totalFrames, requested, config) {
20100
20924
  const effectiveMaxWorkers = (() => {
20101
20925
  const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
@@ -20111,9 +20935,9 @@ function calculateOptimalWorkers(totalFrames, requested, config) {
20111
20935
  return Math.max(MIN_WORKERS, Math.min(effectiveMaxWorkers, requested));
20112
20936
  }
20113
20937
  if (totalFrames < MIN_FRAMES_PER_WORKER * 2) return 1;
20114
- const cpuCount = cpus().length;
20938
+ const cpuCount = cpus2().length;
20115
20939
  const cpuBasedWorkers = Math.max(1, cpuCount - 2);
20116
- const totalMemoryMB = Math.round(totalmem() / (1024 * 1024));
20940
+ const totalMemoryMB = Math.round(totalmem2() / (1024 * 1024));
20117
20941
  const memoryBasedWorkers = Math.max(1, Math.floor(totalMemoryMB * 0.5 / MEMORY_PER_WORKER_MB));
20118
20942
  const frameBasedWorkers = Math.floor(totalFrames / MIN_FRAMES_PER_WORKER);
20119
20943
  const optimal = Math.min(cpuBasedWorkers, memoryBasedWorkers, frameBasedWorkers);
@@ -20138,7 +20962,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
20138
20962
  workerId: i,
20139
20963
  startFrame,
20140
20964
  endFrame,
20141
- outputDir: join11(workDir, `worker-${i}`)
20965
+ outputDir: join16(workDir, `worker-${i}`)
20142
20966
  });
20143
20967
  }
20144
20968
  return tasks;
@@ -20146,7 +20970,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
20146
20970
  async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
20147
20971
  const startTime = Date.now();
20148
20972
  let framesCaptured = 0;
20149
- if (!existsSync12(task.outputDir)) mkdirSync10(task.outputDir, { recursive: true });
20973
+ if (!existsSync18(task.outputDir)) mkdirSync13(task.outputDir, { recursive: true });
20150
20974
  let session = null;
20151
20975
  let perf;
20152
20976
  try {
@@ -20236,17 +21060,17 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
20236
21060
  return results;
20237
21061
  }
20238
21062
  async function mergeWorkerFrames(workDir, tasks, outputDir) {
20239
- if (!existsSync12(outputDir)) mkdirSync10(outputDir, { recursive: true });
21063
+ if (!existsSync18(outputDir)) mkdirSync13(outputDir, { recursive: true });
20240
21064
  let totalFrames = 0;
20241
21065
  const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
20242
21066
  for (const task of sortedTasks) {
20243
- if (!existsSync12(task.outputDir)) {
21067
+ if (!existsSync18(task.outputDir)) {
20244
21068
  continue;
20245
21069
  }
20246
- const files = readdirSync5(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
21070
+ const files = readdirSync7(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
20247
21071
  const copyTasks = files.map(async (file) => {
20248
- const sourcePath = join11(task.outputDir, file);
20249
- const targetPath = join11(outputDir, file);
21072
+ const sourcePath = join16(task.outputDir, file);
21073
+ const targetPath = join16(outputDir, file);
20250
21074
  try {
20251
21075
  await rename(sourcePath, targetPath);
20252
21076
  } catch {
@@ -20273,10 +21097,10 @@ var init_parallelCoordinator = __esm({
20273
21097
  });
20274
21098
 
20275
21099
  // ../engine/src/services/fileServer.ts
20276
- import { Hono } from "hono";
21100
+ import { Hono as Hono2 } from "hono";
20277
21101
  import { serve } from "@hono/node-server";
20278
- import { readFileSync as readFileSync3, existsSync as existsSync13, statSync as statSync3 } from "fs";
20279
- import { join as join12, extname as extname2 } from "path";
21102
+ import { readFileSync as readFileSync10, existsSync as existsSync19, statSync as statSync5 } from "fs";
21103
+ import { join as join17, extname as extname2 } from "path";
20280
21104
  var init_fileServer = __esm({
20281
21105
  "../engine/src/services/fileServer.ts"() {
20282
21106
  "use strict";
@@ -20306,8 +21130,8 @@ var init_src2 = __esm({
20306
21130
 
20307
21131
  // ../producer/src/services/hyperframeRuntimeLoader.ts
20308
21132
  import { createHash as createHash2 } from "crypto";
20309
- import { existsSync as existsSync14, readFileSync as readFileSync4 } from "fs";
20310
- import { dirname as dirname6, resolve as resolve4 } from "path";
21133
+ import { existsSync as existsSync20, readFileSync as readFileSync11 } from "fs";
21134
+ import { dirname as dirname7, resolve as resolve7 } from "path";
20311
21135
  import { fileURLToPath as fileURLToPath2 } from "url";
20312
21136
  function resolveHyperframeManifestPath() {
20313
21137
  if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
@@ -20319,7 +21143,7 @@ function resolveHyperframeManifestPath() {
20319
21143
  MODULE_RELATIVE_MANIFEST_PATH
20320
21144
  ];
20321
21145
  for (const candidate of candidates) {
20322
- if (existsSync14(candidate)) {
21146
+ if (existsSync20(candidate)) {
20323
21147
  return candidate;
20324
21148
  }
20325
21149
  }
@@ -20330,12 +21154,12 @@ function getVerifiedHyperframeRuntimeSource() {
20330
21154
  }
20331
21155
  function resolveVerifiedHyperframeRuntime() {
20332
21156
  const manifestPath = resolveHyperframeManifestPath();
20333
- if (!existsSync14(manifestPath)) {
21157
+ if (!existsSync20(manifestPath)) {
20334
21158
  throw new Error(
20335
21159
  `[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
20336
21160
  );
20337
21161
  }
20338
- const manifestRaw = readFileSync4(manifestPath, "utf8");
21162
+ const manifestRaw = readFileSync11(manifestPath, "utf8");
20339
21163
  const manifest = JSON.parse(manifestRaw);
20340
21164
  const runtimeFileName = manifest.artifacts?.iife;
20341
21165
  if (!runtimeFileName || !manifest.sha256) {
@@ -20343,11 +21167,11 @@ function resolveVerifiedHyperframeRuntime() {
20343
21167
  `[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`
20344
21168
  );
20345
21169
  }
20346
- const runtimePath = resolve4(dirname6(manifestPath), runtimeFileName);
20347
- if (!existsSync14(runtimePath)) {
21170
+ const runtimePath = resolve7(dirname7(manifestPath), runtimeFileName);
21171
+ if (!existsSync20(runtimePath)) {
20348
21172
  throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
20349
21173
  }
20350
- const runtimeSource = readFileSync4(runtimePath, "utf8");
21174
+ const runtimeSource = readFileSync11(runtimePath, "utf8");
20351
21175
  const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
20352
21176
  if (runtimeSha !== manifest.sha256) {
20353
21177
  throw new Error(
@@ -20366,28 +21190,28 @@ var PRODUCER_DIR, SIBLING_MANIFEST_PATH, MODULE_RELATIVE_MANIFEST_PATH, CWD_RELA
20366
21190
  var init_hyperframeRuntimeLoader = __esm({
20367
21191
  "../producer/src/services/hyperframeRuntimeLoader.ts"() {
20368
21192
  "use strict";
20369
- PRODUCER_DIR = dirname6(fileURLToPath2(import.meta.url));
20370
- SIBLING_MANIFEST_PATH = resolve4(PRODUCER_DIR, "hyperframe.manifest.json");
20371
- MODULE_RELATIVE_MANIFEST_PATH = resolve4(
21193
+ PRODUCER_DIR = dirname7(fileURLToPath2(import.meta.url));
21194
+ SIBLING_MANIFEST_PATH = resolve7(PRODUCER_DIR, "hyperframe.manifest.json");
21195
+ MODULE_RELATIVE_MANIFEST_PATH = resolve7(
20372
21196
  PRODUCER_DIR,
20373
21197
  "../../../core/dist/hyperframe.manifest.json"
20374
21198
  );
20375
21199
  CWD_RELATIVE_MANIFEST_PATHS = [
20376
21200
  // When bundled to a single file (dist/public-server.js), the manifest
20377
21201
  // is copied as a sibling by build.mjs
20378
- resolve4(PRODUCER_DIR, "hyperframe.manifest.json"),
20379
- resolve4(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
20380
- resolve4(process.cwd(), "../core/dist/hyperframe.manifest.json"),
20381
- resolve4(process.cwd(), "core/dist/hyperframe.manifest.json")
21202
+ resolve7(PRODUCER_DIR, "hyperframe.manifest.json"),
21203
+ resolve7(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
21204
+ resolve7(process.cwd(), "../core/dist/hyperframe.manifest.json"),
21205
+ resolve7(process.cwd(), "core/dist/hyperframe.manifest.json")
20382
21206
  ];
20383
21207
  }
20384
21208
  });
20385
21209
 
20386
21210
  // ../producer/src/services/fileServer.ts
20387
- import { Hono as Hono2 } from "hono";
21211
+ import { Hono as Hono3 } from "hono";
20388
21212
  import { serve as serve2 } from "@hono/node-server";
20389
- import { readFileSync as readFileSync5, existsSync as existsSync15, statSync as statSync4 } from "fs";
20390
- import { join as join13, extname as extname3 } from "path";
21213
+ import { readFileSync as readFileSync12, existsSync as existsSync21, statSync as statSync6 } from "fs";
21214
+ import { join as join18, extname as extname3 } from "path";
20391
21215
  function stripEmbeddedRuntimeScripts2(html) {
20392
21216
  if (!html) return html;
20393
21217
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -20451,38 +21275,38 @@ function createFileServer2(options) {
20451
21275
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
20452
21276
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
20453
21277
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
20454
- const app = new Hono2();
21278
+ const app = new Hono3();
20455
21279
  app.get("/*", (c2) => {
20456
21280
  let requestPath = c2.req.path;
20457
21281
  if (requestPath === "/") requestPath = "/index.html";
20458
21282
  const relativePath = requestPath.replace(/^\//, "");
20459
- const compiledPath = compiledDir ? join13(compiledDir, relativePath) : null;
21283
+ const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
20460
21284
  const hasCompiledFile = Boolean(
20461
- compiledPath && existsSync15(compiledPath) && statSync4(compiledPath).isFile()
21285
+ compiledPath && existsSync21(compiledPath) && statSync6(compiledPath).isFile()
20462
21286
  );
20463
- const filePath = hasCompiledFile ? compiledPath : join13(projectDir, relativePath);
20464
- if (!existsSync15(filePath) || !statSync4(filePath).isFile()) {
21287
+ const filePath = hasCompiledFile ? compiledPath : join18(projectDir, relativePath);
21288
+ if (!existsSync21(filePath) || !statSync6(filePath).isFile()) {
20465
21289
  return c2.text("Not found", 404);
20466
21290
  }
20467
21291
  const ext = extname3(filePath).toLowerCase();
20468
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
21292
+ const contentType = MIME_TYPES2[ext] || "application/octet-stream";
20469
21293
  if (ext === ".html") {
20470
- const rawHtml = readFileSync5(filePath, "utf-8");
21294
+ const rawHtml = readFileSync12(filePath, "utf-8");
20471
21295
  const isIndex = relativePath === "index.html";
20472
21296
  const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
20473
21297
  return c2.text(html, 200, { "Content-Type": contentType });
20474
21298
  }
20475
- const content = readFileSync5(filePath);
21299
+ const content = readFileSync12(filePath);
20476
21300
  return new Response(content, {
20477
21301
  status: 200,
20478
21302
  headers: { "Content-Type": contentType }
20479
21303
  });
20480
21304
  });
20481
- return new Promise((resolve17) => {
21305
+ return new Promise((resolve20) => {
20482
21306
  const server = serve2({ fetch: app.fetch, port }, (info) => {
20483
21307
  const actualPort = info.port;
20484
21308
  const url = `http://localhost:${actualPort}`;
20485
- resolve17({
21309
+ resolve20({
20486
21310
  url,
20487
21311
  port: actualPort,
20488
21312
  close: () => server.close()
@@ -20490,12 +21314,12 @@ function createFileServer2(options) {
20490
21314
  });
20491
21315
  });
20492
21316
  }
20493
- var MIME_TYPES, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
21317
+ var MIME_TYPES2, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
20494
21318
  var init_fileServer2 = __esm({
20495
21319
  "../producer/src/services/fileServer.ts"() {
20496
21320
  "use strict";
20497
21321
  init_hyperframeRuntimeLoader();
20498
- MIME_TYPES = {
21322
+ MIME_TYPES2 = {
20499
21323
  ".html": "text/html; charset=utf-8",
20500
21324
  ".css": "text/css; charset=utf-8",
20501
21325
  ".js": "application/javascript; charset=utf-8",
@@ -20924,8 +21748,8 @@ var init_deterministicFonts = __esm({
20924
21748
  });
20925
21749
 
20926
21750
  // ../producer/src/services/htmlCompiler.ts
20927
- import { readFileSync as readFileSync6, existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
20928
- import { join as join14, dirname as dirname7, resolve as resolve5 } from "path";
21751
+ import { readFileSync as readFileSync13, existsSync as existsSync22, mkdirSync as mkdirSync14 } from "fs";
21752
+ import { join as join19, dirname as dirname8, resolve as resolve8 } from "path";
20929
21753
  function dedupeElementsById(elements) {
20930
21754
  const deduped = /* @__PURE__ */ new Map();
20931
21755
  for (const element of elements) {
@@ -20936,16 +21760,16 @@ function dedupeElementsById(elements) {
20936
21760
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
20937
21761
  let filePath = src;
20938
21762
  if (isHttpUrl(src)) {
20939
- if (!existsSync16(downloadDir)) mkdirSync11(downloadDir, { recursive: true });
21763
+ if (!existsSync22(downloadDir)) mkdirSync14(downloadDir, { recursive: true });
20940
21764
  try {
20941
21765
  filePath = await downloadToTemp(src, downloadDir);
20942
21766
  } catch {
20943
21767
  return { duration: 0, resolvedPath: src };
20944
21768
  }
20945
21769
  } else if (!filePath.startsWith("/")) {
20946
- filePath = join14(baseDir, filePath);
21770
+ filePath = join19(baseDir, filePath);
20947
21771
  }
20948
- if (!existsSync16(filePath)) {
21772
+ if (!existsSync22(filePath)) {
20949
21773
  return { duration: 0, resolvedPath: filePath };
20950
21774
  }
20951
21775
  const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
@@ -21008,14 +21832,14 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
21008
21832
  const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
21009
21833
  const absoluteStart = parentOffset + elStart;
21010
21834
  const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
21011
- const filePath = resolve5(projectDir, srcPath);
21835
+ const filePath = resolve8(projectDir, srcPath);
21012
21836
  if (visited.has(filePath)) {
21013
21837
  continue;
21014
21838
  }
21015
- if (!existsSync16(filePath)) {
21839
+ if (!existsSync22(filePath)) {
21016
21840
  continue;
21017
21841
  }
21018
- const rawSubHtml = readFileSync6(filePath, "utf-8");
21842
+ const rawSubHtml = readFileSync13(filePath, "utf-8");
21019
21843
  const nestedVisited = new Set(visited);
21020
21844
  nestedVisited.add(filePath);
21021
21845
  workItems.push({ srcPath, absoluteStart, absoluteEnd, filePath, rawSubHtml, nestedVisited });
@@ -21024,7 +21848,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
21024
21848
  workItems.map(async (item) => {
21025
21849
  const { html: compiledSub } = await compileHtmlFile(
21026
21850
  item.rawSubHtml,
21027
- dirname7(item.filePath),
21851
+ dirname8(item.filePath),
21028
21852
  downloadDir
21029
21853
  );
21030
21854
  const nested = await parseSubCompositions(
@@ -21208,9 +22032,9 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
21208
22032
  if (!srcPath) continue;
21209
22033
  let compHtml = subCompositions.get(srcPath) || null;
21210
22034
  if (!compHtml) {
21211
- const filePath = resolve5(projectDir, srcPath);
21212
- if (existsSync16(filePath)) {
21213
- compHtml = readFileSync6(filePath, "utf-8");
22035
+ const filePath = resolve8(projectDir, srcPath);
22036
+ if (existsSync22(filePath)) {
22037
+ compHtml = readFileSync13(filePath, "utf-8");
21214
22038
  }
21215
22039
  }
21216
22040
  if (!compHtml) {
@@ -21330,7 +22154,7 @@ ${html}
21330
22154
  </html>`;
21331
22155
  }
21332
22156
  async function compileForRender(projectDir, htmlPath, downloadDir) {
21333
- const rawHtml = readFileSync6(htmlPath, "utf-8");
22157
+ const rawHtml = readFileSync13(htmlPath, "utf-8");
21334
22158
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
21335
22159
  rawHtml,
21336
22160
  projectDir,
@@ -21518,15 +22342,15 @@ var init_logger = __esm({
21518
22342
 
21519
22343
  // ../producer/src/services/renderOrchestrator.ts
21520
22344
  import {
21521
- existsSync as existsSync17,
21522
- mkdirSync as mkdirSync12,
22345
+ existsSync as existsSync23,
22346
+ mkdirSync as mkdirSync15,
21523
22347
  rmSync as rmSync5,
21524
- readFileSync as readFileSync7,
21525
- writeFileSync as writeFileSync4,
22348
+ readFileSync as readFileSync14,
22349
+ writeFileSync as writeFileSync6,
21526
22350
  copyFileSync as copyFileSync2,
21527
22351
  appendFileSync
21528
22352
  } from "fs";
21529
- import { join as join15, dirname as dirname8, resolve as resolve6 } from "path";
22353
+ import { join as join20, dirname as dirname9, resolve as resolve9 } from "path";
21530
22354
  import { randomUUID as randomUUID2 } from "crypto";
21531
22355
  import { freemem as freemem2 } from "os";
21532
22356
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -21582,13 +22406,13 @@ function installDebugLogger(logPath, log = defaultLogger) {
21582
22406
  };
21583
22407
  }
21584
22408
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
21585
- const compileDir = join15(workDir, "compiled");
21586
- mkdirSync12(compileDir, { recursive: true });
21587
- writeFileSync4(join15(compileDir, "index.html"), compiled.html, "utf-8");
22409
+ const compileDir = join20(workDir, "compiled");
22410
+ mkdirSync15(compileDir, { recursive: true });
22411
+ writeFileSync6(join20(compileDir, "index.html"), compiled.html, "utf-8");
21588
22412
  for (const [srcPath, html] of compiled.subCompositions) {
21589
- const outPath = join15(compileDir, srcPath);
21590
- mkdirSync12(dirname8(outPath), { recursive: true });
21591
- writeFileSync4(outPath, html, "utf-8");
22413
+ const outPath = join20(compileDir, srcPath);
22414
+ mkdirSync15(dirname9(outPath), { recursive: true });
22415
+ writeFileSync6(outPath, html, "utf-8");
21592
22416
  }
21593
22417
  if (includeSummary) {
21594
22418
  const summary = {
@@ -21611,7 +22435,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
21611
22435
  })),
21612
22436
  subCompositions: Array.from(compiled.subCompositions.keys())
21613
22437
  };
21614
- writeFileSync4(join15(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
22438
+ writeFileSync6(join20(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
21615
22439
  }
21616
22440
  }
21617
22441
  function createRenderJob(config) {
@@ -21654,10 +22478,10 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
21654
22478
  return document2.toString();
21655
22479
  }
21656
22480
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
21657
- const moduleDir = dirname8(fileURLToPath3(import.meta.url));
21658
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve6(process.env.PRODUCER_RENDERS_DIR, "..") : resolve6(moduleDir, "../..");
21659
- const debugDir = join15(producerRoot, ".debug");
21660
- const workDir = job.config.debug ? join15(debugDir, job.id) : join15(dirname8(outputPath), `work-${job.id}`);
22481
+ const moduleDir = dirname9(fileURLToPath3(import.meta.url));
22482
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve9(process.env.PRODUCER_RENDERS_DIR, "..") : resolve9(moduleDir, "../..");
22483
+ const debugDir = join20(producerRoot, ".debug");
22484
+ const workDir = job.config.debug ? join20(debugDir, job.id) : join20(dirname9(outputPath), `work-${job.id}`);
21661
22485
  const pipelineStart = Date.now();
21662
22486
  const log = job.config.logger ?? defaultLogger;
21663
22487
  let fileServer = null;
@@ -21665,7 +22489,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21665
22489
  let lastBrowserConsole = [];
21666
22490
  let restoreLogger = null;
21667
22491
  const perfStages = {};
21668
- const perfOutputPath = join15(workDir, "perf-summary.json");
22492
+ const perfOutputPath = join20(workDir, "perf-summary.json");
21669
22493
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
21670
22494
  const outputFormat = job.config.format ?? "mp4";
21671
22495
  const isWebm = outputFormat === "webm";
@@ -21683,28 +22507,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21683
22507
  };
21684
22508
  job.startedAt = /* @__PURE__ */ new Date();
21685
22509
  assertNotAborted();
21686
- if (!existsSync17(workDir)) mkdirSync12(workDir, { recursive: true });
22510
+ if (!existsSync23(workDir)) mkdirSync15(workDir, { recursive: true });
21687
22511
  if (job.config.debug) {
21688
- const logPath = join15(workDir, "render.log");
22512
+ const logPath = join20(workDir, "render.log");
21689
22513
  restoreLogger = installDebugLogger(logPath, log);
21690
22514
  }
21691
22515
  const entryFile = job.config.entryFile || "index.html";
21692
- let htmlPath = join15(projectDir, entryFile);
21693
- if (!existsSync17(htmlPath)) {
22516
+ let htmlPath = join20(projectDir, entryFile);
22517
+ if (!existsSync23(htmlPath)) {
21694
22518
  throw new Error(`Entry file not found: ${htmlPath}`);
21695
22519
  }
21696
22520
  assertNotAborted();
21697
- const rawEntry = readFileSync7(htmlPath, "utf-8");
22521
+ const rawEntry = readFileSync14(htmlPath, "utf-8");
21698
22522
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
21699
- const wrapperPath = join15(workDir, "standalone-entry.html");
21700
- const projectIndexPath = join15(projectDir, "index.html");
21701
- if (!existsSync17(projectIndexPath)) {
22523
+ const wrapperPath = join20(workDir, "standalone-entry.html");
22524
+ const projectIndexPath = join20(projectDir, "index.html");
22525
+ if (!existsSync23(projectIndexPath)) {
21702
22526
  throw new Error(
21703
22527
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
21704
22528
  );
21705
22529
  }
21706
22530
  const standaloneHtml = extractStandaloneEntryFromIndex(
21707
- readFileSync7(projectIndexPath, "utf-8"),
22531
+ readFileSync14(projectIndexPath, "utf-8"),
21708
22532
  entryFile
21709
22533
  );
21710
22534
  if (!standaloneHtml) {
@@ -21712,7 +22536,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21712
22536
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
21713
22537
  );
21714
22538
  }
21715
- writeFileSync4(wrapperPath, standaloneHtml, "utf-8");
22539
+ writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
21716
22540
  htmlPath = wrapperPath;
21717
22541
  log.info("Extracted standalone entry from index.html host context", {
21718
22542
  entryFile
@@ -21721,7 +22545,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21721
22545
  const stage1Start = Date.now();
21722
22546
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
21723
22547
  const compileStart = Date.now();
21724
- let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
22548
+ let compiled = await compileForRender(projectDir, htmlPath, join20(workDir, "downloads"));
21725
22549
  assertNotAborted();
21726
22550
  perfStages.compileOnlyMs = Date.now() - compileStart;
21727
22551
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
@@ -21750,7 +22574,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21750
22574
  reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
21751
22575
  fileServer = await createFileServer2({
21752
22576
  projectDir,
21753
- compiledDir: join15(workDir, "compiled"),
22577
+ compiledDir: join20(workDir, "compiled"),
21754
22578
  port: 0
21755
22579
  });
21756
22580
  assertNotAborted();
@@ -21763,7 +22587,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21763
22587
  };
21764
22588
  probeSession = await createCaptureSession(
21765
22589
  fileServer.url,
21766
- join15(workDir, "probe"),
22590
+ join20(workDir, "probe"),
21767
22591
  captureOpts,
21768
22592
  null,
21769
22593
  cfg
@@ -21795,7 +22619,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21795
22619
  compiled,
21796
22620
  resolutions,
21797
22621
  projectDir,
21798
- join15(workDir, "downloads")
22622
+ join20(workDir, "downloads")
21799
22623
  );
21800
22624
  assertNotAborted();
21801
22625
  composition.videos = compiled.videos;
@@ -21892,7 +22716,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21892
22716
  const extractionResult = await extractAllVideoFrames(
21893
22717
  composition.videos,
21894
22718
  projectDir,
21895
- { fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
22719
+ { fps: job.config.fps, outputDir: join20(workDir, "video-frames") },
21896
22720
  abortSignal
21897
22721
  );
21898
22722
  assertNotAborted();
@@ -21924,13 +22748,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21924
22748
  }
21925
22749
  const stage3Start = Date.now();
21926
22750
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
21927
- const audioOutputPath = join15(workDir, "audio.aac");
22751
+ const audioOutputPath = join20(workDir, "audio.aac");
21928
22752
  let hasAudio = false;
21929
22753
  if (composition.audios.length > 0) {
21930
22754
  const audioResult = await processCompositionAudio(
21931
22755
  composition.audios,
21932
22756
  projectDir,
21933
- join15(workDir, "audio-work"),
22757
+ join20(workDir, "audio-work"),
21934
22758
  audioOutputPath,
21935
22759
  job.duration,
21936
22760
  abortSignal
@@ -21946,13 +22770,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21946
22770
  if (!fileServer) {
21947
22771
  fileServer = await createFileServer2({
21948
22772
  projectDir,
21949
- compiledDir: join15(workDir, "compiled"),
22773
+ compiledDir: join20(workDir, "compiled"),
21950
22774
  port: 0
21951
22775
  });
21952
22776
  assertNotAborted();
21953
22777
  }
21954
- const framesDir = join15(workDir, "captured-frames");
21955
- if (!existsSync17(framesDir)) mkdirSync12(framesDir, { recursive: true });
22778
+ const framesDir = join20(workDir, "captured-frames");
22779
+ if (!existsSync23(framesDir)) mkdirSync15(framesDir, { recursive: true });
21956
22780
  const captureOptions = {
21957
22781
  width,
21958
22782
  height,
@@ -21962,7 +22786,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21962
22786
  };
21963
22787
  const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
21964
22788
  const videoExt = isWebm ? ".webm" : ".mp4";
21965
- const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
22789
+ const videoOnlyPath = join20(workDir, `video-only${videoExt}`);
21966
22790
  const preset = getEncoderPreset(job.config.quality, outputFormat);
21967
22791
  job.framesRendered = 0;
21968
22792
  let streamingEncoder = null;
@@ -22231,7 +23055,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22231
23055
  job.perfSummary = perfSummary;
22232
23056
  if (job.config.debug) {
22233
23057
  try {
22234
- writeFileSync4(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
23058
+ writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
22235
23059
  } catch (err) {
22236
23060
  log.debug("Failed to write perf summary", {
22237
23061
  perfOutputPath,
@@ -22240,8 +23064,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22240
23064
  }
22241
23065
  }
22242
23066
  if (job.config.debug) {
22243
- if (existsSync17(outputPath)) {
22244
- const debugOutput = join15(workDir, isWebm ? "output.webm" : "output.mp4");
23067
+ if (existsSync23(outputPath)) {
23068
+ const debugOutput = join20(workDir, isWebm ? "output.webm" : "output.mp4");
22245
23069
  copyFileSync2(outputPath, debugOutput);
22246
23070
  }
22247
23071
  } else {
@@ -22317,7 +23141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22317
23141
  await safeCleanup(
22318
23142
  "remove workDir (error)",
22319
23143
  () => {
22320
- if (existsSync17(workDir)) rmSync5(workDir, { recursive: true, force: true });
23144
+ if (existsSync23(workDir)) rmSync5(workDir, { recursive: true, force: true });
22321
23145
  },
22322
23146
  log
22323
23147
  );
@@ -22370,17 +23194,9 @@ var init_config3 = __esm({
22370
23194
  }
22371
23195
  });
22372
23196
 
22373
- // ../core/src/lint/index.ts
22374
- var init_lint = __esm({
22375
- "../core/src/lint/index.ts"() {
22376
- "use strict";
22377
- init_hyperframeLinter();
22378
- }
22379
- });
22380
-
22381
23197
  // ../producer/src/services/hyperframeLint.ts
22382
- import { existsSync as existsSync18, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
22383
- import { resolve as resolve7, join as join16 } from "path";
23198
+ import { existsSync as existsSync24, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
23199
+ import { resolve as resolve10, join as join21 } from "path";
22384
23200
  function isStringRecord(value) {
22385
23201
  if (!value || typeof value !== "object" || Array.isArray(value)) {
22386
23202
  return false;
@@ -22407,28 +23223,28 @@ function pickEntryFile(files, preferredEntryFile) {
22407
23223
  return null;
22408
23224
  }
22409
23225
  function readProjectEntryFile(projectDir, preferredEntryFile) {
22410
- const absProjectDir = resolve7(projectDir);
22411
- if (!existsSync18(absProjectDir) || !statSync5(absProjectDir).isDirectory()) {
23226
+ const absProjectDir = resolve10(projectDir);
23227
+ if (!existsSync24(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
22412
23228
  return { error: `Project directory not found: ${absProjectDir}` };
22413
23229
  }
22414
23230
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
22415
23231
  (value) => typeof value === "string" && value.trim().length > 0
22416
23232
  );
22417
23233
  for (const entryFile of entryCandidates) {
22418
- const absoluteEntryPath = resolve7(absProjectDir, entryFile);
23234
+ const absoluteEntryPath = resolve10(absProjectDir, entryFile);
22419
23235
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
22420
23236
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
22421
23237
  }
22422
- if (existsSync18(absoluteEntryPath) && statSync5(absoluteEntryPath).isFile()) {
23238
+ if (existsSync24(absoluteEntryPath) && statSync7(absoluteEntryPath).isFile()) {
22423
23239
  return {
22424
23240
  entryFile,
22425
- html: readFileSync8(absoluteEntryPath, "utf-8"),
23241
+ html: readFileSync15(absoluteEntryPath, "utf-8"),
22426
23242
  source: "projectDir"
22427
23243
  };
22428
23244
  }
22429
23245
  }
22430
23246
  return {
22431
- error: `No HTML entry file found in project directory: ${join16(absProjectDir, preferredEntryFile || "index.html")}`
23247
+ error: `No HTML entry file found in project directory: ${join21(absProjectDir, preferredEntryFile || "index.html")}`
22432
23248
  };
22433
23249
  }
22434
23250
  function prepareHyperframeLintBody(body) {
@@ -22470,43 +23286,43 @@ function runHyperframeLint(prepared) {
22470
23286
  var init_hyperframeLint = __esm({
22471
23287
  "../producer/src/services/hyperframeLint.ts"() {
22472
23288
  "use strict";
22473
- init_lint();
23289
+ init_lint2();
22474
23290
  }
22475
23291
  });
22476
23292
 
22477
23293
  // ../producer/src/utils/paths.ts
22478
- import { resolve as resolve8, basename, join as join17 } from "path";
23294
+ import { resolve as resolve11, basename, join as join22 } from "path";
22479
23295
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
22480
- const absoluteProjectDir = resolve8(projectDir);
23296
+ const absoluteProjectDir = resolve11(projectDir);
22481
23297
  const projectName = basename(absoluteProjectDir);
22482
- const resolvedOutputPath = outputPath ?? join17(rendersDir, `${projectName}.mp4`);
22483
- const absoluteOutputPath = resolve8(resolvedOutputPath);
23298
+ const resolvedOutputPath = outputPath ?? join22(rendersDir, `${projectName}.mp4`);
23299
+ const absoluteOutputPath = resolve11(resolvedOutputPath);
22484
23300
  return { absoluteProjectDir, absoluteOutputPath };
22485
23301
  }
22486
23302
  var DEFAULT_RENDERS_DIR;
22487
23303
  var init_paths = __esm({
22488
23304
  "../producer/src/utils/paths.ts"() {
22489
23305
  "use strict";
22490
- DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve8(new URL(import.meta.url).pathname, "../../..", "renders");
23306
+ DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve11(new URL(import.meta.url).pathname, "../../..", "renders");
22491
23307
  }
22492
23308
  });
22493
23309
 
22494
23310
  // ../producer/src/server.ts
22495
23311
  import {
22496
- existsSync as existsSync19,
22497
- mkdirSync as mkdirSync13,
22498
- statSync as statSync6,
23312
+ existsSync as existsSync25,
23313
+ mkdirSync as mkdirSync16,
23314
+ statSync as statSync8,
22499
23315
  mkdtempSync,
22500
- writeFileSync as writeFileSync5,
23316
+ writeFileSync as writeFileSync7,
22501
23317
  rmSync as rmSync6,
22502
23318
  createReadStream
22503
23319
  } from "fs";
22504
- import { resolve as resolve9, dirname as dirname9, join as join18 } from "path";
23320
+ import { resolve as resolve12, dirname as dirname10, join as join23 } from "path";
22505
23321
  import { tmpdir } from "os";
22506
23322
  import { parseArgs as parseArgs2 } from "util";
22507
23323
  import crypto from "crypto";
22508
- import { Hono as Hono3 } from "hono";
22509
- import { streamSSE } from "hono/streaming";
23324
+ import { Hono as Hono4 } from "hono";
23325
+ import { streamSSE as streamSSE2 } from "hono/streaming";
22510
23326
  import { serve as serve3 } from "@hono/node-server";
22511
23327
  function parseRenderOptions(body) {
22512
23328
  const fps = [24, 30, 60].includes(body.fps) ? body.fps : 30;
@@ -22516,18 +23332,19 @@ function parseRenderOptions(body) {
22516
23332
  const debug = body.debug === true;
22517
23333
  const outputPath = typeof body.outputPath === "string" && body.outputPath.trim().length > 0 ? body.outputPath : typeof body.output === "string" && body.output.trim().length > 0 ? body.output : null;
22518
23334
  const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
22519
- return { outputPath, fps, quality, workers, useGpu, debug, entryFile };
23335
+ const format = ["mp4", "webm"].includes(body.format) ? body.format : void 0;
23336
+ return { outputPath, fps, quality, workers, useGpu, debug, entryFile, format };
22520
23337
  }
22521
23338
  async function prepareRenderBody(body) {
22522
23339
  const options = parseRenderOptions(body);
22523
23340
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
22524
23341
  if (projectDir) {
22525
- const absProjectDir = resolve9(projectDir);
22526
- if (!existsSync19(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
23342
+ const absProjectDir = resolve12(projectDir);
23343
+ if (!existsSync25(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
22527
23344
  return { error: `Project directory not found: ${absProjectDir}` };
22528
23345
  }
22529
23346
  const entry = options.entryFile || "index.html";
22530
- if (!existsSync19(resolve9(absProjectDir, entry))) {
23347
+ if (!existsSync25(resolve12(absProjectDir, entry))) {
22531
23348
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
22532
23349
  }
22533
23350
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -22552,8 +23369,8 @@ async function prepareRenderBody(body) {
22552
23369
  }
22553
23370
  }
22554
23371
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir();
22555
- const tempProjectDir = mkdtempSync(join18(tempRoot, "producer-project-"));
22556
- writeFileSync5(join18(tempProjectDir, "index.html"), htmlContent, "utf-8");
23372
+ const tempProjectDir = mkdtempSync(join23(tempRoot, "producer-project-"));
23373
+ writeFileSync7(join23(tempProjectDir, "index.html"), htmlContent, "utf-8");
22557
23374
  return {
22558
23375
  prepared: {
22559
23376
  input: {
@@ -22568,7 +23385,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
22568
23385
  try {
22569
23386
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
22570
23387
  } catch (error) {
22571
- const fallbackPath = resolve9(rendersDir, `producer-fallback-${Date.now()}.mp4`);
23388
+ const fallbackPath = resolve12(rendersDir, `producer-fallback-${Date.now()}.mp4`);
22572
23389
  log.warn("Failed to resolve output path, using fallback", {
22573
23390
  fallback: fallbackPath,
22574
23391
  error: error instanceof Error ? error.message : String(error)
@@ -22673,8 +23490,8 @@ function createRenderHandlers(options = {}) {
22673
23490
  rendersDir,
22674
23491
  log
22675
23492
  );
22676
- const outputDir = dirname9(absoluteOutputPath);
22677
- if (!existsSync19(outputDir)) mkdirSync13(outputDir, { recursive: true });
23493
+ const outputDir = dirname10(absoluteOutputPath);
23494
+ if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
22678
23495
  log.info("render started", {
22679
23496
  requestId,
22680
23497
  projectDir: input.projectDir,
@@ -22684,6 +23501,7 @@ function createRenderHandlers(options = {}) {
22684
23501
  const job = createRenderJob({
22685
23502
  fps: input.fps,
22686
23503
  quality: input.quality,
23504
+ format: input.format,
22687
23505
  workers: input.workers,
22688
23506
  useGpu: input.useGpu,
22689
23507
  debug: input.debug,
@@ -22699,7 +23517,7 @@ function createRenderHandlers(options = {}) {
22699
23517
  log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
22700
23518
  }
22701
23519
  });
22702
- const fileSize = existsSync19(absoluteOutputPath) ? statSync6(absoluteOutputPath).size : 0;
23520
+ const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
22703
23521
  const durationMs = Date.now() - t0;
22704
23522
  const outputToken = store.register(absoluteOutputPath);
22705
23523
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
@@ -22745,7 +23563,7 @@ function createRenderHandlers(options = {}) {
22745
23563
  }
22746
23564
  };
22747
23565
  const renderStream = (c2) => {
22748
- return streamSSE(c2, async (stream) => {
23566
+ return streamSSE2(c2, async (stream) => {
22749
23567
  const requestId = getRequestId(c2);
22750
23568
  const t0 = Date.now();
22751
23569
  let body;
@@ -22781,12 +23599,13 @@ function createRenderHandlers(options = {}) {
22781
23599
  rendersDir,
22782
23600
  log
22783
23601
  );
22784
- const outputDir = dirname9(absoluteOutputPath);
22785
- if (!existsSync19(outputDir)) mkdirSync13(outputDir, { recursive: true });
23602
+ const outputDir = dirname10(absoluteOutputPath);
23603
+ if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
22786
23604
  log.info("render-stream started", { requestId, projectDir: input.projectDir });
22787
23605
  const job = createRenderJob({
22788
23606
  fps: input.fps,
22789
23607
  quality: input.quality,
23608
+ format: input.format,
22790
23609
  workers: input.workers,
22791
23610
  useGpu: input.useGpu,
22792
23611
  debug: input.debug,
@@ -22816,7 +23635,7 @@ function createRenderHandlers(options = {}) {
22816
23635
  },
22817
23636
  abortController.signal
22818
23637
  );
22819
- const fileSize = existsSync19(absoluteOutputPath) ? statSync6(absoluteOutputPath).size : 0;
23638
+ const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
22820
23639
  const outputToken = store.register(absoluteOutputPath);
22821
23640
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
22822
23641
  log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
@@ -22874,11 +23693,11 @@ function createRenderHandlers(options = {}) {
22874
23693
  if (!artifact) {
22875
23694
  return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
22876
23695
  }
22877
- if (!existsSync19(artifact.path)) {
23696
+ if (!existsSync25(artifact.path)) {
22878
23697
  store.delete(token);
22879
23698
  return c2.json({ success: false, error: "Output artifact file missing" }, 404);
22880
23699
  }
22881
- const stats = statSync6(artifact.path);
23700
+ const stats = statSync8(artifact.path);
22882
23701
  return new Response(createReadStream(artifact.path), {
22883
23702
  headers: {
22884
23703
  "content-type": "video/mp4",
@@ -22890,7 +23709,7 @@ function createRenderHandlers(options = {}) {
22890
23709
  return { render: render2, renderStream, lint, health, outputs };
22891
23710
  }
22892
23711
  function createProducerApp(options = {}) {
22893
- const app = new Hono3();
23712
+ const app = new Hono4();
22894
23713
  const handlers = createRenderHandlers(options);
22895
23714
  app.get("/health", handlers.health);
22896
23715
  app.post("/render", handlers.render);
@@ -22932,7 +23751,7 @@ var init_server = __esm({
22932
23751
  init_hyperframeLint();
22933
23752
  init_paths();
22934
23753
  init_logger();
22935
- entryScript = process.argv[1] ? resolve9(process.argv[1]) : "";
23754
+ entryScript = process.argv[1] ? resolve12(process.argv[1]) : "";
22936
23755
  isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
22937
23756
  if (isPublicServerEntry) {
22938
23757
  const { values } = parseArgs2({
@@ -23008,9 +23827,9 @@ __export(manager_exports2, {
23008
23827
  setBrowserPath: () => setBrowserPath
23009
23828
  });
23010
23829
  import { execSync } from "child_process";
23011
- import { existsSync as existsSync20, rmSync as rmSync7 } from "fs";
23830
+ import { existsSync as existsSync26, rmSync as rmSync7 } from "fs";
23012
23831
  import { homedir as homedir5 } from "os";
23013
- import { join as join19 } from "path";
23832
+ import { join as join24 } from "path";
23014
23833
  import { Browser, detectBrowserPlatform, getInstalledBrowsers, install } from "@puppeteer/browsers";
23015
23834
  function setBrowserPath(path) {
23016
23835
  _browserPathOverride = path;
@@ -23028,17 +23847,17 @@ function whichBinary2(name) {
23028
23847
  }
23029
23848
  }
23030
23849
  function findFromEnv2() {
23031
- if (_browserPathOverride && existsSync20(_browserPathOverride)) {
23850
+ if (_browserPathOverride && existsSync26(_browserPathOverride)) {
23032
23851
  return { executablePath: _browserPathOverride, source: "env" };
23033
23852
  }
23034
23853
  const envPath = process.env["HYPERFRAMES_BROWSER_PATH"];
23035
- if (envPath && existsSync20(envPath)) {
23854
+ if (envPath && existsSync26(envPath)) {
23036
23855
  return { executablePath: envPath, source: "env" };
23037
23856
  }
23038
23857
  return void 0;
23039
23858
  }
23040
23859
  async function findFromCache() {
23041
- if (!existsSync20(CACHE_DIR)) {
23860
+ if (!existsSync26(CACHE_DIR)) {
23042
23861
  return void 0;
23043
23862
  }
23044
23863
  const installed = await getInstalledBrowsers({ cacheDir: CACHE_DIR });
@@ -23050,7 +23869,7 @@ async function findFromCache() {
23050
23869
  }
23051
23870
  function findFromSystem2() {
23052
23871
  for (const p of SYSTEM_CHROME_PATHS) {
23053
- if (existsSync20(p)) {
23872
+ if (existsSync26(p)) {
23054
23873
  return { executablePath: p, source: "system" };
23055
23874
  }
23056
23875
  }
@@ -23070,21 +23889,21 @@ async function findBrowser() {
23070
23889
  async function ensureBrowser(options) {
23071
23890
  const existing = await findBrowser();
23072
23891
  if (existing) return existing;
23073
- const platform2 = detectBrowserPlatform();
23074
- if (!platform2) {
23892
+ const platform4 = detectBrowserPlatform();
23893
+ if (!platform4) {
23075
23894
  throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`);
23076
23895
  }
23077
23896
  const installed = await install({
23078
23897
  cacheDir: CACHE_DIR,
23079
23898
  browser: Browser.CHROMEHEADLESSSHELL,
23080
23899
  buildId: CHROME_VERSION,
23081
- platform: platform2,
23900
+ platform: platform4,
23082
23901
  downloadProgressCallback: options?.onProgress
23083
23902
  });
23084
23903
  return { executablePath: installed.executablePath, source: "download" };
23085
23904
  }
23086
23905
  function clearBrowser() {
23087
- if (!existsSync20(CACHE_DIR)) {
23906
+ if (!existsSync26(CACHE_DIR)) {
23088
23907
  return false;
23089
23908
  }
23090
23909
  rmSync7(CACHE_DIR, { recursive: true, force: true });
@@ -23095,7 +23914,7 @@ var init_manager2 = __esm({
23095
23914
  "src/browser/manager.ts"() {
23096
23915
  "use strict";
23097
23916
  CHROME_VERSION = "131.0.6778.85";
23098
- CACHE_DIR = join19(homedir5(), ".cache", "hyperframes", "chrome");
23917
+ CACHE_DIR = join24(homedir5(), ".cache", "hyperframes", "chrome");
23099
23918
  SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
23100
23919
  "/usr/bin/google-chrome",
23101
23920
  "/usr/bin/google-chrome-stable",
@@ -23110,21 +23929,21 @@ var studioServer_exports = {};
23110
23929
  __export(studioServer_exports, {
23111
23930
  createStudioServer: () => createStudioServer
23112
23931
  });
23113
- import { Hono as Hono4 } from "hono";
23114
- import { streamSSE as streamSSE2 } from "hono/streaming";
23115
- import { existsSync as existsSync21, readFileSync as readFileSync9, readdirSync as readdirSync6, statSync as statSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync14 } from "fs";
23116
- import { resolve as resolve10, join as join20, sep as sep2, basename as basename2, dirname as dirname10, extname as extname4 } from "path";
23932
+ import { Hono as Hono5 } from "hono";
23933
+ import { streamSSE as streamSSE3 } from "hono/streaming";
23934
+ import { existsSync as existsSync27, readFileSync as readFileSync16, writeFileSync as writeFileSync8, statSync as statSync9 } from "fs";
23935
+ import { resolve as resolve13, join as join25, basename as basename2 } from "path";
23117
23936
  function resolveDistDir() {
23118
- const builtPath = resolve10(__dirname, "studio");
23119
- if (existsSync21(resolve10(builtPath, "index.html"))) return builtPath;
23120
- const devPath = resolve10(__dirname, "..", "..", "..", "studio", "dist");
23121
- if (existsSync21(resolve10(devPath, "index.html"))) return devPath;
23937
+ const builtPath = resolve13(__dirname, "studio");
23938
+ if (existsSync27(resolve13(builtPath, "index.html"))) return builtPath;
23939
+ const devPath = resolve13(__dirname, "..", "..", "..", "studio", "dist");
23940
+ if (existsSync27(resolve13(devPath, "index.html"))) return devPath;
23122
23941
  return builtPath;
23123
23942
  }
23124
23943
  function resolveRuntimePath() {
23125
- const builtPath = resolve10(__dirname, "hyperframe-runtime.js");
23126
- if (existsSync21(builtPath)) return builtPath;
23127
- const devPath = resolve10(
23944
+ const builtPath = resolve13(__dirname, "hyperframe-runtime.js");
23945
+ if (existsSync27(builtPath)) return builtPath;
23946
+ const devPath = resolve13(
23128
23947
  __dirname,
23129
23948
  "..",
23130
23949
  "..",
@@ -23133,96 +23952,97 @@ function resolveRuntimePath() {
23133
23952
  "dist",
23134
23953
  "hyperframe.runtime.iife.js"
23135
23954
  );
23136
- if (existsSync21(devPath)) return devPath;
23955
+ if (existsSync27(devPath)) return devPath;
23137
23956
  return builtPath;
23138
23957
  }
23139
- function isSafePath(base, resolved) {
23140
- const norm = resolve10(base) + sep2;
23141
- return resolved.startsWith(norm) || resolved === resolve10(base);
23142
- }
23143
- function getMimeType(filePath) {
23144
- const ext = extname4(filePath).toLowerCase();
23145
- return MIME_TYPES2[ext] ?? "application/octet-stream";
23146
- }
23147
- function walkDir(dir, prefix = "") {
23148
- const files = [];
23149
- for (const entry of readdirSync6(dir, { withFileTypes: true })) {
23150
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
23151
- if (entry.isDirectory()) {
23152
- files.push(...walkDir(join20(dir, entry.name), rel));
23153
- } else {
23154
- files.push(rel);
23155
- }
23156
- }
23157
- return files;
23158
- }
23159
- function serveStaticFile(filePath) {
23160
- if (!existsSync21(filePath) || !statSync7(filePath).isFile()) return null;
23161
- const mime = getMimeType(filePath);
23162
- const content = readFileSync9(filePath);
23163
- return new Response(content, {
23164
- headers: { "Content-Type": mime, "Cache-Control": "no-store" }
23165
- });
23166
- }
23167
- function buildSubCompositionHtml(projectDir, compPath, runtimeUrl) {
23168
- const compFile = resolve10(projectDir, compPath);
23169
- if (!isSafePath(projectDir, compFile) || !existsSync21(compFile) || !statSync7(compFile).isFile()) {
23170
- return null;
23171
- }
23172
- let rawComp = readFileSync9(compFile, "utf-8");
23173
- const templateMatch = rawComp.match(/<template>([\s\S]*)<\/template>/i);
23174
- let content = (templateMatch ? templateMatch[1] : rawComp) ?? rawComp;
23175
- content = content.replace(
23176
- /(<[^>]*?)(data-composition-src=["']([^"']+)["'])([^>]*>)/g,
23177
- (_match, before2, srcAttr, src, after2) => {
23178
- const nestedFile = join20(projectDir, src);
23179
- if (!existsSync21(nestedFile)) return before2 + srcAttr + after2;
23180
- const nestedRaw = readFileSync9(nestedFile, "utf-8");
23181
- const nestedTemplate = nestedRaw.match(/<template>([\s\S]*)<\/template>/i);
23182
- const nestedContent = (nestedTemplate ? nestedTemplate[1] : nestedRaw) ?? nestedRaw;
23183
- const styles = [];
23184
- const scripts = [];
23185
- let body = nestedContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_2, css) => {
23186
- styles.push(css);
23187
- return "";
23188
- }).replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, (_2, js) => {
23189
- scripts.push(js);
23190
- return "";
23191
- });
23192
- const innerRootMatch = body.match(
23193
- /<([a-z][a-z0-9]*)\b[^>]*data-composition-id[^>]*>([\s\S]*)<\/\1>/i
23194
- );
23195
- const innerHTML = innerRootMatch ? innerRootMatch[2] : body;
23196
- return before2 + srcAttr + after2.replace(/>$/, ">") + innerHTML + (styles.length ? `<style>${styles.join("\n")}</style>` : "") + (scripts.length ? `<script>${scripts.map((s) => `(function(){try{${s}}catch(e){}})();`).join("\n")}</script>` : "");
23197
- }
23198
- );
23199
- return `<!DOCTYPE html>
23200
- <html>
23201
- <head>
23202
- <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
23203
- <script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>
23204
- </head>
23205
- <body>
23206
- ${content}
23207
- </body>
23208
- </html>`;
23209
- }
23210
23958
  function createStudioServer(options) {
23211
23959
  const { projectDir } = options;
23212
23960
  const projectId = basename2(projectDir);
23213
23961
  const studioDir = resolveDistDir();
23214
23962
  const runtimePath = resolveRuntimePath();
23215
23963
  const watcher = createProjectWatcher(projectDir);
23216
- const app = new Hono4();
23964
+ const project = { id: projectId, dir: projectDir, title: projectId };
23965
+ const adapter2 = {
23966
+ listProjects: () => [project],
23967
+ resolveProject: (id) => id === projectId ? project : null,
23968
+ async bundle(dir) {
23969
+ try {
23970
+ const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
23971
+ let html = await bundleToSingleHtml2(dir);
23972
+ html = html.replace(
23973
+ 'data-hyperframes-preview-runtime="1" src=""',
23974
+ 'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
23975
+ );
23976
+ return html;
23977
+ } catch {
23978
+ return null;
23979
+ }
23980
+ },
23981
+ async lint(html, opts) {
23982
+ const { lintHyperframeHtml: lintHyperframeHtml2 } = await Promise.resolve().then(() => (init_lint2(), lint_exports));
23983
+ return lintHyperframeHtml2(html, opts);
23984
+ },
23985
+ runtimeUrl: "/api/runtime.js",
23986
+ rendersDir: () => join25(projectDir, "renders"),
23987
+ startRender(opts) {
23988
+ const state = {
23989
+ id: opts.jobId,
23990
+ status: "rendering",
23991
+ progress: 0,
23992
+ outputPath: opts.outputPath
23993
+ };
23994
+ (async () => {
23995
+ try {
23996
+ const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
23997
+ const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
23998
+ try {
23999
+ const browser = await ensureBrowser2();
24000
+ if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
24001
+ process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
24002
+ }
24003
+ } catch {
24004
+ }
24005
+ const job = createRenderJob2({
24006
+ fps: opts.fps,
24007
+ quality: opts.quality,
24008
+ format: opts.format
24009
+ });
24010
+ const startTime = Date.now();
24011
+ const onProgress = (j2) => {
24012
+ state.progress = j2.progress;
24013
+ if (j2.currentStage) state.stage = j2.currentStage;
24014
+ };
24015
+ await executeRenderJob2(job, opts.project.dir, opts.outputPath, onProgress);
24016
+ state.status = "complete";
24017
+ state.progress = 100;
24018
+ const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
24019
+ writeFileSync8(
24020
+ metaPath,
24021
+ JSON.stringify({ status: "complete", durationMs: Date.now() - startTime })
24022
+ );
24023
+ } catch (err) {
24024
+ state.status = "failed";
24025
+ state.error = err instanceof Error ? err.message : String(err);
24026
+ try {
24027
+ const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
24028
+ writeFileSync8(metaPath, JSON.stringify({ status: "failed" }));
24029
+ } catch {
24030
+ }
24031
+ }
24032
+ })();
24033
+ return state;
24034
+ }
24035
+ };
24036
+ const app = new Hono5();
23217
24037
  app.get("/api/runtime.js", (c2) => {
23218
- if (!existsSync21(runtimePath)) return c2.text("runtime not built", 404);
23219
- return c2.body(readFileSync9(runtimePath, "utf-8"), 200, {
24038
+ if (!existsSync27(runtimePath)) return c2.text("runtime not built", 404);
24039
+ return c2.body(readFileSync16(runtimePath, "utf-8"), 200, {
23220
24040
  "Content-Type": "text/javascript",
23221
24041
  "Cache-Control": "no-store"
23222
24042
  });
23223
24043
  });
23224
24044
  app.get("/api/events", (c2) => {
23225
- return streamSSE2(c2, async (stream) => {
24045
+ return streamSSE3(c2, async (stream) => {
23226
24046
  const listener = () => {
23227
24047
  stream.writeSSE({ event: "file-change", data: "{}" }).catch(() => {
23228
24048
  });
@@ -23233,214 +24053,49 @@ function createStudioServer(options) {
23233
24053
  }
23234
24054
  });
23235
24055
  });
23236
- app.get("/api/projects", (c2) => {
23237
- return c2.json({ projects: [{ id: projectId, title: projectId }] });
23238
- });
23239
- app.get("/api/projects/:id", (c2) => {
23240
- const id = c2.req.param("id");
23241
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23242
- const files = walkDir(projectDir);
23243
- return c2.json({ id: projectId, files });
23244
- });
23245
- app.get("/api/projects/:id/preview", async (c2) => {
23246
- const id = c2.req.param("id");
23247
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23248
- let bundled;
23249
- try {
23250
- const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
23251
- bundled = await bundleToSingleHtml2(projectDir);
23252
- } catch {
23253
- const file = join20(projectDir, "index.html");
23254
- if (!existsSync21(file)) return c2.text("not found", 404);
23255
- bundled = readFileSync9(file, "utf-8");
23256
- }
23257
- const baseTag = `<base href="/api/projects/${projectId}/preview/">`;
23258
- if (bundled.includes("<head>")) {
23259
- bundled = bundled.replace("<head>", `<head>${baseTag}`);
23260
- } else {
23261
- bundled = baseTag + bundled;
23262
- }
23263
- bundled = bundled.replace(
23264
- 'data-hyperframes-preview-runtime="1" src=""',
23265
- 'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
23266
- );
23267
- return c2.html(bundled);
23268
- });
23269
- app.get("/api/projects/:id/preview/comp/*", (c2) => {
23270
- const id = c2.req.param("id");
23271
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23272
- const compPath = c2.req.path.replace(`/api/projects/${id}/preview/comp/`, "");
23273
- const html = buildSubCompositionHtml(
23274
- projectDir,
23275
- decodeURIComponent(compPath),
23276
- "/api/runtime.js"
23277
- );
23278
- if (!html) return c2.text("not found", 404);
23279
- return c2.html(html);
23280
- });
23281
- app.get("/api/projects/:id/preview/*", (c2) => {
23282
- const id = c2.req.param("id");
23283
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23284
- const subPath = decodeURIComponent(
23285
- c2.req.path.replace(`/api/projects/${id}/preview/`, "").split("?")[0] ?? ""
23286
- );
23287
- const file = resolve10(projectDir, subPath);
23288
- if (!isSafePath(projectDir, file) || !existsSync21(file) || !statSync7(file).isFile()) {
23289
- return c2.text("not found", 404);
23290
- }
23291
- const mime = getMimeType(file);
23292
- const content = readFileSync9(file);
23293
- return new Response(content, {
23294
- headers: { "Content-Type": mime, "Cache-Control": "no-store" }
23295
- });
23296
- });
23297
- app.get("/api/projects/:id/files/*", (c2) => {
23298
- const id = c2.req.param("id");
23299
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23300
- const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
23301
- const file = resolve10(projectDir, filePath);
23302
- if (!isSafePath(projectDir, file) || !existsSync21(file)) {
23303
- return c2.text("not found", 404);
23304
- }
23305
- const content = readFileSync9(file, "utf-8");
23306
- return c2.json({ filename: filePath, content });
23307
- });
23308
- app.put("/api/projects/:id/files/*", async (c2) => {
23309
- const id = c2.req.param("id");
23310
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23311
- const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
23312
- const file = resolve10(projectDir, filePath);
23313
- if (!isSafePath(projectDir, file)) {
23314
- return c2.json({ error: "forbidden" }, 403);
23315
- }
23316
- const dir = dirname10(file);
23317
- if (!existsSync21(dir)) mkdirSync14(dir, { recursive: true });
23318
- const body = await c2.req.text();
23319
- writeFileSync6(file, body, "utf-8");
23320
- return c2.json({ ok: true });
23321
- });
23322
- app.get("/api/resolve-session/:id", (c2) => c2.json({ error: "not available" }, 404));
23323
- const renderJobs = /* @__PURE__ */ new Map();
23324
- app.post("/api/projects/:id/render", async (c2) => {
23325
- const id = c2.req.param("id");
23326
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23327
- const jobId = Math.random().toString(36).slice(2, 10);
23328
- const outputDir = join20(projectDir, "renders");
23329
- if (!existsSync21(outputDir)) mkdirSync14(outputDir, { recursive: true });
23330
- const outputPath = join20(outputDir, `${projectId}.mp4`);
23331
- renderJobs.set(jobId, { status: "rendering", progress: 0, outputPath });
23332
- (async () => {
23333
- try {
23334
- const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
23335
- const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
23336
- try {
23337
- const browser = await ensureBrowser2();
23338
- if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
23339
- process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
23340
- }
23341
- } catch {
23342
- }
23343
- const job = createRenderJob2({ fps: 30, quality: "standard" });
23344
- const onProgress = (j2) => {
23345
- const entry2 = renderJobs.get(jobId);
23346
- if (entry2) entry2.progress = j2.progress;
23347
- };
23348
- await executeRenderJob2(job, projectDir, outputPath, onProgress);
23349
- const entry = renderJobs.get(jobId);
23350
- if (entry) {
23351
- entry.status = "complete";
23352
- entry.progress = 100;
23353
- }
23354
- } catch (err) {
23355
- const entry = renderJobs.get(jobId);
23356
- if (entry) {
23357
- entry.status = "failed";
23358
- entry.error = err instanceof Error ? err.message : String(err);
23359
- }
23360
- }
23361
- })();
23362
- return c2.json({ jobId });
23363
- });
23364
- app.get("/api/render/:jobId/progress", (c2) => {
23365
- const { jobId } = c2.req.param();
23366
- const job = renderJobs.get(jobId);
23367
- if (!job) return c2.json({ error: "not found" }, 404);
23368
- return streamSSE2(c2, async (stream) => {
23369
- while (true) {
23370
- const current = renderJobs.get(jobId);
23371
- if (!current) break;
23372
- await stream.writeSSE({
23373
- event: "progress",
23374
- data: JSON.stringify({
23375
- progress: current.progress,
23376
- status: current.status,
23377
- error: current.error
23378
- })
23379
- });
23380
- if (current.status === "complete" || current.status === "failed") break;
23381
- await stream.sleep(500);
23382
- }
24056
+ const api = createStudioApi(adapter2);
24057
+ app.all("/api/*", async (c2) => {
24058
+ const url = new URL(c2.req.url);
24059
+ url.pathname = url.pathname.slice(4);
24060
+ const forwardReq = new Request(url.toString(), {
24061
+ method: c2.req.method,
24062
+ headers: c2.req.raw.headers,
24063
+ body: c2.req.raw.body,
24064
+ // @ts-expect-error -- Node needs duplex for streaming bodies
24065
+ duplex: "half"
23383
24066
  });
24067
+ return api.fetch(forwardReq);
23384
24068
  });
23385
- app.get("/api/render/:jobId/download", (c2) => {
23386
- const { jobId } = c2.req.param();
23387
- const job = renderJobs.get(jobId);
23388
- if (!job?.outputPath || !existsSync21(job.outputPath)) {
23389
- return c2.json({ error: "not found" }, 404);
23390
- }
23391
- const content = readFileSync9(job.outputPath);
24069
+ app.get("/assets/*", (c2) => {
24070
+ const filePath = resolve13(studioDir, c2.req.path.slice(1));
24071
+ if (!existsSync27(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
24072
+ const content = readFileSync16(filePath);
23392
24073
  return new Response(content, {
23393
- headers: {
23394
- "Content-Type": "video/mp4",
23395
- "Content-Disposition": `attachment; filename="${projectId}.mp4"`
23396
- }
24074
+ headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
23397
24075
  });
23398
24076
  });
23399
- app.get("/assets/*", (c2) => {
23400
- const filePath = resolve10(studioDir, c2.req.path.slice(1));
23401
- const resp = serveStaticFile(filePath);
23402
- return resp ?? c2.text("not found", 404);
23403
- });
23404
24077
  app.get("/icons/*", (c2) => {
23405
- const filePath = resolve10(studioDir, c2.req.path.slice(1));
23406
- const resp = serveStaticFile(filePath);
23407
- return resp ?? c2.text("not found", 404);
24078
+ const filePath = resolve13(studioDir, c2.req.path.slice(1));
24079
+ if (!existsSync27(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
24080
+ const content = readFileSync16(filePath);
24081
+ return new Response(content, {
24082
+ headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
24083
+ });
23408
24084
  });
23409
24085
  app.get("*", (c2) => {
23410
- const indexPath = resolve10(studioDir, "index.html");
23411
- if (!existsSync21(indexPath)) {
24086
+ const indexPath = resolve13(studioDir, "index.html");
24087
+ if (!existsSync27(indexPath)) {
23412
24088
  return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
23413
24089
  }
23414
- return c2.html(readFileSync9(indexPath, "utf-8"));
24090
+ return c2.html(readFileSync16(indexPath, "utf-8"));
23415
24091
  });
23416
24092
  return { app, watcher };
23417
24093
  }
23418
- var MIME_TYPES2;
23419
24094
  var init_studioServer = __esm({
23420
24095
  "src/server/studioServer.ts"() {
23421
24096
  "use strict";
23422
24097
  init_fileWatcher();
23423
- MIME_TYPES2 = {
23424
- ".html": "text/html",
23425
- ".js": "text/javascript",
23426
- ".css": "text/css",
23427
- ".json": "application/json",
23428
- ".svg": "image/svg+xml",
23429
- ".png": "image/png",
23430
- ".jpg": "image/jpeg",
23431
- ".jpeg": "image/jpeg",
23432
- ".webp": "image/webp",
23433
- ".gif": "image/gif",
23434
- ".mp4": "video/mp4",
23435
- ".webm": "video/webm",
23436
- ".mp3": "audio/mpeg",
23437
- ".wav": "audio/wav",
23438
- ".m4a": "audio/mp4",
23439
- ".ogg": "audio/ogg",
23440
- ".woff2": "font/woff2",
23441
- ".woff": "font/woff",
23442
- ".ttf": "font/ttf"
23443
- };
24098
+ init_studio_api();
23444
24099
  }
23445
24100
  });
23446
24101
 
@@ -23450,8 +24105,8 @@ __export(dev_exports, {
23450
24105
  default: () => dev_default
23451
24106
  });
23452
24107
  import { spawn as spawn7 } from "child_process";
23453
- import { existsSync as existsSync22, lstatSync, symlinkSync, unlinkSync, readlinkSync, mkdirSync as mkdirSync15 } from "fs";
23454
- import { resolve as resolve11, dirname as dirname11, basename as basename3, join as join21 } from "path";
24108
+ import { existsSync as existsSync28, lstatSync, symlinkSync, unlinkSync as unlinkSync2, readlinkSync, mkdirSync as mkdirSync17 } from "fs";
24109
+ import { resolve as resolve14, dirname as dirname11, basename as basename3, join as join26 } from "path";
23455
24110
  import { fileURLToPath as fileURLToPath4 } from "url";
23456
24111
  import { createRequire } from "module";
23457
24112
  async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
@@ -23489,26 +24144,26 @@ async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
23489
24144
  }
23490
24145
  async function runDevMode(dir) {
23491
24146
  const thisFile = fileURLToPath4(import.meta.url);
23492
- const repoRoot = resolve11(dirname11(thisFile), "..", "..", "..", "..");
23493
- const projectsDir = join21(repoRoot, "packages", "studio", "data", "projects");
24147
+ const repoRoot = resolve14(dirname11(thisFile), "..", "..", "..", "..");
24148
+ const projectsDir = join26(repoRoot, "packages", "studio", "data", "projects");
23494
24149
  const projectName = basename3(dir);
23495
- const symlinkPath = join21(projectsDir, projectName);
23496
- mkdirSync15(projectsDir, { recursive: true });
24150
+ const symlinkPath = join26(projectsDir, projectName);
24151
+ mkdirSync17(projectsDir, { recursive: true });
23497
24152
  let createdSymlink = false;
23498
24153
  if (dir !== symlinkPath) {
23499
- if (existsSync22(symlinkPath)) {
24154
+ if (existsSync28(symlinkPath)) {
23500
24155
  try {
23501
24156
  const stat = lstatSync(symlinkPath);
23502
24157
  if (stat.isSymbolicLink()) {
23503
24158
  const target = readlinkSync(symlinkPath);
23504
- if (resolve11(target) !== resolve11(dir)) {
23505
- unlinkSync(symlinkPath);
24159
+ if (resolve14(target) !== resolve14(dir)) {
24160
+ unlinkSync2(symlinkPath);
23506
24161
  }
23507
24162
  }
23508
24163
  } catch {
23509
24164
  }
23510
24165
  }
23511
- if (!existsSync22(symlinkPath)) {
24166
+ if (!existsSync28(symlinkPath)) {
23512
24167
  symlinkSync(dir, symlinkPath, "dir");
23513
24168
  createdSymlink = true;
23514
24169
  }
@@ -23516,7 +24171,7 @@ async function runDevMode(dir) {
23516
24171
  Wt2(c.bold("hyperframes dev"));
23517
24172
  const s = be();
23518
24173
  s.start("Starting studio...");
23519
- const studioPkgDir = join21(repoRoot, "packages", "studio");
24174
+ const studioPkgDir = join26(repoRoot, "packages", "studio");
23520
24175
  const child = spawn7("pnpm", ["exec", "vite"], {
23521
24176
  cwd: studioPkgDir,
23522
24177
  stdio: ["ignore", "pipe", "pipe"]
@@ -23550,18 +24205,18 @@ async function runDevMode(dir) {
23550
24205
  if (createdSymlink) {
23551
24206
  process.on("exit", () => {
23552
24207
  try {
23553
- if (existsSync22(symlinkPath)) unlinkSync(symlinkPath);
24208
+ if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
23554
24209
  } catch {
23555
24210
  }
23556
24211
  });
23557
24212
  }
23558
- return new Promise((resolve17) => {
23559
- child.on("close", () => resolve17());
24213
+ return new Promise((resolve20) => {
24214
+ child.on("close", () => resolve20());
23560
24215
  });
23561
24216
  }
23562
24217
  function hasLocalStudio(dir) {
23563
24218
  try {
23564
- const req = createRequire(join21(dir, "package.json"));
24219
+ const req = createRequire(join26(dir, "package.json"));
23565
24220
  req.resolve("@hyperframes/studio/package.json");
23566
24221
  return true;
23567
24222
  } catch {
@@ -23569,20 +24224,20 @@ function hasLocalStudio(dir) {
23569
24224
  }
23570
24225
  }
23571
24226
  async function runLocalStudioMode(dir) {
23572
- const req = createRequire(join21(dir, "package.json"));
24227
+ const req = createRequire(join26(dir, "package.json"));
23573
24228
  const studioPkgPath = dirname11(req.resolve("@hyperframes/studio/package.json"));
23574
24229
  const projectName = basename3(dir);
23575
- const projectsDir = join21(studioPkgPath, "data", "projects");
23576
- const symlinkPath = join21(projectsDir, projectName);
23577
- mkdirSync15(projectsDir, { recursive: true });
24230
+ const projectsDir = join26(studioPkgPath, "data", "projects");
24231
+ const symlinkPath = join26(projectsDir, projectName);
24232
+ mkdirSync17(projectsDir, { recursive: true });
23578
24233
  let createdSymlink = false;
23579
24234
  if (dir !== symlinkPath) {
23580
- if (existsSync22(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
23581
- if (resolve11(readlinkSync(symlinkPath)) !== resolve11(dir)) {
23582
- unlinkSync(symlinkPath);
24235
+ if (existsSync28(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
24236
+ if (resolve14(readlinkSync(symlinkPath)) !== resolve14(dir)) {
24237
+ unlinkSync2(symlinkPath);
23583
24238
  }
23584
24239
  }
23585
- if (!existsSync22(symlinkPath)) {
24240
+ if (!existsSync28(symlinkPath)) {
23586
24241
  symlinkSync(dir, symlinkPath, "dir");
23587
24242
  createdSymlink = true;
23588
24243
  }
@@ -23621,13 +24276,13 @@ async function runLocalStudioMode(dir) {
23621
24276
  if (createdSymlink) {
23622
24277
  process.on("exit", () => {
23623
24278
  try {
23624
- if (existsSync22(symlinkPath)) unlinkSync(symlinkPath);
24279
+ if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
23625
24280
  } catch {
23626
24281
  }
23627
24282
  });
23628
24283
  }
23629
- return new Promise((resolve17) => {
23630
- child.on("close", () => resolve17());
24284
+ return new Promise((resolve20) => {
24285
+ child.on("close", () => resolve20());
23631
24286
  });
23632
24287
  }
23633
24288
  async function runEmbeddedMode(dir, startPort) {
@@ -23658,6 +24313,9 @@ async function runEmbeddedMode(dir, startPort) {
23658
24313
  console.log(` ${c.dim("Project")} ${c.accent(projectName)}`);
23659
24314
  console.log(` ${c.dim("Studio")} ${c.accent(url)}`);
23660
24315
  console.log();
24316
+ console.log(` ${c.dim("Edit with your AI agent \u2014 it has HyperFrames skills installed.")}`);
24317
+ console.log(` ${c.dim("Changes reload automatically in the studio.")}`);
24318
+ console.log();
23661
24319
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
23662
24320
  console.log();
23663
24321
  import("open").then((mod) => mod.default(`${url}#project/${projectName}`)).catch(() => {
@@ -23680,7 +24338,7 @@ var init_dev = __esm({
23680
24338
  port: { type: "string", description: "Port to run the dev server on", default: "3002" }
23681
24339
  },
23682
24340
  async run({ args }) {
23683
- const dir = resolve11(args.dir ?? ".");
24341
+ const dir = resolve14(args.dir ?? ".");
23684
24342
  const startPort = parseInt(args.port ?? "3002", 10);
23685
24343
  if (isDevMode()) {
23686
24344
  return runDevMode(dir);
@@ -23727,17 +24385,17 @@ var init_format = __esm({
23727
24385
  });
23728
24386
 
23729
24387
  // src/utils/project.ts
23730
- import { existsSync as existsSync23, statSync as statSync8 } from "fs";
23731
- import { resolve as resolve12, basename as basename4 } from "path";
24388
+ import { existsSync as existsSync29, statSync as statSync10 } from "fs";
24389
+ import { resolve as resolve15, basename as basename4 } from "path";
23732
24390
  function resolveProject(dirArg) {
23733
- const dir = resolve12(dirArg ?? ".");
24391
+ const dir = resolve15(dirArg ?? ".");
23734
24392
  const name = basename4(dir);
23735
- const indexPath = resolve12(dir, "index.html");
23736
- if (!existsSync23(dir) || !statSync8(dir).isDirectory()) {
24393
+ const indexPath = resolve15(dir, "index.html");
24394
+ if (!existsSync29(dir) || !statSync10(dir).isDirectory()) {
23737
24395
  errorBox("Not a directory: " + dir);
23738
24396
  process.exit(1);
23739
24397
  }
23740
- if (!existsSync23(indexPath)) {
24398
+ if (!existsSync29(indexPath)) {
23741
24399
  errorBox(
23742
24400
  "No composition found in " + dir,
23743
24401
  "No index.html file found.",
@@ -23826,17 +24484,18 @@ var render_exports = {};
23826
24484
  __export(render_exports, {
23827
24485
  default: () => render_default
23828
24486
  });
23829
- import { existsSync as existsSync24, mkdirSync as mkdirSync16, statSync as statSync9 } from "fs";
23830
- import { cpus as cpus2 } from "os";
23831
- import { resolve as resolve13, dirname as dirname12, join as join22 } from "path";
24487
+ import { existsSync as existsSync30, mkdirSync as mkdirSync18, statSync as statSync11 } from "fs";
24488
+ import { cpus as cpus3, freemem as freemem3 } from "os";
24489
+ import { resolve as resolve16, dirname as dirname12, join as join27 } from "path";
23832
24490
  function defaultWorkerCount() {
23833
24491
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
23834
24492
  }
23835
24493
  async function renderDocker(projectDir, outputPath, options) {
23836
24494
  const producer = await loadProducer();
23837
24495
  const startTime = Date.now();
24496
+ let job;
23838
24497
  try {
23839
- const job = producer.createRenderJob({
24498
+ job = producer.createRenderJob({
23840
24499
  fps: options.fps,
23841
24500
  quality: options.quality,
23842
24501
  format: options.format,
@@ -23845,24 +24504,10 @@ async function renderDocker(projectDir, outputPath, options) {
23845
24504
  });
23846
24505
  await producer.executeRenderJob(job, projectDir, outputPath);
23847
24506
  } catch (error) {
23848
- trackRenderError({
23849
- fps: options.fps,
23850
- quality: options.quality,
23851
- docker: true
23852
- });
23853
- const message = error instanceof Error ? error.message : String(error);
23854
- errorBox("Render failed", message, "Check Docker is running: docker info");
23855
- process.exit(1);
24507
+ handleRenderError(error, options, startTime, true, "Check Docker is running: docker info");
23856
24508
  }
23857
24509
  const elapsed = Date.now() - startTime;
23858
- trackRenderComplete({
23859
- durationMs: elapsed,
23860
- fps: options.fps,
23861
- quality: options.quality,
23862
- workers: options.workers,
23863
- docker: true,
23864
- gpu: options.gpu
23865
- });
24510
+ trackRenderMetrics(job, elapsed, options, true);
23866
24511
  printRenderComplete(outputPath, elapsed, options.quiet);
23867
24512
  }
23868
24513
  async function renderLocal(projectDir, outputPath, options) {
@@ -23884,31 +24529,59 @@ async function renderLocal(projectDir, outputPath, options) {
23884
24529
  try {
23885
24530
  await producer.executeRenderJob(job, projectDir, outputPath, onProgress);
23886
24531
  } catch (error) {
23887
- trackRenderError({
23888
- fps: options.fps,
23889
- quality: options.quality,
23890
- docker: false
23891
- });
23892
- const message = error instanceof Error ? error.message : String(error);
23893
- errorBox("Render failed", message, "Try --docker for containerized rendering");
23894
- process.exit(1);
24532
+ handleRenderError(error, options, startTime, false, "Try --docker for containerized rendering");
23895
24533
  }
23896
24534
  const elapsed = Date.now() - startTime;
24535
+ trackRenderMetrics(job, elapsed, options, false);
24536
+ printRenderComplete(outputPath, elapsed, options.quiet);
24537
+ }
24538
+ function getMemorySnapshot() {
24539
+ return {
24540
+ peakMemoryMb: bytesToMb(process.memoryUsage.rss()),
24541
+ memoryFreeMb: bytesToMb(freemem3())
24542
+ };
24543
+ }
24544
+ function handleRenderError(error, options, startTime, docker, hint) {
24545
+ const message = error instanceof Error ? error.message : String(error);
24546
+ trackRenderError({
24547
+ fps: options.fps,
24548
+ quality: options.quality,
24549
+ docker,
24550
+ workers: options.workers,
24551
+ gpu: options.gpu,
24552
+ elapsedMs: Date.now() - startTime,
24553
+ errorMessage: message,
24554
+ ...getMemorySnapshot()
24555
+ });
24556
+ errorBox("Render failed", message, hint);
24557
+ process.exit(1);
24558
+ }
24559
+ function trackRenderMetrics(job, elapsedMs, options, docker) {
24560
+ const perf = job.perfSummary;
24561
+ const compositionDurationMs = perf ? Math.round(perf.compositionDurationSeconds * 1e3) : void 0;
24562
+ const speedRatio = compositionDurationMs && compositionDurationMs > 0 && elapsedMs > 0 ? Math.round(compositionDurationMs / elapsedMs * 100) / 100 : void 0;
23897
24563
  trackRenderComplete({
23898
- durationMs: elapsed,
24564
+ durationMs: elapsedMs,
23899
24565
  fps: options.fps,
23900
24566
  quality: options.quality,
23901
24567
  workers: options.workers,
23902
- docker: false,
23903
- gpu: options.gpu
24568
+ docker,
24569
+ gpu: options.gpu,
24570
+ compositionDurationMs,
24571
+ compositionWidth: perf?.resolution.width,
24572
+ compositionHeight: perf?.resolution.height,
24573
+ totalFrames: perf?.totalFrames,
24574
+ speedRatio,
24575
+ captureAvgMs: perf?.captureAvgMs,
24576
+ capturePeakMs: perf?.capturePeakMs,
24577
+ ...getMemorySnapshot()
23904
24578
  });
23905
- printRenderComplete(outputPath, elapsed, options.quiet);
23906
24579
  }
23907
24580
  function printRenderComplete(outputPath, elapsedMs, quiet) {
23908
24581
  if (quiet) return;
23909
24582
  let fileSize = "unknown";
23910
- if (existsSync24(outputPath)) {
23911
- const stat = statSync9(outputPath);
24583
+ if (existsSync30(outputPath)) {
24584
+ const stat = statSync11(outputPath);
23912
24585
  fileSize = formatBytes(stat.size);
23913
24586
  }
23914
24587
  const duration = formatDuration(elapsedMs);
@@ -23917,7 +24590,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
23917
24590
  console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
23918
24591
  }
23919
24592
  var VALID_FPS, VALID_QUALITY, VALID_FORMAT, CPU_CORE_COUNT, render_default;
23920
- var init_render = __esm({
24593
+ var init_render2 = __esm({
23921
24594
  "src/commands/render.ts"() {
23922
24595
  "use strict";
23923
24596
  init_dist();
@@ -23927,10 +24600,11 @@ var init_render = __esm({
23927
24600
  init_format();
23928
24601
  init_progress();
23929
24602
  init_events();
24603
+ init_system();
23930
24604
  VALID_FPS = /* @__PURE__ */ new Set([24, 30, 60]);
23931
24605
  VALID_QUALITY = /* @__PURE__ */ new Set(["draft", "standard", "high"]);
23932
24606
  VALID_FORMAT = /* @__PURE__ */ new Set(["mp4", "webm"]);
23933
- CPU_CORE_COUNT = cpus2().length;
24607
+ CPU_CORE_COUNT = cpus3().length;
23934
24608
  render_default = defineCommand({
23935
24609
  meta: {
23936
24610
  name: "render",
@@ -24012,10 +24686,13 @@ Examples:
24012
24686
  }
24013
24687
  workers = parsed;
24014
24688
  }
24015
- const rendersDir = resolve13("renders");
24689
+ const rendersDir = resolve16("renders");
24016
24690
  const ext = format === "webm" ? ".webm" : ".mp4";
24017
- const outputPath = args.output ? resolve13(args.output) : join22(rendersDir, `${project.name}${ext}`);
24018
- mkdirSync16(dirname12(outputPath), { recursive: true });
24691
+ const now = /* @__PURE__ */ new Date();
24692
+ const datePart = now.toISOString().slice(0, 10);
24693
+ const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
24694
+ const outputPath = args.output ? resolve16(args.output) : join27(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
24695
+ mkdirSync18(dirname12(outputPath), { recursive: true });
24019
24696
  const useDocker = args.docker ?? false;
24020
24697
  const useGpu = args.gpu ?? false;
24021
24698
  const quiet = args.quiet ?? false;
@@ -24101,17 +24778,17 @@ __export(transcribe_exports, {
24101
24778
  transcribe: () => transcribe
24102
24779
  });
24103
24780
  import { execFileSync as execFileSync3 } from "child_process";
24104
- import { existsSync as existsSync25, readFileSync as readFileSync10, mkdirSync as mkdirSync17, unlinkSync as unlinkSync2 } from "fs";
24105
- import { join as join23, extname as extname5 } from "path";
24781
+ import { existsSync as existsSync31, readFileSync as readFileSync17, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
24782
+ import { join as join28, extname as extname4 } from "path";
24106
24783
  import { tmpdir as tmpdir2 } from "os";
24107
24784
  function isAudioFile(filePath) {
24108
- return AUDIO_EXTENSIONS.has(extname5(filePath).toLowerCase());
24785
+ return AUDIO_EXTENSIONS.has(extname4(filePath).toLowerCase());
24109
24786
  }
24110
24787
  function isVideoFile(filePath) {
24111
- return VIDEO_EXTENSIONS.has(extname5(filePath).toLowerCase());
24788
+ return VIDEO_EXTENSIONS.has(extname4(filePath).toLowerCase());
24112
24789
  }
24113
24790
  function extractAudio(videoPath) {
24114
- const wavPath = join23(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24791
+ const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24115
24792
  execFileSync3(
24116
24793
  "ffmpeg",
24117
24794
  ["-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -24134,10 +24811,10 @@ function isWav16kMono(filePath) {
24134
24811
  }
24135
24812
  }
24136
24813
  function prepareAudio(audioPath) {
24137
- if (extname5(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
24814
+ if (extname4(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
24138
24815
  return audioPath;
24139
24816
  }
24140
- const wavPath = join23(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24817
+ const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24141
24818
  execFileSync3(
24142
24819
  "ffmpeg",
24143
24820
  ["-i", audioPath, "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -24154,7 +24831,7 @@ async function transcribe(inputPath, outputDir, options) {
24154
24831
  onProgress: options?.onProgress
24155
24832
  });
24156
24833
  let wavPath;
24157
- const ext = extname5(inputPath).toLowerCase();
24834
+ const ext = extname4(inputPath).toLowerCase();
24158
24835
  if (isAudioFile(inputPath)) {
24159
24836
  options?.onProgress?.("Preparing audio...");
24160
24837
  wavPath = prepareAudio(inputPath);
@@ -24170,8 +24847,8 @@ async function transcribe(inputPath, outputDir, options) {
24170
24847
  throw new Error(`Unsupported file type: ${ext}`);
24171
24848
  }
24172
24849
  options?.onProgress?.("Transcribing...");
24173
- const outputBase = join23(outputDir, "transcript");
24174
- mkdirSync17(outputDir, { recursive: true });
24850
+ const outputBase = join28(outputDir, "transcript");
24851
+ mkdirSync19(outputDir, { recursive: true });
24175
24852
  execFileSync3(
24176
24853
  whisper.executablePath,
24177
24854
  [
@@ -24188,10 +24865,10 @@ async function transcribe(inputPath, outputDir, options) {
24188
24865
  { stdio: "ignore", timeout: 3e5 }
24189
24866
  );
24190
24867
  const transcriptPath = `${outputBase}.json`;
24191
- if (!existsSync25(transcriptPath)) {
24868
+ if (!existsSync31(transcriptPath)) {
24192
24869
  throw new Error("Whisper did not produce output. Check the input file.");
24193
24870
  }
24194
- const transcript = JSON.parse(readFileSync10(transcriptPath, "utf-8"));
24871
+ const transcript = JSON.parse(readFileSync17(transcriptPath, "utf-8"));
24195
24872
  const segments = transcript.transcription ?? [];
24196
24873
  let wordCount = 0;
24197
24874
  let maxEnd = 0;
@@ -24204,7 +24881,7 @@ async function transcribe(inputPath, outputDir, options) {
24204
24881
  }
24205
24882
  if (wavPath !== inputPath) {
24206
24883
  try {
24207
- unlinkSync2(wavPath);
24884
+ unlinkSync3(wavPath);
24208
24885
  } catch {
24209
24886
  }
24210
24887
  }
@@ -24230,15 +24907,15 @@ __export(init_exports, {
24230
24907
  default: () => init_default
24231
24908
  });
24232
24909
  import {
24233
- existsSync as existsSync26,
24234
- mkdirSync as mkdirSync18,
24910
+ existsSync as existsSync32,
24911
+ mkdirSync as mkdirSync20,
24235
24912
  copyFileSync as copyFileSync3,
24236
24913
  cpSync as cpSync2,
24237
- writeFileSync as writeFileSync7,
24238
- readFileSync as readFileSync11,
24239
- readdirSync as readdirSync7
24914
+ writeFileSync as writeFileSync9,
24915
+ readFileSync as readFileSync18,
24916
+ readdirSync as readdirSync8
24240
24917
  } from "fs";
24241
- import { resolve as resolve14, basename as basename5, join as join24, dirname as dirname13 } from "path";
24918
+ import { resolve as resolve17, basename as basename5, join as join29, dirname as dirname13 } from "path";
24242
24919
  import { fileURLToPath as fileURLToPath5 } from "url";
24243
24920
  import { execFileSync as execFileSync4, spawn as spawn8 } from "child_process";
24244
24921
  async function installSkills(interactive) {
@@ -24359,9 +25036,9 @@ function transcodeToMp4(inputPath, outputPath) {
24359
25036
  }
24360
25037
  function resolveAssetDir(devSegments, builtSegments) {
24361
25038
  const base = dirname13(fileURLToPath5(import.meta.url));
24362
- const devPath = resolve14(base, ...devSegments);
24363
- const builtPath = resolve14(base, ...builtSegments);
24364
- return existsSync26(devPath) ? devPath : builtPath;
25039
+ const devPath = resolve17(base, ...devSegments);
25040
+ const builtPath = resolve17(base, ...builtSegments);
25041
+ return existsSync32(devPath) ? devPath : builtPath;
24365
25042
  }
24366
25043
  function getStaticTemplateDir(templateId) {
24367
25044
  return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
@@ -24373,9 +25050,9 @@ function getBundledSkillsDir() {
24373
25050
  return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
24374
25051
  }
24375
25052
  function patchVideoSrc(dir, videoFilename, durationSeconds) {
24376
- const htmlFiles = readdirSync7(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join24(e.parentPath ?? e.path, e.name));
25053
+ const htmlFiles = readdirSync8(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join29(e.parentPath ?? e.path, e.name));
24377
25054
  for (const file of htmlFiles) {
24378
- let content = readFileSync11(file, "utf-8");
25055
+ let content = readFileSync18(file, "utf-8");
24379
25056
  if (videoFilename) {
24380
25057
  content = content.replaceAll("__VIDEO_SRC__", videoFilename);
24381
25058
  } else {
@@ -24386,11 +25063,11 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
24386
25063
  }
24387
25064
  const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
24388
25065
  content = content.replaceAll("__VIDEO_DURATION__", dur);
24389
- writeFileSync7(file, content, "utf-8");
25066
+ writeFileSync9(file, content, "utf-8");
24390
25067
  }
24391
25068
  }
24392
25069
  function patchTranscript(dir, transcriptPath) {
24393
- const raw = JSON.parse(readFileSync11(transcriptPath, "utf-8"));
25070
+ const raw = JSON.parse(readFileSync18(transcriptPath, "utf-8"));
24394
25071
  const words = [];
24395
25072
  for (const seg of raw.transcription ?? []) {
24396
25073
  for (const token of seg.tokens ?? []) {
@@ -24412,9 +25089,9 @@ function patchTranscript(dir, transcriptPath) {
24412
25089
  }
24413
25090
  if (words.length === 0) return;
24414
25091
  const wordsJson = JSON.stringify(words, null, 10).replace(/^\[/, "[").replace(/\n {10}/g, "\n ");
24415
- const htmlFiles = readdirSync7(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join24(e.parentPath ?? e.path, e.name));
25092
+ const htmlFiles = readdirSync8(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join29(e.parentPath ?? e.path, e.name));
24416
25093
  for (const file of htmlFiles) {
24417
- let content = readFileSync11(file, "utf-8");
25094
+ let content = readFileSync18(file, "utf-8");
24418
25095
  const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
24419
25096
  let scriptMatch = null;
24420
25097
  let transcriptMatch = null;
@@ -24426,7 +25103,7 @@ function patchTranscript(dir, transcriptPath) {
24426
25103
  if (match) {
24427
25104
  const varName = scriptMatch ? "script" : "TRANSCRIPT";
24428
25105
  content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
24429
- writeFileSync7(file, content, "utf-8");
25106
+ writeFileSync9(file, content, "utf-8");
24430
25107
  }
24431
25108
  }
24432
25109
  }
@@ -24483,7 +25160,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24483
25160
  }
24484
25161
  if (shouldTranscode) {
24485
25162
  const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
24486
- const mp4Path = resolve14(destDir, mp4Name);
25163
+ const mp4Path = resolve17(destDir, mp4Name);
24487
25164
  const spin = be();
24488
25165
  spin.start("Transcoding to H.264 MP4...");
24489
25166
  const ok = await transcodeToMp4(videoPath, mp4Path);
@@ -24492,10 +25169,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24492
25169
  localVideoName = mp4Name;
24493
25170
  } else {
24494
25171
  spin.stop(c.warn("Transcode failed \u2014 copying original file"));
24495
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25172
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24496
25173
  }
24497
25174
  } else {
24498
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25175
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24499
25176
  }
24500
25177
  } else {
24501
25178
  if (interactive) {
@@ -24505,20 +25182,20 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24505
25182
  console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
24506
25183
  console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
24507
25184
  }
24508
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25185
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24509
25186
  }
24510
25187
  } else {
24511
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25188
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24512
25189
  }
24513
25190
  return { meta, localVideoName };
24514
25191
  }
24515
25192
  function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
24516
- mkdirSync18(destDir, { recursive: true });
25193
+ mkdirSync20(destDir, { recursive: true });
24517
25194
  const templateDir = getStaticTemplateDir(templateId);
24518
25195
  cpSync2(templateDir, destDir, { recursive: true });
24519
25196
  patchVideoSrc(destDir, localVideoName, durationSeconds);
24520
- writeFileSync7(
24521
- resolve14(destDir, "meta.json"),
25197
+ writeFileSync9(
25198
+ resolve17(destDir, "meta.json"),
24522
25199
  JSON.stringify(
24523
25200
  {
24524
25201
  id: name,
@@ -24531,23 +25208,23 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
24531
25208
  "utf-8"
24532
25209
  );
24533
25210
  const sharedDir = getSharedTemplateDir();
24534
- if (existsSync26(sharedDir)) {
24535
- for (const entry of readdirSync7(sharedDir, { withFileTypes: true })) {
24536
- const src = join24(sharedDir, entry.name);
24537
- const dest = resolve14(destDir, entry.name);
25211
+ if (existsSync32(sharedDir)) {
25212
+ for (const entry of readdirSync8(sharedDir, { withFileTypes: true })) {
25213
+ const src = join29(sharedDir, entry.name);
25214
+ const dest = resolve17(destDir, entry.name);
24538
25215
  if (entry.isFile() || entry.isSymbolicLink()) {
24539
25216
  copyFileSync3(src, dest);
24540
25217
  }
24541
25218
  }
24542
25219
  }
24543
25220
  const skillsSrcDir = getBundledSkillsDir();
24544
- if (existsSync26(skillsSrcDir)) {
24545
- const projectSkills = ["compose-video", "captions"];
25221
+ if (existsSync32(skillsSrcDir)) {
25222
+ const projectSkills = ["hyperframes-compose", "hyperframes-captions"];
24546
25223
  for (const skill of projectSkills) {
24547
- const src = join24(skillsSrcDir, skill);
24548
- if (existsSync26(src)) {
24549
- const dest = resolve14(destDir, ".claude", "skills", skill);
24550
- mkdirSync18(dest, { recursive: true });
25224
+ const src = join29(skillsSrcDir, skill);
25225
+ if (existsSync32(src)) {
25226
+ const dest = resolve17(destDir, ".claude", "skills", skill);
25227
+ mkdirSync20(dest, { recursive: true });
24551
25228
  cpSync2(src, dest, { recursive: true });
24552
25229
  }
24553
25230
  }
@@ -24576,7 +25253,7 @@ async function nextStepLoop(destDir) {
24576
25253
  const devCmd = await Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default);
24577
25254
  await runCommand(devCmd, { rawArgs: [destDir] });
24578
25255
  } else if (next === "render") {
24579
- const renderCmd = await Promise.resolve().then(() => (init_render(), render_exports)).then((m) => m.default);
25256
+ const renderCmd = await Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default);
24580
25257
  await runCommand(renderCmd, { rawArgs: [destDir] });
24581
25258
  }
24582
25259
  } catch {
@@ -24663,18 +25340,18 @@ Examples:
24663
25340
  }
24664
25341
  const templateId2 = resolvedTemplate;
24665
25342
  const name2 = args.name ?? "my-video";
24666
- const destDir2 = resolve14(name2);
24667
- if (existsSync26(destDir2) && readdirSync7(destDir2).length > 0) {
25343
+ const destDir2 = resolve17(name2);
25344
+ if (existsSync32(destDir2) && readdirSync8(destDir2).length > 0) {
24668
25345
  console.error(c.error(`Directory already exists and is not empty: ${name2}`));
24669
25346
  process.exit(1);
24670
25347
  }
24671
- mkdirSync18(destDir2, { recursive: true });
25348
+ mkdirSync20(destDir2, { recursive: true });
24672
25349
  let localVideoName2;
24673
25350
  let videoDuration2;
24674
25351
  let sourceFilePath2;
24675
25352
  if (videoFlag) {
24676
- const videoPath = resolve14(videoFlag);
24677
- if (!existsSync26(videoPath)) {
25353
+ const videoPath = resolve17(videoFlag);
25354
+ if (!existsSync32(videoPath)) {
24678
25355
  console.error(c.error(`Video file not found: ${videoFlag}`));
24679
25356
  process.exit(1);
24680
25357
  }
@@ -24687,13 +25364,13 @@ Examples:
24687
25364
  );
24688
25365
  }
24689
25366
  if (audioFlag) {
24690
- const audioPath = resolve14(audioFlag);
24691
- if (!existsSync26(audioPath)) {
25367
+ const audioPath = resolve17(audioFlag);
25368
+ if (!existsSync32(audioPath)) {
24692
25369
  console.error(c.error(`Audio file not found: ${audioFlag}`));
24693
25370
  process.exit(1);
24694
25371
  }
24695
25372
  sourceFilePath2 = audioPath;
24696
- copyFileSync3(audioPath, resolve14(destDir2, basename5(audioPath)));
25373
+ copyFileSync3(audioPath, resolve17(destDir2, basename5(audioPath)));
24697
25374
  console.log(`Audio: ${basename5(audioPath)}`);
24698
25375
  }
24699
25376
  if (sourceFilePath2 && !skipTranscribe) {
@@ -24714,35 +25391,34 @@ Examples:
24714
25391
  }
24715
25392
  scaffoldProject(destDir2, basename5(destDir2), templateId2, localVideoName2, videoDuration2);
24716
25393
  trackInitTemplate(templateId2);
24717
- const transcriptFile2 = resolve14(destDir2, "transcript.json");
24718
- if (existsSync26(transcriptFile2)) {
25394
+ const transcriptFile2 = resolve17(destDir2, "transcript.json");
25395
+ if (existsSync32(transcriptFile2)) {
24719
25396
  patchTranscript(destDir2, transcriptFile2);
24720
25397
  }
24721
25398
  if (!skipSkills) {
24722
25399
  await installSkills(false);
24723
25400
  }
24724
25401
  console.log(c.success(`Created ${c.accent(name2 + "/")}`));
24725
- for (const f of readdirSync7(destDir2).filter((f2) => !f2.startsWith("."))) {
25402
+ for (const f of readdirSync8(destDir2).filter((f2) => !f2.startsWith("."))) {
24726
25403
  console.log(` ${c.accent(f)}`);
24727
25404
  }
24728
25405
  console.log();
24729
- console.log("Next steps:");
24730
- console.log(
24731
- ` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes dev")} ${c.dim("# preview in studio")}`
24732
- );
24733
- console.log(
24734
- ` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes render")} ${c.dim("# render to MP4")}`
24735
- );
25406
+ console.log("Get started:");
25407
+ console.log();
25408
+ console.log(` ${c.accent("1.")} Open this project with your AI coding agent:`);
24736
25409
  console.log(
24737
- ` ${c.accent("npx hyperframes docs")} ${c.dim("<topic>")} ${c.dim("# learn composition syntax")}`
25410
+ ` ${c.accent(`cd ${name2}`)} then start ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred agent`
24738
25411
  );
24739
25412
  console.log(
24740
- ` ${c.dim("topics: data-attributes, gsap, compositions, rendering, templates, troubleshooting")}`
25413
+ ` ${c.dim("AI skills are installed \u2014 your agent knows how to create and edit compositions.")}`
24741
25414
  );
24742
25415
  console.log();
24743
- console.log(
24744
- ` ${c.dim("AI skills installed \u2014 open this folder in your AI coding agent to get started.")}`
24745
- );
25416
+ console.log(` ${c.accent("2.")} Preview in the browser:`);
25417
+ console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes dev")}`);
25418
+ console.log();
25419
+ console.log(` ${c.accent("3.")} Render to MP4 when ready:`);
25420
+ console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes render")}`);
25421
+ console.log();
24746
25422
  console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
24747
25423
  return;
24748
25424
  }
@@ -24763,8 +25439,8 @@ Examples:
24763
25439
  }
24764
25440
  name = nameResult;
24765
25441
  }
24766
- const destDir = resolve14(name);
24767
- if (existsSync26(destDir) && readdirSync7(destDir).length > 0) {
25442
+ const destDir = resolve17(name);
25443
+ if (existsSync32(destDir) && readdirSync8(destDir).length > 0) {
24768
25444
  const overwrite = await Rt({
24769
25445
  message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
24770
25446
  initialValue: false
@@ -24779,13 +25455,13 @@ Examples:
24779
25455
  let videoDuration;
24780
25456
  let isAudioOnly = false;
24781
25457
  if (videoFlag) {
24782
- const videoPath = resolve14(videoFlag);
24783
- if (!existsSync26(videoPath)) {
25458
+ const videoPath = resolve17(videoFlag);
25459
+ if (!existsSync32(videoPath)) {
24784
25460
  R2.error(`File not found: ${videoFlag}`);
24785
25461
  Nt("Setup cancelled.");
24786
25462
  process.exit(1);
24787
25463
  }
24788
- mkdirSync18(destDir, { recursive: true });
25464
+ mkdirSync20(destDir, { recursive: true });
24789
25465
  sourceFilePath = videoPath;
24790
25466
  const result = await handleVideoFile(videoPath, destDir, true);
24791
25467
  localVideoName = result.localVideoName;
@@ -24815,7 +25491,7 @@ Examples:
24815
25491
  validate(val) {
24816
25492
  const trimmed = val?.trim();
24817
25493
  if (!trimmed) return "Please enter a file path";
24818
- if (!existsSync26(resolve14(trimmed))) return "File not found";
25494
+ if (!existsSync32(resolve17(trimmed))) return "File not found";
24819
25495
  return void 0;
24820
25496
  }
24821
25497
  });
@@ -24823,16 +25499,16 @@ Examples:
24823
25499
  Nt("Setup cancelled.");
24824
25500
  process.exit(0);
24825
25501
  }
24826
- const filePath = resolve14(String(pathResult).trim());
25502
+ const filePath = resolve17(String(pathResult).trim());
24827
25503
  sourceFilePath = filePath;
24828
- mkdirSync18(destDir, { recursive: true });
25504
+ mkdirSync20(destDir, { recursive: true });
24829
25505
  if (mediaChoice === "video") {
24830
25506
  const result = await handleVideoFile(filePath, destDir, true);
24831
25507
  localVideoName = result.localVideoName;
24832
25508
  videoDuration = result.meta.durationSeconds;
24833
25509
  } else {
24834
25510
  isAudioOnly = true;
24835
- copyFileSync3(filePath, resolve14(destDir, basename5(filePath)));
25511
+ copyFileSync3(filePath, resolve17(destDir, basename5(filePath)));
24836
25512
  R2.info(`Audio copied to ${c.accent(basename5(filePath))}`);
24837
25513
  }
24838
25514
  }
@@ -24900,15 +25576,19 @@ Examples:
24900
25576
  }
24901
25577
  scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
24902
25578
  trackInitTemplate(templateId);
24903
- const transcriptFile = resolve14(destDir, "transcript.json");
24904
- if (existsSync26(transcriptFile)) {
25579
+ const transcriptFile = resolve17(destDir, "transcript.json");
25580
+ if (existsSync32(transcriptFile)) {
24905
25581
  patchTranscript(destDir, transcriptFile);
24906
25582
  }
24907
25583
  if (!skipSkills) {
24908
25584
  await installSkills(true);
24909
25585
  }
24910
- const files = readdirSync7(destDir);
25586
+ const files = readdirSync8(destDir);
24911
25587
  Vt2(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
25588
+ R2.message(
25589
+ `${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.
25590
+ ${c.dim(" AI skills are installed \u2014 your agent knows how to create and edit compositions.")}`
25591
+ );
24912
25592
  await nextStepLoop(destDir);
24913
25593
  }
24914
25594
  });
@@ -24916,17 +25596,17 @@ Examples:
24916
25596
  });
24917
25597
 
24918
25598
  // src/commands/lint.ts
24919
- var lint_exports = {};
24920
- __export(lint_exports, {
25599
+ var lint_exports2 = {};
25600
+ __export(lint_exports2, {
24921
25601
  default: () => lint_default
24922
25602
  });
24923
- import { readFileSync as readFileSync12 } from "fs";
25603
+ import { readFileSync as readFileSync19 } from "fs";
24924
25604
  var lint_default;
24925
- var init_lint2 = __esm({
25605
+ var init_lint3 = __esm({
24926
25606
  "src/commands/lint.ts"() {
24927
25607
  "use strict";
24928
25608
  init_dist();
24929
- init_lint();
25609
+ init_lint2();
24930
25610
  init_colors();
24931
25611
  init_project();
24932
25612
  init_updateCheck();
@@ -24938,7 +25618,7 @@ var init_lint2 = __esm({
24938
25618
  },
24939
25619
  async run({ args }) {
24940
25620
  const project = resolveProject(args.dir);
24941
- const html = readFileSync12(project.indexPath, "utf-8");
25621
+ const html = readFileSync19(project.indexPath, "utf-8");
24942
25622
  const result = lintHyperframeHtml(html, { filePath: project.indexPath });
24943
25623
  if (args.json) {
24944
25624
  console.log(JSON.stringify(withMeta(result), null, 2));
@@ -24987,16 +25667,16 @@ var info_exports = {};
24987
25667
  __export(info_exports, {
24988
25668
  default: () => info_default
24989
25669
  });
24990
- import { readFileSync as readFileSync13, readdirSync as readdirSync8, statSync as statSync10 } from "fs";
24991
- import { join as join25 } from "path";
25670
+ import { readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync12 } from "fs";
25671
+ import { join as join30 } from "path";
24992
25672
  function totalSize(dir) {
24993
25673
  let total = 0;
24994
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
24995
- const path = join25(dir, entry.name);
25674
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
25675
+ const path = join30(dir, entry.name);
24996
25676
  if (entry.isDirectory()) {
24997
25677
  total += totalSize(path);
24998
25678
  } else {
24999
- total += statSync10(path).size;
25679
+ total += statSync12(path).size;
25000
25680
  }
25001
25681
  }
25002
25682
  return total;
@@ -25020,7 +25700,7 @@ var init_info = __esm({
25020
25700
  },
25021
25701
  async run({ args }) {
25022
25702
  const project = resolveProject(args.dir);
25023
- const html = readFileSync13(project.indexPath, "utf-8");
25703
+ const html = readFileSync20(project.indexPath, "utf-8");
25024
25704
  ensureDOMParser();
25025
25705
  const parsed = parseHtml(html);
25026
25706
  const tracks = new Set(parsed.elements.map((el) => el.zIndex));
@@ -25071,7 +25751,7 @@ var compositions_exports = {};
25071
25751
  __export(compositions_exports, {
25072
25752
  default: () => compositions_default
25073
25753
  });
25074
- import { readFileSync as readFileSync14 } from "fs";
25754
+ import { readFileSync as readFileSync21 } from "fs";
25075
25755
  function parseCompositions(html) {
25076
25756
  const parser = new DOMParser();
25077
25757
  const doc = parser.parseFromString(html, "text/html");
@@ -25128,7 +25808,7 @@ var init_compositions = __esm({
25128
25808
  },
25129
25809
  async run({ args }) {
25130
25810
  const project = resolveProject(args.dir);
25131
- const html = readFileSync14(project.indexPath, "utf-8");
25811
+ const html = readFileSync21(project.indexPath, "utf-8");
25132
25812
  ensureDOMParser();
25133
25813
  const compositions = parseCompositions(html);
25134
25814
  if (compositions.length === 0) {
@@ -25164,8 +25844,8 @@ var benchmark_exports = {};
25164
25844
  __export(benchmark_exports, {
25165
25845
  default: () => benchmark_default
25166
25846
  });
25167
- import { existsSync as existsSync27, statSync as statSync11 } from "fs";
25168
- import { resolve as resolve15, join as join26 } from "path";
25847
+ import { existsSync as existsSync33, statSync as statSync13 } from "fs";
25848
+ import { resolve as resolve18, join as join31 } from "path";
25169
25849
  var DEFAULT_CONFIGS, benchmark_default;
25170
25850
  var init_benchmark = __esm({
25171
25851
  "src/commands/benchmark.ts"() {
@@ -25202,7 +25882,7 @@ var init_benchmark = __esm({
25202
25882
  process.exit(1);
25203
25883
  }
25204
25884
  const jsonOutput = args.json ?? false;
25205
- const benchDir = resolve15("renders", ".benchmark");
25885
+ const benchDir = resolve18("renders", ".benchmark");
25206
25886
  let producer = null;
25207
25887
  try {
25208
25888
  producer = await loadProducer();
@@ -25235,7 +25915,7 @@ var init_benchmark = __esm({
25235
25915
  s?.start(`Benchmarking ${config.label}...`);
25236
25916
  for (let i = 0; i < runsPerConfig; i++) {
25237
25917
  s?.message(`${config.label} \u2014 run ${i + 1}/${runsPerConfig}`);
25238
- const outputPath = join26(
25918
+ const outputPath = join31(
25239
25919
  benchDir,
25240
25920
  `${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i}.mp4`
25241
25921
  );
@@ -25249,8 +25929,8 @@ var init_benchmark = __esm({
25249
25929
  await producer.executeRenderJob(job, project.dir, outputPath);
25250
25930
  const elapsedMs = Date.now() - startTime;
25251
25931
  let fileSize = null;
25252
- if (existsSync27(outputPath)) {
25253
- const stat = statSync11(outputPath);
25932
+ if (existsSync33(outputPath)) {
25933
+ const stat = statSync13(outputPath);
25254
25934
  fileSize = stat.size;
25255
25935
  }
25256
25936
  runs.push({ elapsedMs, fileSize });
@@ -25458,15 +26138,15 @@ var docs_exports = {};
25458
26138
  __export(docs_exports, {
25459
26139
  default: () => docs_default
25460
26140
  });
25461
- import { readFileSync as readFileSync15, existsSync as existsSync28 } from "fs";
25462
- import { resolve as resolve16, dirname as dirname14, join as join27 } from "path";
26141
+ import { readFileSync as readFileSync22, existsSync as existsSync34 } from "fs";
26142
+ import { resolve as resolve19, dirname as dirname14, join as join32 } from "path";
25463
26143
  import { fileURLToPath as fileURLToPath6 } from "url";
25464
26144
  function docsDir() {
25465
26145
  const thisFile = fileURLToPath6(import.meta.url);
25466
26146
  const dir = dirname14(thisFile);
25467
- const devPath = resolve16(dir, "..", "docs");
25468
- const builtPath = resolve16(dir, "docs");
25469
- return existsSync28(devPath) ? devPath : builtPath;
26147
+ const devPath = resolve19(dir, "..", "docs");
26148
+ const builtPath = resolve19(dir, "docs");
26149
+ return existsSync34(devPath) ? devPath : builtPath;
25470
26150
  }
25471
26151
  function formatInlineCode(line) {
25472
26152
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -25557,12 +26237,12 @@ var init_docs = __esm({
25557
26237
  }
25558
26238
  process.exit(1);
25559
26239
  }
25560
- const filePath = join27(docsDir(), entry.file);
25561
- if (!existsSync28(filePath)) {
26240
+ const filePath = join32(docsDir(), entry.file);
26241
+ if (!existsSync34(filePath)) {
25562
26242
  console.error(c.error(`Doc file not found: ${filePath}`));
25563
26243
  process.exit(1);
25564
26244
  }
25565
- const content = readFileSync15(filePath, "utf-8");
26245
+ const content = readFileSync22(filePath, "utf-8");
25566
26246
  console.log();
25567
26247
  renderMarkdown(content);
25568
26248
  }
@@ -25576,6 +26256,7 @@ __export(doctor_exports, {
25576
26256
  default: () => doctor_default
25577
26257
  });
25578
26258
  import { execSync as execSync3 } from "child_process";
26259
+ import { freemem as freemem4, platform as platform3 } from "os";
25579
26260
  function checkFFmpeg() {
25580
26261
  const path = findFFmpeg();
25581
26262
  if (path) {
@@ -25653,6 +26334,67 @@ function checkVersion() {
25653
26334
  function checkNode() {
25654
26335
  return { ok: true, detail: `${process.version} (${process.platform} ${process.arch})` };
25655
26336
  }
26337
+ function checkCPU() {
26338
+ const sys = getSystemMeta();
26339
+ const model = sys.cpu_model ?? "Unknown";
26340
+ const speedStr = sys.cpu_speed ? ` @ ${sys.cpu_speed}MHz` : "";
26341
+ return { ok: true, detail: `${sys.cpu_count} cores \xB7 ${model}${speedStr}` };
26342
+ }
26343
+ function checkMemory() {
26344
+ const sys = getSystemMeta();
26345
+ const freeMb = bytesToMb(freemem4());
26346
+ const totalGb = (sys.memory_total_mb / 1024).toFixed(1);
26347
+ const freeGb = (freeMb / 1024).toFixed(1);
26348
+ if (freeMb < 2048) {
26349
+ return {
26350
+ ok: false,
26351
+ detail: `${totalGb} GB total \xB7 ${freeGb} GB free`,
26352
+ hint: "Low memory \u2014 renders may fail. Close other apps or increase RAM."
26353
+ };
26354
+ }
26355
+ return { ok: true, detail: `${totalGb} GB total \xB7 ${freeGb} GB free` };
26356
+ }
26357
+ function checkShm() {
26358
+ const shmMb = getShmSizeMb();
26359
+ if (shmMb === null) {
26360
+ return { ok: true, detail: "N/A (non-Linux)" };
26361
+ }
26362
+ if (shmMb < 256) {
26363
+ return {
26364
+ ok: false,
26365
+ detail: `${shmMb} MB`,
26366
+ hint: "Chrome needs \u2265256 MB. Use: docker run --shm-size=512m"
26367
+ };
26368
+ }
26369
+ return { ok: true, detail: `${shmMb} MB` };
26370
+ }
26371
+ function checkDisk() {
26372
+ const freeMb = getFreeDiskMb(".");
26373
+ if (freeMb === null) {
26374
+ return { ok: true, detail: "Unable to check" };
26375
+ }
26376
+ const freeGb = (freeMb / 1024).toFixed(1);
26377
+ if (freeMb < 1024) {
26378
+ return {
26379
+ ok: false,
26380
+ detail: `${freeGb} GB free`,
26381
+ hint: "Low disk space \u2014 renders produce large temp files."
26382
+ };
26383
+ }
26384
+ return { ok: true, detail: `${freeGb} GB free` };
26385
+ }
26386
+ function checkEnvironment() {
26387
+ const sys = getSystemMeta();
26388
+ const parts = [];
26389
+ if (sys.is_docker) parts.push("Docker");
26390
+ if (sys.is_wsl) parts.push("WSL");
26391
+ if (sys.is_ci) parts.push(`CI (${sys.ci_name ?? "detected"})`);
26392
+ if (!sys.is_tty) parts.push("non-TTY");
26393
+ if (parts.length === 0) {
26394
+ return { ok: true, detail: "Native terminal" };
26395
+ }
26396
+ return { ok: true, detail: parts.join(" \xB7 ") };
26397
+ }
25656
26398
  var doctor_default;
25657
26399
  var init_doctor = __esm({
25658
26400
  "src/commands/doctor.ts"() {
@@ -25663,6 +26405,7 @@ var init_doctor = __esm({
25663
26405
  init_ffmpeg();
25664
26406
  init_version();
25665
26407
  init_updateCheck();
26408
+ init_system();
25666
26409
  doctor_default = defineCommand({
25667
26410
  meta: { name: "doctor", description: "Check system dependencies and environment" },
25668
26411
  args: {},
@@ -25673,12 +26416,21 @@ var init_doctor = __esm({
25673
26416
  const checks = [
25674
26417
  { name: "Version", run: checkVersion },
25675
26418
  { name: "Node.js", run: checkNode },
26419
+ { name: "CPU", run: checkCPU },
26420
+ { name: "Memory", run: checkMemory },
26421
+ { name: "Disk", run: checkDisk }
26422
+ ];
26423
+ if (platform3() === "linux") {
26424
+ checks.push({ name: "/dev/shm", run: checkShm });
26425
+ }
26426
+ checks.push(
26427
+ { name: "Environment", run: checkEnvironment },
25676
26428
  { name: "FFmpeg", run: checkFFmpeg },
25677
26429
  { name: "FFprobe", run: checkFFprobe },
25678
26430
  { name: "Chrome", run: checkChrome },
25679
26431
  { name: "Docker", run: checkDocker },
25680
26432
  { name: "Docker running", run: checkDockerRunning }
25681
- ];
26433
+ );
25682
26434
  let allOk = true;
25683
26435
  for (const check of checks) {
25684
26436
  const result = await check.run();
@@ -25877,14 +26629,15 @@ init_version();
25877
26629
  init_config();
25878
26630
  init_client();
25879
26631
  init_events();
26632
+ init_system();
25880
26633
 
25881
26634
  // src/cli.ts
25882
26635
  init_updateCheck();
25883
26636
  var subCommands = {
25884
26637
  init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
25885
26638
  dev: () => Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default),
25886
- render: () => Promise.resolve().then(() => (init_render(), render_exports)).then((m) => m.default),
25887
- lint: () => Promise.resolve().then(() => (init_lint2(), lint_exports)).then((m) => m.default),
26639
+ render: () => Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default),
26640
+ lint: () => Promise.resolve().then(() => (init_lint3(), lint_exports2)).then((m) => m.default),
25888
26641
  info: () => Promise.resolve().then(() => (init_info(), info_exports)).then((m) => m.default),
25889
26642
  compositions: () => Promise.resolve().then(() => (init_compositions(), compositions_exports)).then((m) => m.default),
25890
26643
  benchmark: () => Promise.resolve().then(() => (init_benchmark(), benchmark_exports)).then((m) => m.default),