hyperframes 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -422,7 +422,7 @@ var VERSION;
422
422
  var init_version = __esm({
423
423
  "src/version.ts"() {
424
424
  "use strict";
425
- VERSION = true ? "0.1.10" : "0.0.0-dev";
425
+ VERSION = true ? "0.1.12" : "0.0.0-dev";
426
426
  }
427
427
  });
428
428
 
@@ -600,13 +600,6 @@ var init_env = __esm({
600
600
  });
601
601
 
602
602
  // src/telemetry/system.ts
603
- var system_exports = {};
604
- __export(system_exports, {
605
- bytesToMb: () => bytesToMb,
606
- getFreeDiskMb: () => getFreeDiskMb,
607
- getShmSizeMb: () => getShmSizeMb,
608
- getSystemMeta: () => getSystemMeta
609
- });
610
603
  import { cpus, totalmem, platform, release } from "os";
611
604
  import { existsSync as existsSync2, readFileSync as readFileSync2, statfsSync } from "fs";
612
605
  function bytesToMb(bytes) {
@@ -825,14 +818,6 @@ var init_client = __esm({
825
818
  });
826
819
 
827
820
  // src/telemetry/events.ts
828
- var events_exports = {};
829
- __export(events_exports, {
830
- trackBrowserInstall: () => trackBrowserInstall,
831
- trackCommand: () => trackCommand,
832
- trackInitTemplate: () => trackInitTemplate,
833
- trackRenderComplete: () => trackRenderComplete,
834
- trackRenderError: () => trackRenderError
835
- });
836
821
  function trackCommand(command2) {
837
822
  trackEvent("cli_command", { command: command2 });
838
823
  }
@@ -2697,7 +2682,7 @@ import { join as join2 } from "path";
2697
2682
  import { get as httpsGet } from "https";
2698
2683
  import { pipeline } from "stream/promises";
2699
2684
  function downloadFile(url, dest) {
2700
- return new Promise((resolve17, reject) => {
2685
+ return new Promise((resolve20, reject) => {
2701
2686
  const follow = (u) => {
2702
2687
  httpsGet(u, (res) => {
2703
2688
  if (res.statusCode === 301 || res.statusCode === 302) {
@@ -2712,7 +2697,7 @@ function downloadFile(url, dest) {
2712
2697
  return;
2713
2698
  }
2714
2699
  const file = createWriteStream(dest);
2715
- pipeline(res, file).then(resolve17).catch(reject);
2700
+ pipeline(res, file).then(resolve20).catch(reject);
2716
2701
  }).on("error", reject);
2717
2702
  };
2718
2703
  follow(url);
@@ -3201,6 +3186,553 @@ var init_fileWatcher = __esm({
3201
3186
  }
3202
3187
  });
3203
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
+
3204
3736
  // ../core/src/compiler/timingCompiler.ts
3205
3737
  function getAttr(tag, attr) {
3206
3738
  const match = tag.match(new RegExp(`${attr}=["']([^"']+)["']`));
@@ -3337,9 +3869,9 @@ var init_timingCompiler = __esm({
3337
3869
  });
3338
3870
 
3339
3871
  // ../core/src/compiler/htmlCompiler.ts
3340
- import { resolve } from "path";
3872
+ import { resolve as resolve4 } from "path";
3341
3873
  function resolveMediaSrc(src, projectDir) {
3342
- return src.startsWith("http://") || src.startsWith("https://") ? src : resolve(projectDir, src);
3874
+ return src.startsWith("http://") || src.startsWith("https://") ? src : resolve4(projectDir, src);
3343
3875
  }
3344
3876
  async function compileHtml(rawHtml, projectDir, probeMediaDuration) {
3345
3877
  const { html: staticCompiled, unresolved } = compileTimingAttrs(rawHtml);
@@ -3585,7 +4117,9 @@ var init_gsapParser = __esm({
3585
4117
 
3586
4118
  // ../core/src/lint/hyperframeLinter.ts
3587
4119
  function lintHyperframeHtml(html, options = {}) {
3588
- const source = html || "";
4120
+ let source = html || "";
4121
+ const templateMatch = source.match(/<template[^>]*>([\s\S]*)<\/template>/i);
4122
+ if (templateMatch?.[1]) source = templateMatch[1];
3589
4123
  const filePath = options.filePath;
3590
4124
  const findings = [];
3591
4125
  const seen = /* @__PURE__ */ new Set();
@@ -3942,18 +4476,20 @@ ${right.raw}`)
3942
4476
  for (const tag of tags) {
3943
4477
  if (tag.name === "audio" || tag.name === "script" || tag.name === "style") continue;
3944
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;
3945
4481
  const classAttr = readAttr(tag.raw, "class") || "";
3946
4482
  const styleAttr = readAttr(tag.raw, "style") || "";
3947
4483
  const hasClip = classAttr.split(/\s+/).includes("clip");
3948
- 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);
3949
4485
  if (!hasClip && !hasHiddenStyle) {
3950
4486
  const elementId = readAttr(tag.raw, "id") || void 0;
3951
4487
  pushFinding({
3952
4488
  code: "timed_element_missing_visibility_hidden",
3953
- severity: "warning",
3954
- 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.`,
3955
4491
  elementId,
3956
- 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.',
3957
4493
  snippet: truncateSnippet(tag.raw)
3958
4494
  });
3959
4495
  }
@@ -4336,6 +4872,72 @@ function truncateSnippet(value, maxLength = 220) {
4336
4872
  }
4337
4873
  return `${normalized.slice(0, maxLength - 3)}...`;
4338
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
+ }
4339
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;
4340
4942
  var init_hyperframeLinter = __esm({
4341
4943
  "../core/src/lint/hyperframeLinter.ts"() {
@@ -4381,14 +4983,14 @@ var init_staticGuard = __esm({
4381
4983
  });
4382
4984
 
4383
4985
  // ../core/src/compiler/htmlBundler.ts
4384
- import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
4385
- 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";
4386
4988
  import * as cheerio from "cheerio";
4387
4989
  import { transformSync } from "esbuild";
4388
4990
  function safePath(projectDir, relativePath) {
4389
- const resolved = resolve2(projectDir, relativePath);
4390
- const normalizedBase = resolve2(projectDir) + sep;
4391
- 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;
4392
4994
  return resolved;
4393
4995
  }
4394
4996
  function stripEmbeddedRuntimeScripts(html) {
@@ -4444,17 +5046,17 @@ function isRelativeUrl(url) {
4444
5046
  return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute(url);
4445
5047
  }
4446
5048
  function safeReadFile(filePath) {
4447
- if (!existsSync5(filePath)) return null;
5049
+ if (!existsSync10(filePath)) return null;
4448
5050
  try {
4449
- return readFileSync3(filePath, "utf-8");
5051
+ return readFileSync9(filePath, "utf-8");
4450
5052
  } catch {
4451
5053
  return null;
4452
5054
  }
4453
5055
  }
4454
5056
  function safeReadFileBuffer(filePath) {
4455
- if (!existsSync5(filePath)) return null;
5057
+ if (!existsSync10(filePath)) return null;
4456
5058
  try {
4457
- return readFileSync3(filePath);
5059
+ return readFileSync9(filePath);
4458
5060
  } catch {
4459
5061
  return null;
4460
5062
  }
@@ -4641,9 +5243,9 @@ function stripJsCommentsParserSafe(source) {
4641
5243
  }
4642
5244
  }
4643
5245
  async function bundleToSingleHtml(projectDir, options) {
4644
- const indexPath = join4(projectDir, "index.html");
4645
- if (!existsSync5(indexPath)) throw new Error("index.html not found in project directory");
4646
- const rawHtml = readFileSync3(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");
4647
5249
  const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
4648
5250
  const staticGuard = validateHyperframeHtmlContract(compiled);
4649
5251
  if (!staticGuard.isValid) {
@@ -4798,6 +5400,19 @@ var init_compiler = __esm({
4798
5400
  }
4799
5401
  });
4800
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
+
4801
5416
  // ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/symbols.js
4802
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;
4803
5418
  var init_symbols = __esm({
@@ -8652,8 +9267,8 @@ var init_custom_element_registry = __esm({
8652
9267
  } : (element) => element.localName === localName;
8653
9268
  registry.set(localName, { Class, check });
8654
9269
  if (waiting.has(localName)) {
8655
- for (const resolve17 of waiting.get(localName))
8656
- resolve17(Class);
9270
+ for (const resolve20 of waiting.get(localName))
9271
+ resolve20(Class);
8657
9272
  waiting.delete(localName);
8658
9273
  }
8659
9274
  ownerDocument.querySelectorAll(
@@ -8693,13 +9308,13 @@ var init_custom_element_registry = __esm({
8693
9308
  */
8694
9309
  whenDefined(localName) {
8695
9310
  const { registry, waiting } = this;
8696
- return new Promise((resolve17) => {
9311
+ return new Promise((resolve20) => {
8697
9312
  if (registry.has(localName))
8698
- resolve17(registry.get(localName).Class);
9313
+ resolve20(registry.get(localName).Class);
8699
9314
  else {
8700
9315
  if (!waiting.has(localName))
8701
9316
  waiting.set(localName, []);
8702
- waiting.get(localName).push(resolve17);
9317
+ waiting.get(localName).push(resolve20);
8703
9318
  }
8704
9319
  });
8705
9320
  }
@@ -16636,7 +17251,7 @@ var init_html_classes = __esm({
16636
17251
 
16637
17252
  // ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js
16638
17253
  var voidElements2, Mime;
16639
- var init_mime = __esm({
17254
+ var init_mime2 = __esm({
16640
17255
  "../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js"() {
16641
17256
  "use strict";
16642
17257
  voidElements2 = { test: () => true };
@@ -16901,7 +17516,7 @@ var init_document = __esm({
16901
17516
  init_symbols();
16902
17517
  init_facades();
16903
17518
  init_html_classes();
16904
- init_mime();
17519
+ init_mime2();
16905
17520
  init_utils();
16906
17521
  init_object();
16907
17522
  init_non_element_parent_node();
@@ -17507,8 +18122,8 @@ var init_config2 = __esm({
17507
18122
  });
17508
18123
 
17509
18124
  // ../engine/src/services/browserManager.ts
17510
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
17511
- import { join as join5 } from "path";
18125
+ import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
18126
+ import { join as join10 } from "path";
17512
18127
  import { homedir as homedir4 } from "os";
17513
18128
  async function getPuppeteer() {
17514
18129
  if (_puppeteer) return _puppeteer;
@@ -17529,19 +18144,19 @@ function resolveHeadlessShellPath(config) {
17529
18144
  if (process.env.PRODUCER_HEADLESS_SHELL_PATH) {
17530
18145
  return process.env.PRODUCER_HEADLESS_SHELL_PATH;
17531
18146
  }
17532
- const baseDir = join5(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
17533
- if (!existsSync6(baseDir)) return void 0;
18147
+ const baseDir = join10(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
18148
+ if (!existsSync11(baseDir)) return void 0;
17534
18149
  try {
17535
- const versions = readdirSync2(baseDir).sort().reverse();
18150
+ const versions = readdirSync4(baseDir).sort().reverse();
17536
18151
  for (const version of versions) {
17537
18152
  const candidates = [
17538
- join5(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
17539
- join5(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
17540
- join5(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
17541
- 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")
17542
18157
  ];
17543
18158
  for (const binary of candidates) {
17544
- if (existsSync6(binary)) return binary;
18159
+ if (existsSync11(binary)) return binary;
17545
18160
  }
17546
18161
  }
17547
18162
  } catch {
@@ -18115,7 +18730,7 @@ var init_hyperframes = __esm({
18115
18730
 
18116
18731
  // ../core/src/inline-scripts/hyperframesRuntime.engine.ts
18117
18732
  import { buildSync } from "esbuild";
18118
- import { dirname as dirname2, resolve as resolve3 } from "path";
18733
+ import { dirname as dirname3, resolve as resolve6 } from "path";
18119
18734
  import { fileURLToPath } from "url";
18120
18735
  var init_hyperframesRuntime_engine = __esm({
18121
18736
  "../core/src/inline-scripts/hyperframesRuntime.engine.ts"() {
@@ -18372,10 +18987,10 @@ var init_screenshotService = __esm({
18372
18987
  });
18373
18988
 
18374
18989
  // ../engine/src/services/frameCapture.ts
18375
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
18376
- 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";
18377
18992
  async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
18378
- if (!existsSync7(outputDir)) mkdirSync4(outputDir, { recursive: true });
18993
+ if (!existsSync12(outputDir)) mkdirSync7(outputDir, { recursive: true });
18379
18994
  const headlessShell = resolveHeadlessShellPath(config);
18380
18995
  const isLinux = process.platform === "linux";
18381
18996
  const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG2.forceScreenshot;
@@ -18534,13 +19149,13 @@ async function initializeSession(session) {
18534
19149
  }
18535
19150
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
18536
19151
  try {
18537
- const diagnosticsDir = join6(session.outputDir, "diagnostics");
18538
- if (!existsSync7(diagnosticsDir)) mkdirSync4(diagnosticsDir, { recursive: true });
18539
- 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}`);
18540
19155
  await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
18541
19156
  const html = await session.page.content();
18542
- writeFileSync2(`${base}.html`, html, "utf-8");
18543
- writeFileSync2(
19157
+ writeFileSync4(`${base}.html`, html, "utf-8");
19158
+ writeFileSync4(
18544
19159
  `${base}.json`,
18545
19160
  JSON.stringify(
18546
19161
  {
@@ -18634,8 +19249,8 @@ async function captureFrame(session, frameIndex, time) {
18634
19249
  );
18635
19250
  const ext = options.format === "png" ? "png" : "jpg";
18636
19251
  const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
18637
- const framePath = join6(outputDir, frameName);
18638
- writeFileSync2(framePath, buffer);
19252
+ const framePath = join11(outputDir, frameName);
19253
+ writeFileSync4(framePath, buffer);
18639
19254
  return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
18640
19255
  }
18641
19256
  async function captureFrameToBuffer(session, frameIndex, time) {
@@ -18649,8 +19264,8 @@ async function closeCaptureSession(session) {
18649
19264
  session.isInitialized = false;
18650
19265
  }
18651
19266
  function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
18652
- if (!existsSync7(outputDir)) {
18653
- mkdirSync4(outputDir, { recursive: true });
19267
+ if (!existsSync12(outputDir)) {
19268
+ mkdirSync7(outputDir, { recursive: true });
18654
19269
  }
18655
19270
  session.outputDir = outputDir;
18656
19271
  session.onBeforeCapture = onBeforeCapture;
@@ -18695,7 +19310,7 @@ var init_frameCapture = __esm({
18695
19310
  // ../engine/src/utils/gpuEncoder.ts
18696
19311
  import { spawn } from "child_process";
18697
19312
  async function detectGpuEncoder() {
18698
- return new Promise((resolve17) => {
19313
+ return new Promise((resolve20) => {
18699
19314
  const ffmpeg = spawn("ffmpeg", ["-encoders"], {
18700
19315
  stdio: ["pipe", "pipe", "pipe"]
18701
19316
  });
@@ -18704,13 +19319,13 @@ async function detectGpuEncoder() {
18704
19319
  stdout2 += data.toString();
18705
19320
  });
18706
19321
  ffmpeg.on("close", () => {
18707
- if (stdout2.includes("h264_nvenc")) resolve17("nvenc");
18708
- else if (stdout2.includes("h264_videotoolbox")) resolve17("videotoolbox");
18709
- else if (stdout2.includes("h264_vaapi")) resolve17("vaapi");
18710
- else if (stdout2.includes("h264_qsv")) resolve17("qsv");
18711
- 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);
18712
19327
  });
18713
- ffmpeg.on("error", () => resolve17(null));
19328
+ ffmpeg.on("error", () => resolve20(null));
18714
19329
  });
18715
19330
  }
18716
19331
  async function getCachedGpuEncoder() {
@@ -18749,7 +19364,7 @@ async function runFfmpeg(args, opts) {
18749
19364
  const signal = opts?.signal;
18750
19365
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
18751
19366
  const onStderr = opts?.onStderr;
18752
- return new Promise((resolve17) => {
19367
+ return new Promise((resolve20) => {
18753
19368
  const ffmpeg = spawn2("ffmpeg", args);
18754
19369
  let stderr = "";
18755
19370
  const onAbort = () => {
@@ -18775,7 +19390,7 @@ async function runFfmpeg(args, opts) {
18775
19390
  ffmpeg.on("close", (code) => {
18776
19391
  clearTimeout(timer);
18777
19392
  if (signal) signal.removeEventListener("abort", onAbort);
18778
- resolve17({
19393
+ resolve20({
18779
19394
  success: !signal?.aborted && code === 0,
18780
19395
  exitCode: code,
18781
19396
  stderr,
@@ -18785,7 +19400,7 @@ async function runFfmpeg(args, opts) {
18785
19400
  ffmpeg.on("error", (err) => {
18786
19401
  clearTimeout(timer);
18787
19402
  if (signal) signal.removeEventListener("abort", onAbort);
18788
- resolve17({
19403
+ resolve20({
18789
19404
  success: false,
18790
19405
  exitCode: null,
18791
19406
  stderr: err.message,
@@ -18804,8 +19419,8 @@ var init_runFfmpeg = __esm({
18804
19419
 
18805
19420
  // ../engine/src/services/chunkEncoder.ts
18806
19421
  import { spawn as spawn3 } from "child_process";
18807
- import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
18808
- 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";
18809
19424
  function getEncoderPreset(quality, format = "mp4") {
18810
19425
  const base = ENCODER_PRESETS[quality];
18811
19426
  if (format === "webm") {
@@ -18886,9 +19501,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
18886
19501
  }
18887
19502
  async function encodeFramesFromDir(framesDir, framePattern, outputPath, options, signal, config) {
18888
19503
  const startTime = Date.now();
18889
- const outputDir = dirname3(outputPath);
18890
- if (!existsSync8(outputDir)) mkdirSync5(outputDir, { recursive: true });
18891
- 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));
18892
19507
  const frameCount = files.length;
18893
19508
  if (frameCount === 0) {
18894
19509
  return {
@@ -18904,10 +19519,10 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18904
19519
  if (options.useGpu) {
18905
19520
  gpuEncoder = await getCachedGpuEncoder();
18906
19521
  }
18907
- const inputPath = join7(framesDir, framePattern);
19522
+ const inputPath = join12(framesDir, framePattern);
18908
19523
  const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
18909
19524
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
18910
- return new Promise((resolve17) => {
19525
+ return new Promise((resolve20) => {
18911
19526
  const ffmpeg = spawn3("ffmpeg", args);
18912
19527
  let stderr = "";
18913
19528
  const onAbort = () => {
@@ -18932,7 +19547,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18932
19547
  if (signal) signal.removeEventListener("abort", onAbort);
18933
19548
  const durationMs = Date.now() - startTime;
18934
19549
  if (signal?.aborted) {
18935
- resolve17({
19550
+ resolve20({
18936
19551
  success: false,
18937
19552
  outputPath,
18938
19553
  durationMs,
@@ -18943,7 +19558,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18943
19558
  return;
18944
19559
  }
18945
19560
  if (code !== 0) {
18946
- resolve17({
19561
+ resolve20({
18947
19562
  success: false,
18948
19563
  outputPath,
18949
19564
  durationMs,
@@ -18953,13 +19568,13 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18953
19568
  });
18954
19569
  return;
18955
19570
  }
18956
- const fileSize = existsSync8(outputPath) ? statSync(outputPath).size : 0;
18957
- 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 });
18958
19573
  });
18959
19574
  ffmpeg.on("error", (err) => {
18960
19575
  clearTimeout(timer);
18961
19576
  if (signal) signal.removeEventListener("abort", onAbort);
18962
- resolve17({
19577
+ resolve20({
18963
19578
  success: false,
18964
19579
  outputPath,
18965
19580
  durationMs: Date.now() - startTime,
@@ -18972,7 +19587,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
18972
19587
  }
18973
19588
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal) {
18974
19589
  const start = Date.now();
18975
- 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();
18976
19591
  if (files.length === 0) {
18977
19592
  return {
18978
19593
  success: false,
@@ -18985,8 +19600,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
18985
19600
  }
18986
19601
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
18987
19602
  const chunkCount = Math.ceil(files.length / chunkSize);
18988
- const chunkDir = join7(dirname3(outputPath), "chunk-encode");
18989
- if (!existsSync8(chunkDir)) mkdirSync5(chunkDir, { recursive: true });
19603
+ const chunkDir = join12(dirname4(outputPath), "chunk-encode");
19604
+ if (!existsSync13(chunkDir)) mkdirSync8(chunkDir, { recursive: true });
18990
19605
  const chunkPaths = [];
18991
19606
  for (let i = 0; i < chunkCount; i++) {
18992
19607
  if (signal?.aborted) {
@@ -19002,8 +19617,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19002
19617
  const startNumber = i * chunkSize;
19003
19618
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
19004
19619
  const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
19005
- const chunkPath = join7(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
19006
- const inputPath = join7(framesDir, framePattern);
19620
+ const chunkPath = join12(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
19621
+ const inputPath = join12(framesDir, framePattern);
19007
19622
  const inputArgs = [
19008
19623
  "-framerate",
19009
19624
  String(options.fps),
@@ -19017,18 +19632,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19017
19632
  let gpuEncoder = null;
19018
19633
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
19019
19634
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
19020
- const chunkResult = await new Promise((resolve17) => {
19635
+ const chunkResult = await new Promise((resolve20) => {
19021
19636
  const ffmpeg = spawn3("ffmpeg", args);
19022
19637
  let stderr = "";
19023
19638
  ffmpeg.stderr.on("data", (d) => {
19024
19639
  stderr += d.toString();
19025
19640
  });
19026
19641
  ffmpeg.on("close", (code) => {
19027
- if (code === 0) resolve17({ success: true });
19028
- 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)}` });
19029
19644
  });
19030
19645
  ffmpeg.on("error", (err) => {
19031
- resolve17({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
19646
+ resolve20({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
19032
19647
  });
19033
19648
  });
19034
19649
  if (!chunkResult.success) {
@@ -19043,9 +19658,9 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19043
19658
  }
19044
19659
  chunkPaths.push(chunkPath);
19045
19660
  }
19046
- const concatListPath = join7(chunkDir, "concat-list.txt");
19661
+ const concatListPath = join12(chunkDir, "concat-list.txt");
19047
19662
  const concatInput = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
19048
- writeFileSync3(concatListPath, concatInput, "utf-8");
19663
+ writeFileSync5(concatListPath, concatInput, "utf-8");
19049
19664
  const concatArgs = [
19050
19665
  "-f",
19051
19666
  "concat",
@@ -19058,18 +19673,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19058
19673
  "-y",
19059
19674
  outputPath
19060
19675
  ];
19061
- const concatResult = await new Promise((resolve17) => {
19676
+ const concatResult = await new Promise((resolve20) => {
19062
19677
  const ffmpeg = spawn3("ffmpeg", concatArgs);
19063
19678
  let stderr = "";
19064
19679
  ffmpeg.stderr.on("data", (d) => {
19065
19680
  stderr += d.toString();
19066
19681
  });
19067
19682
  ffmpeg.on("close", (code) => {
19068
- if (code === 0) resolve17({ success: true });
19069
- 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)}` });
19070
19685
  });
19071
19686
  ffmpeg.on("error", (err) => {
19072
- resolve17({ success: false, error: `Chunk concat error: ${err.message}` });
19687
+ resolve20({ success: false, error: `Chunk concat error: ${err.message}` });
19073
19688
  });
19074
19689
  });
19075
19690
  if (!concatResult.success) {
@@ -19082,7 +19697,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19082
19697
  error: concatResult.error
19083
19698
  };
19084
19699
  }
19085
- const fileSize = existsSync8(outputPath) ? statSync(outputPath).size : 0;
19700
+ const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
19086
19701
  return {
19087
19702
  success: true,
19088
19703
  outputPath,
@@ -19092,8 +19707,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
19092
19707
  };
19093
19708
  }
19094
19709
  async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, config) {
19095
- const outputDir = dirname3(outputPath);
19096
- if (!existsSync8(outputDir)) mkdirSync5(outputDir, { recursive: true });
19710
+ const outputDir = dirname4(outputPath);
19711
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
19097
19712
  const isWebm = outputPath.endsWith(".webm");
19098
19713
  const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
19099
19714
  if (isWebm) {
@@ -19160,8 +19775,8 @@ var init_chunkEncoder = __esm({
19160
19775
 
19161
19776
  // ../engine/src/services/streamingEncoder.ts
19162
19777
  import { spawn as spawn4 } from "child_process";
19163
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, statSync as statSync2 } from "fs";
19164
- 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";
19165
19780
  function createFrameReorderBuffer(startFrame, endFrame) {
19166
19781
  let nextFrame = startFrame;
19167
19782
  let waiters = [];
@@ -19174,16 +19789,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
19174
19789
  }
19175
19790
  };
19176
19791
  return {
19177
- waitForFrame: (frame) => new Promise((resolve17) => {
19178
- waiters.push({ frame, resolve: resolve17 });
19792
+ waitForFrame: (frame) => new Promise((resolve20) => {
19793
+ waiters.push({ frame, resolve: resolve20 });
19179
19794
  resolveWaiters();
19180
19795
  }),
19181
19796
  advanceTo: (frame) => {
19182
19797
  nextFrame = frame;
19183
19798
  resolveWaiters();
19184
19799
  },
19185
- waitForAllDone: () => new Promise((resolve17) => {
19186
- waiters.push({ frame: endFrame, resolve: resolve17 });
19800
+ waitForAllDone: () => new Promise((resolve20) => {
19801
+ waiters.push({ frame: endFrame, resolve: resolve20 });
19187
19802
  resolveWaiters();
19188
19803
  })
19189
19804
  };
@@ -19268,8 +19883,8 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
19268
19883
  return args;
19269
19884
  }
19270
19885
  async function spawnStreamingEncoder(outputPath, options, signal, config) {
19271
- const outputDir = dirname4(outputPath);
19272
- if (!existsSync9(outputDir)) mkdirSync6(outputDir, { recursive: true });
19886
+ const outputDir = dirname5(outputPath);
19887
+ if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
19273
19888
  let gpuEncoder = null;
19274
19889
  if (options.useGpu) {
19275
19890
  gpuEncoder = await getCachedGpuEncoder();
@@ -19283,7 +19898,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
19283
19898
  let stderr = "";
19284
19899
  let exitCode = null;
19285
19900
  let exitPromiseResolve = null;
19286
- const exitPromise = new Promise((resolve17) => exitPromiseResolve = resolve17);
19901
+ const exitPromise = new Promise((resolve20) => exitPromiseResolve = resolve20);
19287
19902
  ffmpeg.stderr?.on("data", (data) => {
19288
19903
  stderr += data.toString();
19289
19904
  });
@@ -19327,8 +19942,8 @@ Process error: ${err.message}`;
19327
19942
  clearTimeout(timer);
19328
19943
  if (signal) signal.removeEventListener("abort", onAbort);
19329
19944
  if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
19330
- await new Promise((resolve17) => {
19331
- ffmpeg.stdin.end(() => resolve17());
19945
+ await new Promise((resolve20) => {
19946
+ ffmpeg.stdin.end(() => resolve20());
19332
19947
  });
19333
19948
  }
19334
19949
  await exitPromise;
@@ -19349,7 +19964,7 @@ Process error: ${err.message}`;
19349
19964
  error: `FFmpeg exited with code ${exitCode}`
19350
19965
  };
19351
19966
  }
19352
- const fileSize = existsSync9(outputPath) ? statSync2(outputPath).size : 0;
19967
+ const fileSize = existsSync14(outputPath) ? statSync4(outputPath).size : 0;
19353
19968
  return { success: true, durationMs, fileSize };
19354
19969
  },
19355
19970
  getExitStatus: () => exitStatus
@@ -19381,7 +19996,7 @@ async function extractVideoMetadata(filePath) {
19381
19996
  if (cached2) {
19382
19997
  return cached2;
19383
19998
  }
19384
- const probePromise = new Promise((resolve17, reject) => {
19999
+ const probePromise = new Promise((resolve20, reject) => {
19385
20000
  const args = [
19386
20001
  "-v",
19387
20002
  "quiet",
@@ -19423,7 +20038,7 @@ async function extractVideoMetadata(filePath) {
19423
20038
  videoCodec: videoStream.codec_name || "unknown",
19424
20039
  hasAudio
19425
20040
  };
19426
- resolve17(metadata);
20041
+ resolve20(metadata);
19427
20042
  } catch (parseError) {
19428
20043
  reject(
19429
20044
  new Error(
@@ -19453,7 +20068,7 @@ async function extractAudioMetadata(filePath) {
19453
20068
  if (cached2) {
19454
20069
  return cached2;
19455
20070
  }
19456
- const probePromise = new Promise((resolve17, reject) => {
20071
+ const probePromise = new Promise((resolve20, reject) => {
19457
20072
  const args = [
19458
20073
  "-v",
19459
20074
  "quiet",
@@ -19492,7 +20107,7 @@ async function extractAudioMetadata(filePath) {
19492
20107
  audioCodec: audioStream.codec_name || "unknown",
19493
20108
  bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
19494
20109
  };
19495
- resolve17(metadata);
20110
+ resolve20(metadata);
19496
20111
  } catch (parseError) {
19497
20112
  reject(
19498
20113
  new Error(
@@ -19527,9 +20142,9 @@ var init_ffprobe = __esm({
19527
20142
  });
19528
20143
 
19529
20144
  // ../engine/src/utils/urlDownloader.ts
19530
- import { createWriteStream as createWriteStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
20145
+ import { createWriteStream as createWriteStream2, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
19531
20146
  import { createHash } from "crypto";
19532
- import { join as join8, extname } from "path";
20147
+ import { join as join13, extname } from "path";
19533
20148
  import { Readable } from "stream";
19534
20149
  import { finished } from "stream/promises";
19535
20150
  function getFilenameFromUrl(url) {
@@ -19540,19 +20155,19 @@ function getFilenameFromUrl(url) {
19540
20155
  }
19541
20156
  async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
19542
20157
  const cachedPath = downloadPathCache.get(url);
19543
- if (cachedPath && existsSync10(cachedPath)) {
20158
+ if (cachedPath && existsSync15(cachedPath)) {
19544
20159
  return cachedPath;
19545
20160
  }
19546
20161
  const inFlight = inFlightDownloads.get(url);
19547
20162
  if (inFlight) {
19548
20163
  return inFlight;
19549
20164
  }
19550
- if (!existsSync10(destDir)) {
19551
- mkdirSync7(destDir, { recursive: true });
20165
+ if (!existsSync15(destDir)) {
20166
+ mkdirSync10(destDir, { recursive: true });
19552
20167
  }
19553
20168
  const filename = getFilenameFromUrl(url);
19554
- const localPath = join8(destDir, filename);
19555
- if (existsSync10(localPath)) {
20169
+ const localPath = join13(destDir, filename);
20170
+ if (existsSync15(localPath)) {
19556
20171
  downloadPathCache.set(url, localPath);
19557
20172
  return localPath;
19558
20173
  }
@@ -19600,8 +20215,8 @@ var init_urlDownloader = __esm({
19600
20215
 
19601
20216
  // ../engine/src/services/videoFrameExtractor.ts
19602
20217
  import { spawn as spawn6 } from "child_process";
19603
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readdirSync as readdirSync4, rmSync as rmSync3 } from "fs";
19604
- 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";
19605
20220
  function parseVideoElements(html) {
19606
20221
  const videos = [];
19607
20222
  const { document: document2 } = parseHTML(html);
@@ -19628,11 +20243,11 @@ function parseVideoElements(html) {
19628
20243
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
19629
20244
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
19630
20245
  const { fps, outputDir, quality = 95, format = "jpg" } = options;
19631
- const videoOutputDir = join9(outputDir, videoId);
19632
- if (!existsSync11(videoOutputDir)) mkdirSync8(videoOutputDir, { recursive: true });
20246
+ const videoOutputDir = join14(outputDir, videoId);
20247
+ if (!existsSync16(videoOutputDir)) mkdirSync11(videoOutputDir, { recursive: true });
19633
20248
  const metadata = await extractVideoMetadata(videoPath);
19634
20249
  const framePattern = `frame_%05d.${format}`;
19635
- const outputPattern = join9(videoOutputDir, framePattern);
20250
+ const outputPattern = join14(videoOutputDir, framePattern);
19636
20251
  const args = [
19637
20252
  "-ss",
19638
20253
  String(startTime),
@@ -19647,7 +20262,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
19647
20262
  ];
19648
20263
  if (format === "png") args.push("-compression_level", "6");
19649
20264
  args.push("-y", outputPattern);
19650
- return new Promise((resolve17, reject) => {
20265
+ return new Promise((resolve20, reject) => {
19651
20266
  const ffmpeg = spawn6("ffmpeg", args);
19652
20267
  let stderr = "";
19653
20268
  const onAbort = () => {
@@ -19678,11 +20293,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
19678
20293
  return;
19679
20294
  }
19680
20295
  const framePaths = /* @__PURE__ */ new Map();
19681
- 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();
19682
20297
  files.forEach((file, index) => {
19683
- framePaths.set(index, join9(videoOutputDir, file));
20298
+ framePaths.set(index, join14(videoOutputDir, file));
19684
20299
  });
19685
- resolve17({
20300
+ resolve20({
19686
20301
  videoId,
19687
20302
  srcPath: videoPath,
19688
20303
  outputDir: videoOutputDir,
@@ -19717,14 +20332,14 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
19717
20332
  try {
19718
20333
  let videoPath = video.src;
19719
20334
  if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
19720
- videoPath = join9(baseDir, videoPath);
20335
+ videoPath = join14(baseDir, videoPath);
19721
20336
  }
19722
20337
  if (isHttpUrl(videoPath)) {
19723
- const downloadDir = join9(options.outputDir, "_downloads");
19724
- mkdirSync8(downloadDir, { recursive: true });
20338
+ const downloadDir = join14(options.outputDir, "_downloads");
20339
+ mkdirSync11(downloadDir, { recursive: true });
19725
20340
  videoPath = await downloadToTemp(videoPath, downloadDir);
19726
20341
  }
19727
- if (!existsSync11(videoPath)) {
20342
+ if (!existsSync16(videoPath)) {
19728
20343
  return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
19729
20344
  }
19730
20345
  let videoDuration = video.end - video.start;
@@ -19878,7 +20493,7 @@ var init_videoFrameExtractor = __esm({
19878
20493
  }
19879
20494
  cleanup() {
19880
20495
  for (const video of this.videos.values()) {
19881
- if (existsSync11(video.extracted.outputDir)) {
20496
+ if (existsSync16(video.extracted.outputDir)) {
19882
20497
  rmSync3(video.extracted.outputDir, { recursive: true, force: true });
19883
20498
  }
19884
20499
  }
@@ -19980,8 +20595,8 @@ var init_videoFrameInjector = __esm({
19980
20595
  });
19981
20596
 
19982
20597
  // ../engine/src/services/audioMixer.ts
19983
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, rmSync as rmSync4 } from "fs";
19984
- 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";
19985
20600
  function parseAudioElements(html) {
19986
20601
  const elements = [];
19987
20602
  const { document: document2 } = parseHTML(html);
@@ -20031,8 +20646,8 @@ function parseAudioElements(html) {
20031
20646
  }
20032
20647
  async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
20033
20648
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
20034
- const outputDir = dirname5(outputPath);
20035
- if (!existsSync12(outputDir)) mkdirSync9(outputDir, { recursive: true });
20649
+ const outputDir = dirname6(outputPath);
20650
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
20036
20651
  const args = ["-i", videoPath];
20037
20652
  if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
20038
20653
  if (options?.duration !== void 0) args.push("-t", String(options.duration));
@@ -20058,8 +20673,8 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
20058
20673
  }
20059
20674
  async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, signal, config) {
20060
20675
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
20061
- const outputDir = dirname5(outputPath);
20062
- if (!existsSync12(outputDir)) mkdirSync9(outputDir, { recursive: true });
20676
+ const outputDir = dirname6(outputPath);
20677
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
20063
20678
  const args = [
20064
20679
  "-ss",
20065
20680
  String(mediaStart),
@@ -20094,8 +20709,8 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, sign
20094
20709
  }
20095
20710
  async function generateSilence(outputPath, duration, signal, config) {
20096
20711
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
20097
- const outputDir = dirname5(outputPath);
20098
- if (!existsSync12(outputDir)) mkdirSync9(outputDir, { recursive: true });
20712
+ const outputDir = dirname6(outputPath);
20713
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
20099
20714
  const args = [
20100
20715
  "-f",
20101
20716
  "lavfi",
@@ -20137,8 +20752,8 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
20137
20752
  error: result2.error
20138
20753
  };
20139
20754
  }
20140
- const outputDir = dirname5(outputPath);
20141
- if (!existsSync12(outputDir)) mkdirSync9(outputDir, { recursive: true });
20755
+ const outputDir = dirname6(outputPath);
20756
+ if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
20142
20757
  const inputs = [];
20143
20758
  const filterParts = [];
20144
20759
  tracks.forEach((track, i) => {
@@ -20199,7 +20814,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20199
20814
  const startMs = Date.now();
20200
20815
  const tracks = [];
20201
20816
  const errors = [];
20202
- if (!existsSync12(workDir)) mkdirSync9(workDir, { recursive: true });
20817
+ if (!existsSync17(workDir)) mkdirSync12(workDir, { recursive: true });
20203
20818
  await Promise.all(
20204
20819
  elements.map(async (element) => {
20205
20820
  if (signal?.aborted) {
@@ -20209,7 +20824,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20209
20824
  try {
20210
20825
  let srcPath = element.src;
20211
20826
  if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
20212
- srcPath = join10(baseDir, srcPath);
20827
+ srcPath = join15(baseDir, srcPath);
20213
20828
  }
20214
20829
  if (isHttpUrl(srcPath)) {
20215
20830
  try {
@@ -20221,7 +20836,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20221
20836
  return;
20222
20837
  }
20223
20838
  }
20224
- if (!existsSync12(srcPath)) {
20839
+ if (!existsSync17(srcPath)) {
20225
20840
  errors.push(`Source not found: ${element.id}`);
20226
20841
  return;
20227
20842
  }
@@ -20232,7 +20847,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20232
20847
  }
20233
20848
  let audioSrcPath = srcPath;
20234
20849
  if (element.type === "video") {
20235
- const extractedPath = join10(workDir, `${element.id}-extracted.wav`);
20850
+ const extractedPath = join15(workDir, `${element.id}-extracted.wav`);
20236
20851
  const extractResult = await extractAudioFromVideo(
20237
20852
  srcPath,
20238
20853
  extractedPath,
@@ -20249,7 +20864,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
20249
20864
  }
20250
20865
  audioSrcPath = extractedPath;
20251
20866
  } else {
20252
- const trimmedPath = join10(workDir, `${element.id}-trimmed.wav`);
20867
+ const trimmedPath = join15(workDir, `${element.id}-trimmed.wav`);
20253
20868
  const prepResult = await prepareAudioTrack(
20254
20869
  srcPath,
20255
20870
  trimmedPath,
@@ -20302,9 +20917,9 @@ var init_audioMixer = __esm({
20302
20917
 
20303
20918
  // ../engine/src/services/parallelCoordinator.ts
20304
20919
  import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
20305
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync5 } from "fs";
20920
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync7 } from "fs";
20306
20921
  import { copyFile, rename } from "fs/promises";
20307
- import { join as join11 } from "path";
20922
+ import { join as join16 } from "path";
20308
20923
  function calculateOptimalWorkers(totalFrames, requested, config) {
20309
20924
  const effectiveMaxWorkers = (() => {
20310
20925
  const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
@@ -20347,7 +20962,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
20347
20962
  workerId: i,
20348
20963
  startFrame,
20349
20964
  endFrame,
20350
- outputDir: join11(workDir, `worker-${i}`)
20965
+ outputDir: join16(workDir, `worker-${i}`)
20351
20966
  });
20352
20967
  }
20353
20968
  return tasks;
@@ -20355,7 +20970,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
20355
20970
  async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
20356
20971
  const startTime = Date.now();
20357
20972
  let framesCaptured = 0;
20358
- if (!existsSync13(task.outputDir)) mkdirSync10(task.outputDir, { recursive: true });
20973
+ if (!existsSync18(task.outputDir)) mkdirSync13(task.outputDir, { recursive: true });
20359
20974
  let session = null;
20360
20975
  let perf;
20361
20976
  try {
@@ -20445,17 +21060,17 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
20445
21060
  return results;
20446
21061
  }
20447
21062
  async function mergeWorkerFrames(workDir, tasks, outputDir) {
20448
- if (!existsSync13(outputDir)) mkdirSync10(outputDir, { recursive: true });
21063
+ if (!existsSync18(outputDir)) mkdirSync13(outputDir, { recursive: true });
20449
21064
  let totalFrames = 0;
20450
21065
  const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
20451
21066
  for (const task of sortedTasks) {
20452
- if (!existsSync13(task.outputDir)) {
21067
+ if (!existsSync18(task.outputDir)) {
20453
21068
  continue;
20454
21069
  }
20455
- 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();
20456
21071
  const copyTasks = files.map(async (file) => {
20457
- const sourcePath = join11(task.outputDir, file);
20458
- const targetPath = join11(outputDir, file);
21072
+ const sourcePath = join16(task.outputDir, file);
21073
+ const targetPath = join16(outputDir, file);
20459
21074
  try {
20460
21075
  await rename(sourcePath, targetPath);
20461
21076
  } catch {
@@ -20482,10 +21097,10 @@ var init_parallelCoordinator = __esm({
20482
21097
  });
20483
21098
 
20484
21099
  // ../engine/src/services/fileServer.ts
20485
- import { Hono } from "hono";
21100
+ import { Hono as Hono2 } from "hono";
20486
21101
  import { serve } from "@hono/node-server";
20487
- import { readFileSync as readFileSync4, existsSync as existsSync14, statSync as statSync3 } from "fs";
20488
- 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";
20489
21104
  var init_fileServer = __esm({
20490
21105
  "../engine/src/services/fileServer.ts"() {
20491
21106
  "use strict";
@@ -20515,8 +21130,8 @@ var init_src2 = __esm({
20515
21130
 
20516
21131
  // ../producer/src/services/hyperframeRuntimeLoader.ts
20517
21132
  import { createHash as createHash2 } from "crypto";
20518
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "fs";
20519
- 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";
20520
21135
  import { fileURLToPath as fileURLToPath2 } from "url";
20521
21136
  function resolveHyperframeManifestPath() {
20522
21137
  if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
@@ -20528,7 +21143,7 @@ function resolveHyperframeManifestPath() {
20528
21143
  MODULE_RELATIVE_MANIFEST_PATH
20529
21144
  ];
20530
21145
  for (const candidate of candidates) {
20531
- if (existsSync15(candidate)) {
21146
+ if (existsSync20(candidate)) {
20532
21147
  return candidate;
20533
21148
  }
20534
21149
  }
@@ -20539,12 +21154,12 @@ function getVerifiedHyperframeRuntimeSource() {
20539
21154
  }
20540
21155
  function resolveVerifiedHyperframeRuntime() {
20541
21156
  const manifestPath = resolveHyperframeManifestPath();
20542
- if (!existsSync15(manifestPath)) {
21157
+ if (!existsSync20(manifestPath)) {
20543
21158
  throw new Error(
20544
21159
  `[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
20545
21160
  );
20546
21161
  }
20547
- const manifestRaw = readFileSync5(manifestPath, "utf8");
21162
+ const manifestRaw = readFileSync11(manifestPath, "utf8");
20548
21163
  const manifest = JSON.parse(manifestRaw);
20549
21164
  const runtimeFileName = manifest.artifacts?.iife;
20550
21165
  if (!runtimeFileName || !manifest.sha256) {
@@ -20552,11 +21167,11 @@ function resolveVerifiedHyperframeRuntime() {
20552
21167
  `[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`
20553
21168
  );
20554
21169
  }
20555
- const runtimePath = resolve4(dirname6(manifestPath), runtimeFileName);
20556
- if (!existsSync15(runtimePath)) {
21170
+ const runtimePath = resolve7(dirname7(manifestPath), runtimeFileName);
21171
+ if (!existsSync20(runtimePath)) {
20557
21172
  throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
20558
21173
  }
20559
- const runtimeSource = readFileSync5(runtimePath, "utf8");
21174
+ const runtimeSource = readFileSync11(runtimePath, "utf8");
20560
21175
  const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
20561
21176
  if (runtimeSha !== manifest.sha256) {
20562
21177
  throw new Error(
@@ -20575,28 +21190,28 @@ var PRODUCER_DIR, SIBLING_MANIFEST_PATH, MODULE_RELATIVE_MANIFEST_PATH, CWD_RELA
20575
21190
  var init_hyperframeRuntimeLoader = __esm({
20576
21191
  "../producer/src/services/hyperframeRuntimeLoader.ts"() {
20577
21192
  "use strict";
20578
- PRODUCER_DIR = dirname6(fileURLToPath2(import.meta.url));
20579
- SIBLING_MANIFEST_PATH = resolve4(PRODUCER_DIR, "hyperframe.manifest.json");
20580
- 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(
20581
21196
  PRODUCER_DIR,
20582
21197
  "../../../core/dist/hyperframe.manifest.json"
20583
21198
  );
20584
21199
  CWD_RELATIVE_MANIFEST_PATHS = [
20585
21200
  // When bundled to a single file (dist/public-server.js), the manifest
20586
21201
  // is copied as a sibling by build.mjs
20587
- resolve4(PRODUCER_DIR, "hyperframe.manifest.json"),
20588
- resolve4(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
20589
- resolve4(process.cwd(), "../core/dist/hyperframe.manifest.json"),
20590
- 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")
20591
21206
  ];
20592
21207
  }
20593
21208
  });
20594
21209
 
20595
21210
  // ../producer/src/services/fileServer.ts
20596
- import { Hono as Hono2 } from "hono";
21211
+ import { Hono as Hono3 } from "hono";
20597
21212
  import { serve as serve2 } from "@hono/node-server";
20598
- import { readFileSync as readFileSync6, existsSync as existsSync16, statSync as statSync4 } from "fs";
20599
- 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";
20600
21215
  function stripEmbeddedRuntimeScripts2(html) {
20601
21216
  if (!html) return html;
20602
21217
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -20660,38 +21275,38 @@ function createFileServer2(options) {
20660
21275
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
20661
21276
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
20662
21277
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
20663
- const app = new Hono2();
21278
+ const app = new Hono3();
20664
21279
  app.get("/*", (c2) => {
20665
21280
  let requestPath = c2.req.path;
20666
21281
  if (requestPath === "/") requestPath = "/index.html";
20667
21282
  const relativePath = requestPath.replace(/^\//, "");
20668
- const compiledPath = compiledDir ? join13(compiledDir, relativePath) : null;
21283
+ const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
20669
21284
  const hasCompiledFile = Boolean(
20670
- compiledPath && existsSync16(compiledPath) && statSync4(compiledPath).isFile()
21285
+ compiledPath && existsSync21(compiledPath) && statSync6(compiledPath).isFile()
20671
21286
  );
20672
- const filePath = hasCompiledFile ? compiledPath : join13(projectDir, relativePath);
20673
- if (!existsSync16(filePath) || !statSync4(filePath).isFile()) {
21287
+ const filePath = hasCompiledFile ? compiledPath : join18(projectDir, relativePath);
21288
+ if (!existsSync21(filePath) || !statSync6(filePath).isFile()) {
20674
21289
  return c2.text("Not found", 404);
20675
21290
  }
20676
21291
  const ext = extname3(filePath).toLowerCase();
20677
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
21292
+ const contentType = MIME_TYPES2[ext] || "application/octet-stream";
20678
21293
  if (ext === ".html") {
20679
- const rawHtml = readFileSync6(filePath, "utf-8");
21294
+ const rawHtml = readFileSync12(filePath, "utf-8");
20680
21295
  const isIndex = relativePath === "index.html";
20681
21296
  const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
20682
21297
  return c2.text(html, 200, { "Content-Type": contentType });
20683
21298
  }
20684
- const content = readFileSync6(filePath);
21299
+ const content = readFileSync12(filePath);
20685
21300
  return new Response(content, {
20686
21301
  status: 200,
20687
21302
  headers: { "Content-Type": contentType }
20688
21303
  });
20689
21304
  });
20690
- return new Promise((resolve17) => {
21305
+ return new Promise((resolve20) => {
20691
21306
  const server = serve2({ fetch: app.fetch, port }, (info) => {
20692
21307
  const actualPort = info.port;
20693
21308
  const url = `http://localhost:${actualPort}`;
20694
- resolve17({
21309
+ resolve20({
20695
21310
  url,
20696
21311
  port: actualPort,
20697
21312
  close: () => server.close()
@@ -20699,12 +21314,12 @@ function createFileServer2(options) {
20699
21314
  });
20700
21315
  });
20701
21316
  }
20702
- 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;
20703
21318
  var init_fileServer2 = __esm({
20704
21319
  "../producer/src/services/fileServer.ts"() {
20705
21320
  "use strict";
20706
21321
  init_hyperframeRuntimeLoader();
20707
- MIME_TYPES = {
21322
+ MIME_TYPES2 = {
20708
21323
  ".html": "text/html; charset=utf-8",
20709
21324
  ".css": "text/css; charset=utf-8",
20710
21325
  ".js": "application/javascript; charset=utf-8",
@@ -21133,8 +21748,8 @@ var init_deterministicFonts = __esm({
21133
21748
  });
21134
21749
 
21135
21750
  // ../producer/src/services/htmlCompiler.ts
21136
- import { readFileSync as readFileSync7, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
21137
- 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";
21138
21753
  function dedupeElementsById(elements) {
21139
21754
  const deduped = /* @__PURE__ */ new Map();
21140
21755
  for (const element of elements) {
@@ -21145,16 +21760,16 @@ function dedupeElementsById(elements) {
21145
21760
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
21146
21761
  let filePath = src;
21147
21762
  if (isHttpUrl(src)) {
21148
- if (!existsSync17(downloadDir)) mkdirSync11(downloadDir, { recursive: true });
21763
+ if (!existsSync22(downloadDir)) mkdirSync14(downloadDir, { recursive: true });
21149
21764
  try {
21150
21765
  filePath = await downloadToTemp(src, downloadDir);
21151
21766
  } catch {
21152
21767
  return { duration: 0, resolvedPath: src };
21153
21768
  }
21154
21769
  } else if (!filePath.startsWith("/")) {
21155
- filePath = join14(baseDir, filePath);
21770
+ filePath = join19(baseDir, filePath);
21156
21771
  }
21157
- if (!existsSync17(filePath)) {
21772
+ if (!existsSync22(filePath)) {
21158
21773
  return { duration: 0, resolvedPath: filePath };
21159
21774
  }
21160
21775
  const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
@@ -21217,14 +21832,14 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
21217
21832
  const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
21218
21833
  const absoluteStart = parentOffset + elStart;
21219
21834
  const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
21220
- const filePath = resolve5(projectDir, srcPath);
21835
+ const filePath = resolve8(projectDir, srcPath);
21221
21836
  if (visited.has(filePath)) {
21222
21837
  continue;
21223
21838
  }
21224
- if (!existsSync17(filePath)) {
21839
+ if (!existsSync22(filePath)) {
21225
21840
  continue;
21226
21841
  }
21227
- const rawSubHtml = readFileSync7(filePath, "utf-8");
21842
+ const rawSubHtml = readFileSync13(filePath, "utf-8");
21228
21843
  const nestedVisited = new Set(visited);
21229
21844
  nestedVisited.add(filePath);
21230
21845
  workItems.push({ srcPath, absoluteStart, absoluteEnd, filePath, rawSubHtml, nestedVisited });
@@ -21233,7 +21848,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
21233
21848
  workItems.map(async (item) => {
21234
21849
  const { html: compiledSub } = await compileHtmlFile(
21235
21850
  item.rawSubHtml,
21236
- dirname7(item.filePath),
21851
+ dirname8(item.filePath),
21237
21852
  downloadDir
21238
21853
  );
21239
21854
  const nested = await parseSubCompositions(
@@ -21417,9 +22032,9 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
21417
22032
  if (!srcPath) continue;
21418
22033
  let compHtml = subCompositions.get(srcPath) || null;
21419
22034
  if (!compHtml) {
21420
- const filePath = resolve5(projectDir, srcPath);
21421
- if (existsSync17(filePath)) {
21422
- compHtml = readFileSync7(filePath, "utf-8");
22035
+ const filePath = resolve8(projectDir, srcPath);
22036
+ if (existsSync22(filePath)) {
22037
+ compHtml = readFileSync13(filePath, "utf-8");
21423
22038
  }
21424
22039
  }
21425
22040
  if (!compHtml) {
@@ -21539,7 +22154,7 @@ ${html}
21539
22154
  </html>`;
21540
22155
  }
21541
22156
  async function compileForRender(projectDir, htmlPath, downloadDir) {
21542
- const rawHtml = readFileSync7(htmlPath, "utf-8");
22157
+ const rawHtml = readFileSync13(htmlPath, "utf-8");
21543
22158
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
21544
22159
  rawHtml,
21545
22160
  projectDir,
@@ -21727,15 +22342,15 @@ var init_logger = __esm({
21727
22342
 
21728
22343
  // ../producer/src/services/renderOrchestrator.ts
21729
22344
  import {
21730
- existsSync as existsSync18,
21731
- mkdirSync as mkdirSync12,
22345
+ existsSync as existsSync23,
22346
+ mkdirSync as mkdirSync15,
21732
22347
  rmSync as rmSync5,
21733
- readFileSync as readFileSync8,
21734
- writeFileSync as writeFileSync4,
22348
+ readFileSync as readFileSync14,
22349
+ writeFileSync as writeFileSync6,
21735
22350
  copyFileSync as copyFileSync2,
21736
22351
  appendFileSync
21737
22352
  } from "fs";
21738
- 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";
21739
22354
  import { randomUUID as randomUUID2 } from "crypto";
21740
22355
  import { freemem as freemem2 } from "os";
21741
22356
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -21791,13 +22406,13 @@ function installDebugLogger(logPath, log = defaultLogger) {
21791
22406
  };
21792
22407
  }
21793
22408
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
21794
- const compileDir = join15(workDir, "compiled");
21795
- mkdirSync12(compileDir, { recursive: true });
21796
- 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");
21797
22412
  for (const [srcPath, html] of compiled.subCompositions) {
21798
- const outPath = join15(compileDir, srcPath);
21799
- mkdirSync12(dirname8(outPath), { recursive: true });
21800
- writeFileSync4(outPath, html, "utf-8");
22413
+ const outPath = join20(compileDir, srcPath);
22414
+ mkdirSync15(dirname9(outPath), { recursive: true });
22415
+ writeFileSync6(outPath, html, "utf-8");
21801
22416
  }
21802
22417
  if (includeSummary) {
21803
22418
  const summary = {
@@ -21820,7 +22435,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
21820
22435
  })),
21821
22436
  subCompositions: Array.from(compiled.subCompositions.keys())
21822
22437
  };
21823
- 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");
21824
22439
  }
21825
22440
  }
21826
22441
  function createRenderJob(config) {
@@ -21863,10 +22478,10 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
21863
22478
  return document2.toString();
21864
22479
  }
21865
22480
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
21866
- const moduleDir = dirname8(fileURLToPath3(import.meta.url));
21867
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve6(process.env.PRODUCER_RENDERS_DIR, "..") : resolve6(moduleDir, "../..");
21868
- const debugDir = join15(producerRoot, ".debug");
21869
- 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}`);
21870
22485
  const pipelineStart = Date.now();
21871
22486
  const log = job.config.logger ?? defaultLogger;
21872
22487
  let fileServer = null;
@@ -21874,7 +22489,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21874
22489
  let lastBrowserConsole = [];
21875
22490
  let restoreLogger = null;
21876
22491
  const perfStages = {};
21877
- const perfOutputPath = join15(workDir, "perf-summary.json");
22492
+ const perfOutputPath = join20(workDir, "perf-summary.json");
21878
22493
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
21879
22494
  const outputFormat = job.config.format ?? "mp4";
21880
22495
  const isWebm = outputFormat === "webm";
@@ -21892,28 +22507,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21892
22507
  };
21893
22508
  job.startedAt = /* @__PURE__ */ new Date();
21894
22509
  assertNotAborted();
21895
- if (!existsSync18(workDir)) mkdirSync12(workDir, { recursive: true });
22510
+ if (!existsSync23(workDir)) mkdirSync15(workDir, { recursive: true });
21896
22511
  if (job.config.debug) {
21897
- const logPath = join15(workDir, "render.log");
22512
+ const logPath = join20(workDir, "render.log");
21898
22513
  restoreLogger = installDebugLogger(logPath, log);
21899
22514
  }
21900
22515
  const entryFile = job.config.entryFile || "index.html";
21901
- let htmlPath = join15(projectDir, entryFile);
21902
- if (!existsSync18(htmlPath)) {
22516
+ let htmlPath = join20(projectDir, entryFile);
22517
+ if (!existsSync23(htmlPath)) {
21903
22518
  throw new Error(`Entry file not found: ${htmlPath}`);
21904
22519
  }
21905
22520
  assertNotAborted();
21906
- const rawEntry = readFileSync8(htmlPath, "utf-8");
22521
+ const rawEntry = readFileSync14(htmlPath, "utf-8");
21907
22522
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
21908
- const wrapperPath = join15(workDir, "standalone-entry.html");
21909
- const projectIndexPath = join15(projectDir, "index.html");
21910
- if (!existsSync18(projectIndexPath)) {
22523
+ const wrapperPath = join20(workDir, "standalone-entry.html");
22524
+ const projectIndexPath = join20(projectDir, "index.html");
22525
+ if (!existsSync23(projectIndexPath)) {
21911
22526
  throw new Error(
21912
22527
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
21913
22528
  );
21914
22529
  }
21915
22530
  const standaloneHtml = extractStandaloneEntryFromIndex(
21916
- readFileSync8(projectIndexPath, "utf-8"),
22531
+ readFileSync14(projectIndexPath, "utf-8"),
21917
22532
  entryFile
21918
22533
  );
21919
22534
  if (!standaloneHtml) {
@@ -21921,7 +22536,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21921
22536
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
21922
22537
  );
21923
22538
  }
21924
- writeFileSync4(wrapperPath, standaloneHtml, "utf-8");
22539
+ writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
21925
22540
  htmlPath = wrapperPath;
21926
22541
  log.info("Extracted standalone entry from index.html host context", {
21927
22542
  entryFile
@@ -21930,7 +22545,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21930
22545
  const stage1Start = Date.now();
21931
22546
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
21932
22547
  const compileStart = Date.now();
21933
- let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
22548
+ let compiled = await compileForRender(projectDir, htmlPath, join20(workDir, "downloads"));
21934
22549
  assertNotAborted();
21935
22550
  perfStages.compileOnlyMs = Date.now() - compileStart;
21936
22551
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
@@ -21959,7 +22574,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21959
22574
  reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
21960
22575
  fileServer = await createFileServer2({
21961
22576
  projectDir,
21962
- compiledDir: join15(workDir, "compiled"),
22577
+ compiledDir: join20(workDir, "compiled"),
21963
22578
  port: 0
21964
22579
  });
21965
22580
  assertNotAborted();
@@ -21972,7 +22587,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
21972
22587
  };
21973
22588
  probeSession = await createCaptureSession(
21974
22589
  fileServer.url,
21975
- join15(workDir, "probe"),
22590
+ join20(workDir, "probe"),
21976
22591
  captureOpts,
21977
22592
  null,
21978
22593
  cfg
@@ -22004,7 +22619,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22004
22619
  compiled,
22005
22620
  resolutions,
22006
22621
  projectDir,
22007
- join15(workDir, "downloads")
22622
+ join20(workDir, "downloads")
22008
22623
  );
22009
22624
  assertNotAborted();
22010
22625
  composition.videos = compiled.videos;
@@ -22101,7 +22716,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22101
22716
  const extractionResult = await extractAllVideoFrames(
22102
22717
  composition.videos,
22103
22718
  projectDir,
22104
- { fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
22719
+ { fps: job.config.fps, outputDir: join20(workDir, "video-frames") },
22105
22720
  abortSignal
22106
22721
  );
22107
22722
  assertNotAborted();
@@ -22133,13 +22748,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22133
22748
  }
22134
22749
  const stage3Start = Date.now();
22135
22750
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
22136
- const audioOutputPath = join15(workDir, "audio.aac");
22751
+ const audioOutputPath = join20(workDir, "audio.aac");
22137
22752
  let hasAudio = false;
22138
22753
  if (composition.audios.length > 0) {
22139
22754
  const audioResult = await processCompositionAudio(
22140
22755
  composition.audios,
22141
22756
  projectDir,
22142
- join15(workDir, "audio-work"),
22757
+ join20(workDir, "audio-work"),
22143
22758
  audioOutputPath,
22144
22759
  job.duration,
22145
22760
  abortSignal
@@ -22155,13 +22770,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22155
22770
  if (!fileServer) {
22156
22771
  fileServer = await createFileServer2({
22157
22772
  projectDir,
22158
- compiledDir: join15(workDir, "compiled"),
22773
+ compiledDir: join20(workDir, "compiled"),
22159
22774
  port: 0
22160
22775
  });
22161
22776
  assertNotAborted();
22162
22777
  }
22163
- const framesDir = join15(workDir, "captured-frames");
22164
- if (!existsSync18(framesDir)) mkdirSync12(framesDir, { recursive: true });
22778
+ const framesDir = join20(workDir, "captured-frames");
22779
+ if (!existsSync23(framesDir)) mkdirSync15(framesDir, { recursive: true });
22165
22780
  const captureOptions = {
22166
22781
  width,
22167
22782
  height,
@@ -22171,7 +22786,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22171
22786
  };
22172
22787
  const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
22173
22788
  const videoExt = isWebm ? ".webm" : ".mp4";
22174
- const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
22789
+ const videoOnlyPath = join20(workDir, `video-only${videoExt}`);
22175
22790
  const preset = getEncoderPreset(job.config.quality, outputFormat);
22176
22791
  job.framesRendered = 0;
22177
22792
  let streamingEncoder = null;
@@ -22440,7 +23055,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22440
23055
  job.perfSummary = perfSummary;
22441
23056
  if (job.config.debug) {
22442
23057
  try {
22443
- writeFileSync4(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
23058
+ writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
22444
23059
  } catch (err) {
22445
23060
  log.debug("Failed to write perf summary", {
22446
23061
  perfOutputPath,
@@ -22449,8 +23064,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22449
23064
  }
22450
23065
  }
22451
23066
  if (job.config.debug) {
22452
- if (existsSync18(outputPath)) {
22453
- const debugOutput = join15(workDir, isWebm ? "output.webm" : "output.mp4");
23067
+ if (existsSync23(outputPath)) {
23068
+ const debugOutput = join20(workDir, isWebm ? "output.webm" : "output.mp4");
22454
23069
  copyFileSync2(outputPath, debugOutput);
22455
23070
  }
22456
23071
  } else {
@@ -22526,7 +23141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
22526
23141
  await safeCleanup(
22527
23142
  "remove workDir (error)",
22528
23143
  () => {
22529
- if (existsSync18(workDir)) rmSync5(workDir, { recursive: true, force: true });
23144
+ if (existsSync23(workDir)) rmSync5(workDir, { recursive: true, force: true });
22530
23145
  },
22531
23146
  log
22532
23147
  );
@@ -22579,17 +23194,9 @@ var init_config3 = __esm({
22579
23194
  }
22580
23195
  });
22581
23196
 
22582
- // ../core/src/lint/index.ts
22583
- var init_lint = __esm({
22584
- "../core/src/lint/index.ts"() {
22585
- "use strict";
22586
- init_hyperframeLinter();
22587
- }
22588
- });
22589
-
22590
23197
  // ../producer/src/services/hyperframeLint.ts
22591
- import { existsSync as existsSync19, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
22592
- 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";
22593
23200
  function isStringRecord(value) {
22594
23201
  if (!value || typeof value !== "object" || Array.isArray(value)) {
22595
23202
  return false;
@@ -22616,28 +23223,28 @@ function pickEntryFile(files, preferredEntryFile) {
22616
23223
  return null;
22617
23224
  }
22618
23225
  function readProjectEntryFile(projectDir, preferredEntryFile) {
22619
- const absProjectDir = resolve7(projectDir);
22620
- if (!existsSync19(absProjectDir) || !statSync5(absProjectDir).isDirectory()) {
23226
+ const absProjectDir = resolve10(projectDir);
23227
+ if (!existsSync24(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
22621
23228
  return { error: `Project directory not found: ${absProjectDir}` };
22622
23229
  }
22623
23230
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
22624
23231
  (value) => typeof value === "string" && value.trim().length > 0
22625
23232
  );
22626
23233
  for (const entryFile of entryCandidates) {
22627
- const absoluteEntryPath = resolve7(absProjectDir, entryFile);
23234
+ const absoluteEntryPath = resolve10(absProjectDir, entryFile);
22628
23235
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
22629
23236
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
22630
23237
  }
22631
- if (existsSync19(absoluteEntryPath) && statSync5(absoluteEntryPath).isFile()) {
23238
+ if (existsSync24(absoluteEntryPath) && statSync7(absoluteEntryPath).isFile()) {
22632
23239
  return {
22633
23240
  entryFile,
22634
- html: readFileSync9(absoluteEntryPath, "utf-8"),
23241
+ html: readFileSync15(absoluteEntryPath, "utf-8"),
22635
23242
  source: "projectDir"
22636
23243
  };
22637
23244
  }
22638
23245
  }
22639
23246
  return {
22640
- 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")}`
22641
23248
  };
22642
23249
  }
22643
23250
  function prepareHyperframeLintBody(body) {
@@ -22679,43 +23286,43 @@ function runHyperframeLint(prepared) {
22679
23286
  var init_hyperframeLint = __esm({
22680
23287
  "../producer/src/services/hyperframeLint.ts"() {
22681
23288
  "use strict";
22682
- init_lint();
23289
+ init_lint2();
22683
23290
  }
22684
23291
  });
22685
23292
 
22686
23293
  // ../producer/src/utils/paths.ts
22687
- import { resolve as resolve8, basename, join as join17 } from "path";
23294
+ import { resolve as resolve11, basename, join as join22 } from "path";
22688
23295
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
22689
- const absoluteProjectDir = resolve8(projectDir);
23296
+ const absoluteProjectDir = resolve11(projectDir);
22690
23297
  const projectName = basename(absoluteProjectDir);
22691
- const resolvedOutputPath = outputPath ?? join17(rendersDir, `${projectName}.mp4`);
22692
- const absoluteOutputPath = resolve8(resolvedOutputPath);
23298
+ const resolvedOutputPath = outputPath ?? join22(rendersDir, `${projectName}.mp4`);
23299
+ const absoluteOutputPath = resolve11(resolvedOutputPath);
22693
23300
  return { absoluteProjectDir, absoluteOutputPath };
22694
23301
  }
22695
23302
  var DEFAULT_RENDERS_DIR;
22696
23303
  var init_paths = __esm({
22697
23304
  "../producer/src/utils/paths.ts"() {
22698
23305
  "use strict";
22699
- 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");
22700
23307
  }
22701
23308
  });
22702
23309
 
22703
23310
  // ../producer/src/server.ts
22704
23311
  import {
22705
- existsSync as existsSync20,
22706
- mkdirSync as mkdirSync13,
22707
- statSync as statSync6,
23312
+ existsSync as existsSync25,
23313
+ mkdirSync as mkdirSync16,
23314
+ statSync as statSync8,
22708
23315
  mkdtempSync,
22709
- writeFileSync as writeFileSync5,
23316
+ writeFileSync as writeFileSync7,
22710
23317
  rmSync as rmSync6,
22711
23318
  createReadStream
22712
23319
  } from "fs";
22713
- 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";
22714
23321
  import { tmpdir } from "os";
22715
23322
  import { parseArgs as parseArgs2 } from "util";
22716
23323
  import crypto from "crypto";
22717
- import { Hono as Hono3 } from "hono";
22718
- import { streamSSE } from "hono/streaming";
23324
+ import { Hono as Hono4 } from "hono";
23325
+ import { streamSSE as streamSSE2 } from "hono/streaming";
22719
23326
  import { serve as serve3 } from "@hono/node-server";
22720
23327
  function parseRenderOptions(body) {
22721
23328
  const fps = [24, 30, 60].includes(body.fps) ? body.fps : 30;
@@ -22725,18 +23332,19 @@ function parseRenderOptions(body) {
22725
23332
  const debug = body.debug === true;
22726
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;
22727
23334
  const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
22728
- 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 };
22729
23337
  }
22730
23338
  async function prepareRenderBody(body) {
22731
23339
  const options = parseRenderOptions(body);
22732
23340
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
22733
23341
  if (projectDir) {
22734
- const absProjectDir = resolve9(projectDir);
22735
- if (!existsSync20(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
23342
+ const absProjectDir = resolve12(projectDir);
23343
+ if (!existsSync25(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
22736
23344
  return { error: `Project directory not found: ${absProjectDir}` };
22737
23345
  }
22738
23346
  const entry = options.entryFile || "index.html";
22739
- if (!existsSync20(resolve9(absProjectDir, entry))) {
23347
+ if (!existsSync25(resolve12(absProjectDir, entry))) {
22740
23348
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
22741
23349
  }
22742
23350
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -22761,8 +23369,8 @@ async function prepareRenderBody(body) {
22761
23369
  }
22762
23370
  }
22763
23371
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir();
22764
- const tempProjectDir = mkdtempSync(join18(tempRoot, "producer-project-"));
22765
- 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");
22766
23374
  return {
22767
23375
  prepared: {
22768
23376
  input: {
@@ -22777,7 +23385,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
22777
23385
  try {
22778
23386
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
22779
23387
  } catch (error) {
22780
- const fallbackPath = resolve9(rendersDir, `producer-fallback-${Date.now()}.mp4`);
23388
+ const fallbackPath = resolve12(rendersDir, `producer-fallback-${Date.now()}.mp4`);
22781
23389
  log.warn("Failed to resolve output path, using fallback", {
22782
23390
  fallback: fallbackPath,
22783
23391
  error: error instanceof Error ? error.message : String(error)
@@ -22882,8 +23490,8 @@ function createRenderHandlers(options = {}) {
22882
23490
  rendersDir,
22883
23491
  log
22884
23492
  );
22885
- const outputDir = dirname9(absoluteOutputPath);
22886
- if (!existsSync20(outputDir)) mkdirSync13(outputDir, { recursive: true });
23493
+ const outputDir = dirname10(absoluteOutputPath);
23494
+ if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
22887
23495
  log.info("render started", {
22888
23496
  requestId,
22889
23497
  projectDir: input.projectDir,
@@ -22893,6 +23501,7 @@ function createRenderHandlers(options = {}) {
22893
23501
  const job = createRenderJob({
22894
23502
  fps: input.fps,
22895
23503
  quality: input.quality,
23504
+ format: input.format,
22896
23505
  workers: input.workers,
22897
23506
  useGpu: input.useGpu,
22898
23507
  debug: input.debug,
@@ -22908,7 +23517,7 @@ function createRenderHandlers(options = {}) {
22908
23517
  log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
22909
23518
  }
22910
23519
  });
22911
- const fileSize = existsSync20(absoluteOutputPath) ? statSync6(absoluteOutputPath).size : 0;
23520
+ const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
22912
23521
  const durationMs = Date.now() - t0;
22913
23522
  const outputToken = store.register(absoluteOutputPath);
22914
23523
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
@@ -22954,7 +23563,7 @@ function createRenderHandlers(options = {}) {
22954
23563
  }
22955
23564
  };
22956
23565
  const renderStream = (c2) => {
22957
- return streamSSE(c2, async (stream) => {
23566
+ return streamSSE2(c2, async (stream) => {
22958
23567
  const requestId = getRequestId(c2);
22959
23568
  const t0 = Date.now();
22960
23569
  let body;
@@ -22990,12 +23599,13 @@ function createRenderHandlers(options = {}) {
22990
23599
  rendersDir,
22991
23600
  log
22992
23601
  );
22993
- const outputDir = dirname9(absoluteOutputPath);
22994
- if (!existsSync20(outputDir)) mkdirSync13(outputDir, { recursive: true });
23602
+ const outputDir = dirname10(absoluteOutputPath);
23603
+ if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
22995
23604
  log.info("render-stream started", { requestId, projectDir: input.projectDir });
22996
23605
  const job = createRenderJob({
22997
23606
  fps: input.fps,
22998
23607
  quality: input.quality,
23608
+ format: input.format,
22999
23609
  workers: input.workers,
23000
23610
  useGpu: input.useGpu,
23001
23611
  debug: input.debug,
@@ -23025,7 +23635,7 @@ function createRenderHandlers(options = {}) {
23025
23635
  },
23026
23636
  abortController.signal
23027
23637
  );
23028
- const fileSize = existsSync20(absoluteOutputPath) ? statSync6(absoluteOutputPath).size : 0;
23638
+ const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
23029
23639
  const outputToken = store.register(absoluteOutputPath);
23030
23640
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
23031
23641
  log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
@@ -23083,11 +23693,11 @@ function createRenderHandlers(options = {}) {
23083
23693
  if (!artifact) {
23084
23694
  return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
23085
23695
  }
23086
- if (!existsSync20(artifact.path)) {
23696
+ if (!existsSync25(artifact.path)) {
23087
23697
  store.delete(token);
23088
23698
  return c2.json({ success: false, error: "Output artifact file missing" }, 404);
23089
23699
  }
23090
- const stats = statSync6(artifact.path);
23700
+ const stats = statSync8(artifact.path);
23091
23701
  return new Response(createReadStream(artifact.path), {
23092
23702
  headers: {
23093
23703
  "content-type": "video/mp4",
@@ -23099,7 +23709,7 @@ function createRenderHandlers(options = {}) {
23099
23709
  return { render: render2, renderStream, lint, health, outputs };
23100
23710
  }
23101
23711
  function createProducerApp(options = {}) {
23102
- const app = new Hono3();
23712
+ const app = new Hono4();
23103
23713
  const handlers = createRenderHandlers(options);
23104
23714
  app.get("/health", handlers.health);
23105
23715
  app.post("/render", handlers.render);
@@ -23141,7 +23751,7 @@ var init_server = __esm({
23141
23751
  init_hyperframeLint();
23142
23752
  init_paths();
23143
23753
  init_logger();
23144
- entryScript = process.argv[1] ? resolve9(process.argv[1]) : "";
23754
+ entryScript = process.argv[1] ? resolve12(process.argv[1]) : "";
23145
23755
  isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
23146
23756
  if (isPublicServerEntry) {
23147
23757
  const { values } = parseArgs2({
@@ -23217,9 +23827,9 @@ __export(manager_exports2, {
23217
23827
  setBrowserPath: () => setBrowserPath
23218
23828
  });
23219
23829
  import { execSync } from "child_process";
23220
- import { existsSync as existsSync21, rmSync as rmSync7 } from "fs";
23830
+ import { existsSync as existsSync26, rmSync as rmSync7 } from "fs";
23221
23831
  import { homedir as homedir5 } from "os";
23222
- import { join as join19 } from "path";
23832
+ import { join as join24 } from "path";
23223
23833
  import { Browser, detectBrowserPlatform, getInstalledBrowsers, install } from "@puppeteer/browsers";
23224
23834
  function setBrowserPath(path) {
23225
23835
  _browserPathOverride = path;
@@ -23237,17 +23847,17 @@ function whichBinary2(name) {
23237
23847
  }
23238
23848
  }
23239
23849
  function findFromEnv2() {
23240
- if (_browserPathOverride && existsSync21(_browserPathOverride)) {
23850
+ if (_browserPathOverride && existsSync26(_browserPathOverride)) {
23241
23851
  return { executablePath: _browserPathOverride, source: "env" };
23242
23852
  }
23243
23853
  const envPath = process.env["HYPERFRAMES_BROWSER_PATH"];
23244
- if (envPath && existsSync21(envPath)) {
23854
+ if (envPath && existsSync26(envPath)) {
23245
23855
  return { executablePath: envPath, source: "env" };
23246
23856
  }
23247
23857
  return void 0;
23248
23858
  }
23249
23859
  async function findFromCache() {
23250
- if (!existsSync21(CACHE_DIR)) {
23860
+ if (!existsSync26(CACHE_DIR)) {
23251
23861
  return void 0;
23252
23862
  }
23253
23863
  const installed = await getInstalledBrowsers({ cacheDir: CACHE_DIR });
@@ -23259,7 +23869,7 @@ async function findFromCache() {
23259
23869
  }
23260
23870
  function findFromSystem2() {
23261
23871
  for (const p of SYSTEM_CHROME_PATHS) {
23262
- if (existsSync21(p)) {
23872
+ if (existsSync26(p)) {
23263
23873
  return { executablePath: p, source: "system" };
23264
23874
  }
23265
23875
  }
@@ -23293,7 +23903,7 @@ async function ensureBrowser(options) {
23293
23903
  return { executablePath: installed.executablePath, source: "download" };
23294
23904
  }
23295
23905
  function clearBrowser() {
23296
- if (!existsSync21(CACHE_DIR)) {
23906
+ if (!existsSync26(CACHE_DIR)) {
23297
23907
  return false;
23298
23908
  }
23299
23909
  rmSync7(CACHE_DIR, { recursive: true, force: true });
@@ -23304,7 +23914,7 @@ var init_manager2 = __esm({
23304
23914
  "src/browser/manager.ts"() {
23305
23915
  "use strict";
23306
23916
  CHROME_VERSION = "131.0.6778.85";
23307
- CACHE_DIR = join19(homedir5(), ".cache", "hyperframes", "chrome");
23917
+ CACHE_DIR = join24(homedir5(), ".cache", "hyperframes", "chrome");
23308
23918
  SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
23309
23919
  "/usr/bin/google-chrome",
23310
23920
  "/usr/bin/google-chrome-stable",
@@ -23319,21 +23929,21 @@ var studioServer_exports = {};
23319
23929
  __export(studioServer_exports, {
23320
23930
  createStudioServer: () => createStudioServer
23321
23931
  });
23322
- import { Hono as Hono4 } from "hono";
23323
- import { streamSSE as streamSSE2 } from "hono/streaming";
23324
- import { existsSync as existsSync22, readFileSync as readFileSync10, readdirSync as readdirSync6, statSync as statSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync14 } from "fs";
23325
- 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";
23326
23936
  function resolveDistDir() {
23327
- const builtPath = resolve10(__dirname, "studio");
23328
- if (existsSync22(resolve10(builtPath, "index.html"))) return builtPath;
23329
- const devPath = resolve10(__dirname, "..", "..", "..", "studio", "dist");
23330
- if (existsSync22(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;
23331
23941
  return builtPath;
23332
23942
  }
23333
23943
  function resolveRuntimePath() {
23334
- const builtPath = resolve10(__dirname, "hyperframe-runtime.js");
23335
- if (existsSync22(builtPath)) return builtPath;
23336
- const devPath = resolve10(
23944
+ const builtPath = resolve13(__dirname, "hyperframe-runtime.js");
23945
+ if (existsSync27(builtPath)) return builtPath;
23946
+ const devPath = resolve13(
23337
23947
  __dirname,
23338
23948
  "..",
23339
23949
  "..",
@@ -23342,96 +23952,97 @@ function resolveRuntimePath() {
23342
23952
  "dist",
23343
23953
  "hyperframe.runtime.iife.js"
23344
23954
  );
23345
- if (existsSync22(devPath)) return devPath;
23955
+ if (existsSync27(devPath)) return devPath;
23346
23956
  return builtPath;
23347
23957
  }
23348
- function isSafePath(base, resolved) {
23349
- const norm = resolve10(base) + sep2;
23350
- return resolved.startsWith(norm) || resolved === resolve10(base);
23351
- }
23352
- function getMimeType(filePath) {
23353
- const ext = extname4(filePath).toLowerCase();
23354
- return MIME_TYPES2[ext] ?? "application/octet-stream";
23355
- }
23356
- function walkDir(dir, prefix = "") {
23357
- const files = [];
23358
- for (const entry of readdirSync6(dir, { withFileTypes: true })) {
23359
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
23360
- if (entry.isDirectory()) {
23361
- files.push(...walkDir(join20(dir, entry.name), rel));
23362
- } else {
23363
- files.push(rel);
23364
- }
23365
- }
23366
- return files;
23367
- }
23368
- function serveStaticFile(filePath) {
23369
- if (!existsSync22(filePath) || !statSync7(filePath).isFile()) return null;
23370
- const mime = getMimeType(filePath);
23371
- const content = readFileSync10(filePath);
23372
- return new Response(content, {
23373
- headers: { "Content-Type": mime, "Cache-Control": "no-store" }
23374
- });
23375
- }
23376
- function buildSubCompositionHtml(projectDir, compPath, runtimeUrl) {
23377
- const compFile = resolve10(projectDir, compPath);
23378
- if (!isSafePath(projectDir, compFile) || !existsSync22(compFile) || !statSync7(compFile).isFile()) {
23379
- return null;
23380
- }
23381
- let rawComp = readFileSync10(compFile, "utf-8");
23382
- const templateMatch = rawComp.match(/<template>([\s\S]*)<\/template>/i);
23383
- let content = (templateMatch ? templateMatch[1] : rawComp) ?? rawComp;
23384
- content = content.replace(
23385
- /(<[^>]*?)(data-composition-src=["']([^"']+)["'])([^>]*>)/g,
23386
- (_match, before2, srcAttr, src, after2) => {
23387
- const nestedFile = join20(projectDir, src);
23388
- if (!existsSync22(nestedFile)) return before2 + srcAttr + after2;
23389
- const nestedRaw = readFileSync10(nestedFile, "utf-8");
23390
- const nestedTemplate = nestedRaw.match(/<template>([\s\S]*)<\/template>/i);
23391
- const nestedContent = (nestedTemplate ? nestedTemplate[1] : nestedRaw) ?? nestedRaw;
23392
- const styles = [];
23393
- const scripts = [];
23394
- let body = nestedContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_2, css) => {
23395
- styles.push(css);
23396
- return "";
23397
- }).replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, (_2, js) => {
23398
- scripts.push(js);
23399
- return "";
23400
- });
23401
- const innerRootMatch = body.match(
23402
- /<([a-z][a-z0-9]*)\b[^>]*data-composition-id[^>]*>([\s\S]*)<\/\1>/i
23403
- );
23404
- const innerHTML = innerRootMatch ? innerRootMatch[2] : body;
23405
- 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>` : "");
23406
- }
23407
- );
23408
- return `<!DOCTYPE html>
23409
- <html>
23410
- <head>
23411
- <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
23412
- <script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>
23413
- </head>
23414
- <body>
23415
- ${content}
23416
- </body>
23417
- </html>`;
23418
- }
23419
23958
  function createStudioServer(options) {
23420
- const { projectDir } = options;
23421
- const projectId = basename2(projectDir);
23959
+ const { projectDir, projectName } = options;
23960
+ const projectId = projectName || basename2(projectDir);
23422
23961
  const studioDir = resolveDistDir();
23423
23962
  const runtimePath = resolveRuntimePath();
23424
23963
  const watcher = createProjectWatcher(projectDir);
23425
- 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();
23426
24037
  app.get("/api/runtime.js", (c2) => {
23427
- if (!existsSync22(runtimePath)) return c2.text("runtime not built", 404);
23428
- return c2.body(readFileSync10(runtimePath, "utf-8"), 200, {
24038
+ if (!existsSync27(runtimePath)) return c2.text("runtime not built", 404);
24039
+ return c2.body(readFileSync16(runtimePath, "utf-8"), 200, {
23429
24040
  "Content-Type": "text/javascript",
23430
24041
  "Cache-Control": "no-store"
23431
24042
  });
23432
24043
  });
23433
24044
  app.get("/api/events", (c2) => {
23434
- return streamSSE2(c2, async (stream) => {
24045
+ return streamSSE3(c2, async (stream) => {
23435
24046
  const listener = () => {
23436
24047
  stream.writeSSE({ event: "file-change", data: "{}" }).catch(() => {
23437
24048
  });
@@ -23442,252 +24053,49 @@ function createStudioServer(options) {
23442
24053
  }
23443
24054
  });
23444
24055
  });
23445
- app.get("/api/projects", (c2) => {
23446
- return c2.json({ projects: [{ id: projectId, title: projectId }] });
23447
- });
23448
- app.get("/api/projects/:id", (c2) => {
23449
- const id = c2.req.param("id");
23450
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23451
- const files = walkDir(projectDir);
23452
- return c2.json({ id: projectId, files });
23453
- });
23454
- app.get("/api/projects/:id/preview", async (c2) => {
23455
- const id = c2.req.param("id");
23456
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23457
- let bundled;
23458
- try {
23459
- const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
23460
- bundled = await bundleToSingleHtml2(projectDir);
23461
- } catch {
23462
- const file = join20(projectDir, "index.html");
23463
- if (!existsSync22(file)) return c2.text("not found", 404);
23464
- bundled = readFileSync10(file, "utf-8");
23465
- }
23466
- const baseTag = `<base href="/api/projects/${projectId}/preview/">`;
23467
- if (bundled.includes("<head>")) {
23468
- bundled = bundled.replace("<head>", `<head>${baseTag}`);
23469
- } else {
23470
- bundled = baseTag + bundled;
23471
- }
23472
- bundled = bundled.replace(
23473
- 'data-hyperframes-preview-runtime="1" src=""',
23474
- 'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
23475
- );
23476
- return c2.html(bundled);
23477
- });
23478
- app.get("/api/projects/:id/preview/comp/*", (c2) => {
23479
- const id = c2.req.param("id");
23480
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23481
- const compPath = c2.req.path.replace(`/api/projects/${id}/preview/comp/`, "");
23482
- const html = buildSubCompositionHtml(
23483
- projectDir,
23484
- decodeURIComponent(compPath),
23485
- "/api/runtime.js"
23486
- );
23487
- if (!html) return c2.text("not found", 404);
23488
- return c2.html(html);
23489
- });
23490
- app.get("/api/projects/:id/preview/*", (c2) => {
23491
- const id = c2.req.param("id");
23492
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23493
- const subPath = decodeURIComponent(
23494
- c2.req.path.replace(`/api/projects/${id}/preview/`, "").split("?")[0] ?? ""
23495
- );
23496
- const file = resolve10(projectDir, subPath);
23497
- if (!isSafePath(projectDir, file) || !existsSync22(file) || !statSync7(file).isFile()) {
23498
- return c2.text("not found", 404);
23499
- }
23500
- const mime = getMimeType(file);
23501
- const content = readFileSync10(file);
23502
- return new Response(content, {
23503
- headers: { "Content-Type": mime, "Cache-Control": "no-store" }
23504
- });
23505
- });
23506
- app.get("/api/projects/:id/files/*", (c2) => {
23507
- const id = c2.req.param("id");
23508
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23509
- const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
23510
- const file = resolve10(projectDir, filePath);
23511
- if (!isSafePath(projectDir, file) || !existsSync22(file)) {
23512
- return c2.text("not found", 404);
23513
- }
23514
- const content = readFileSync10(file, "utf-8");
23515
- return c2.json({ filename: filePath, content });
23516
- });
23517
- app.put("/api/projects/:id/files/*", async (c2) => {
23518
- const id = c2.req.param("id");
23519
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23520
- const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
23521
- const file = resolve10(projectDir, filePath);
23522
- if (!isSafePath(projectDir, file)) {
23523
- return c2.json({ error: "forbidden" }, 403);
23524
- }
23525
- const dir = dirname10(file);
23526
- if (!existsSync22(dir)) mkdirSync14(dir, { recursive: true });
23527
- const body = await c2.req.text();
23528
- writeFileSync6(file, body, "utf-8");
23529
- return c2.json({ ok: true });
23530
- });
23531
- app.get("/api/resolve-session/:id", (c2) => c2.json({ error: "not available" }, 404));
23532
- const renderJobs = /* @__PURE__ */ new Map();
23533
- app.post("/api/projects/:id/render", async (c2) => {
23534
- const id = c2.req.param("id");
23535
- if (id !== projectId) return c2.json({ error: "not found" }, 404);
23536
- const jobId = Math.random().toString(36).slice(2, 10);
23537
- const outputDir = join20(projectDir, "renders");
23538
- if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
23539
- const outputPath = join20(outputDir, `${projectId}.mp4`);
23540
- renderJobs.set(jobId, { status: "rendering", progress: 0, outputPath });
23541
- (async () => {
23542
- try {
23543
- const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
23544
- const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
23545
- try {
23546
- const browser = await ensureBrowser2();
23547
- if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
23548
- process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
23549
- }
23550
- } catch {
23551
- }
23552
- const { trackRenderComplete: trackRenderComplete2 } = await Promise.resolve().then(() => (init_events(), events_exports));
23553
- const { bytesToMb: bytesToMb2 } = await Promise.resolve().then(() => (init_system(), system_exports));
23554
- const { freemem: freemem5 } = await import("os");
23555
- const job = createRenderJob2({ fps: 30, quality: "standard" });
23556
- const startTime = Date.now();
23557
- const onProgress = (j2) => {
23558
- const entry2 = renderJobs.get(jobId);
23559
- if (entry2) entry2.progress = j2.progress;
23560
- };
23561
- await executeRenderJob2(job, projectDir, outputPath, onProgress);
23562
- const entry = renderJobs.get(jobId);
23563
- if (entry) {
23564
- entry.status = "complete";
23565
- entry.progress = 100;
23566
- }
23567
- const elapsed = Date.now() - startTime;
23568
- const perf = job.perfSummary;
23569
- const compositionDurationMs = perf ? Math.round(perf.compositionDurationSeconds * 1e3) : void 0;
23570
- trackRenderComplete2({
23571
- durationMs: elapsed,
23572
- fps: 30,
23573
- quality: "standard",
23574
- workers: perf?.workers ?? 1,
23575
- docker: false,
23576
- gpu: false,
23577
- compositionDurationMs,
23578
- compositionWidth: perf?.resolution.width,
23579
- compositionHeight: perf?.resolution.height,
23580
- totalFrames: perf?.totalFrames,
23581
- speedRatio: compositionDurationMs && compositionDurationMs > 0 && elapsed > 0 ? Math.round(compositionDurationMs / elapsed * 100) / 100 : void 0,
23582
- captureAvgMs: perf?.captureAvgMs,
23583
- capturePeakMs: perf?.capturePeakMs,
23584
- peakMemoryMb: bytesToMb2(process.memoryUsage.rss()),
23585
- memoryFreeMb: bytesToMb2(freemem5())
23586
- });
23587
- } catch (err) {
23588
- try {
23589
- const { trackRenderError: trackRenderError2 } = await Promise.resolve().then(() => (init_events(), events_exports));
23590
- const { bytesToMb: bytesToMb2 } = await Promise.resolve().then(() => (init_system(), system_exports));
23591
- const { freemem: freemem5 } = await import("os");
23592
- trackRenderError2({
23593
- fps: 30,
23594
- quality: "standard",
23595
- docker: false,
23596
- errorMessage: err instanceof Error ? err.message : String(err),
23597
- peakMemoryMb: bytesToMb2(process.memoryUsage.rss()),
23598
- memoryFreeMb: bytesToMb2(freemem5())
23599
- });
23600
- } catch {
23601
- }
23602
- const entry = renderJobs.get(jobId);
23603
- if (entry) {
23604
- entry.status = "failed";
23605
- entry.error = err instanceof Error ? err.message : String(err);
23606
- }
23607
- }
23608
- })();
23609
- return c2.json({ jobId });
23610
- });
23611
- app.get("/api/render/:jobId/progress", (c2) => {
23612
- const { jobId } = c2.req.param();
23613
- const job = renderJobs.get(jobId);
23614
- if (!job) return c2.json({ error: "not found" }, 404);
23615
- return streamSSE2(c2, async (stream) => {
23616
- while (true) {
23617
- const current = renderJobs.get(jobId);
23618
- if (!current) break;
23619
- await stream.writeSSE({
23620
- event: "progress",
23621
- data: JSON.stringify({
23622
- progress: current.progress,
23623
- status: current.status,
23624
- error: current.error
23625
- })
23626
- });
23627
- if (current.status === "complete" || current.status === "failed") break;
23628
- await stream.sleep(500);
23629
- }
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"
23630
24066
  });
24067
+ return api.fetch(forwardReq);
23631
24068
  });
23632
- app.get("/api/render/:jobId/download", (c2) => {
23633
- const { jobId } = c2.req.param();
23634
- const job = renderJobs.get(jobId);
23635
- if (!job?.outputPath || !existsSync22(job.outputPath)) {
23636
- return c2.json({ error: "not found" }, 404);
23637
- }
23638
- const content = readFileSync10(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);
23639
24073
  return new Response(content, {
23640
- headers: {
23641
- "Content-Type": "video/mp4",
23642
- "Content-Disposition": `attachment; filename="${projectId}.mp4"`
23643
- }
24074
+ headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
23644
24075
  });
23645
24076
  });
23646
- app.get("/assets/*", (c2) => {
23647
- const filePath = resolve10(studioDir, c2.req.path.slice(1));
23648
- const resp = serveStaticFile(filePath);
23649
- return resp ?? c2.text("not found", 404);
23650
- });
23651
24077
  app.get("/icons/*", (c2) => {
23652
- const filePath = resolve10(studioDir, c2.req.path.slice(1));
23653
- const resp = serveStaticFile(filePath);
23654
- 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
+ });
23655
24084
  });
23656
24085
  app.get("*", (c2) => {
23657
- const indexPath = resolve10(studioDir, "index.html");
23658
- if (!existsSync22(indexPath)) {
24086
+ const indexPath = resolve13(studioDir, "index.html");
24087
+ if (!existsSync27(indexPath)) {
23659
24088
  return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
23660
24089
  }
23661
- return c2.html(readFileSync10(indexPath, "utf-8"));
24090
+ return c2.html(readFileSync16(indexPath, "utf-8"));
23662
24091
  });
23663
24092
  return { app, watcher };
23664
24093
  }
23665
- var MIME_TYPES2;
23666
24094
  var init_studioServer = __esm({
23667
24095
  "src/server/studioServer.ts"() {
23668
24096
  "use strict";
23669
24097
  init_fileWatcher();
23670
- MIME_TYPES2 = {
23671
- ".html": "text/html",
23672
- ".js": "text/javascript",
23673
- ".css": "text/css",
23674
- ".json": "application/json",
23675
- ".svg": "image/svg+xml",
23676
- ".png": "image/png",
23677
- ".jpg": "image/jpeg",
23678
- ".jpeg": "image/jpeg",
23679
- ".webp": "image/webp",
23680
- ".gif": "image/gif",
23681
- ".mp4": "video/mp4",
23682
- ".webm": "video/webm",
23683
- ".mp3": "audio/mpeg",
23684
- ".wav": "audio/wav",
23685
- ".m4a": "audio/mp4",
23686
- ".ogg": "audio/ogg",
23687
- ".woff2": "font/woff2",
23688
- ".woff": "font/woff",
23689
- ".ttf": "font/ttf"
23690
- };
24098
+ init_studio_api();
23691
24099
  }
23692
24100
  });
23693
24101
 
@@ -23697,8 +24105,8 @@ __export(dev_exports, {
23697
24105
  default: () => dev_default
23698
24106
  });
23699
24107
  import { spawn as spawn7 } from "child_process";
23700
- import { existsSync as existsSync23, lstatSync, symlinkSync, unlinkSync, readlinkSync, mkdirSync as mkdirSync15 } from "fs";
23701
- 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";
23702
24110
  import { fileURLToPath as fileURLToPath4 } from "url";
23703
24111
  import { createRequire } from "module";
23704
24112
  async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
@@ -23734,28 +24142,28 @@ async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
23734
24142
  `Ports ${startPort}\u2013${lastPort} are all in use. Use --port to specify a different port.`
23735
24143
  );
23736
24144
  }
23737
- async function runDevMode(dir) {
24145
+ async function runDevMode(dir, projectName) {
23738
24146
  const thisFile = fileURLToPath4(import.meta.url);
23739
- const repoRoot = resolve11(dirname11(thisFile), "..", "..", "..", "..");
23740
- const projectsDir = join21(repoRoot, "packages", "studio", "data", "projects");
23741
- const projectName = basename3(dir);
23742
- const symlinkPath = join21(projectsDir, projectName);
23743
- mkdirSync15(projectsDir, { recursive: true });
24147
+ const repoRoot = resolve14(dirname11(thisFile), "..", "..", "..", "..");
24148
+ const projectsDir = join26(repoRoot, "packages", "studio", "data", "projects");
24149
+ const pName = projectName ?? basename3(dir);
24150
+ const symlinkPath = join26(projectsDir, pName);
24151
+ mkdirSync17(projectsDir, { recursive: true });
23744
24152
  let createdSymlink = false;
23745
24153
  if (dir !== symlinkPath) {
23746
- if (existsSync23(symlinkPath)) {
24154
+ if (existsSync28(symlinkPath)) {
23747
24155
  try {
23748
24156
  const stat = lstatSync(symlinkPath);
23749
24157
  if (stat.isSymbolicLink()) {
23750
24158
  const target = readlinkSync(symlinkPath);
23751
- if (resolve11(target) !== resolve11(dir)) {
23752
- unlinkSync(symlinkPath);
24159
+ if (resolve14(target) !== resolve14(dir)) {
24160
+ unlinkSync2(symlinkPath);
23753
24161
  }
23754
24162
  }
23755
24163
  } catch {
23756
24164
  }
23757
24165
  }
23758
- if (!existsSync23(symlinkPath)) {
24166
+ if (!existsSync28(symlinkPath)) {
23759
24167
  symlinkSync(dir, symlinkPath, "dir");
23760
24168
  createdSymlink = true;
23761
24169
  }
@@ -23763,7 +24171,7 @@ async function runDevMode(dir) {
23763
24171
  Wt2(c.bold("hyperframes dev"));
23764
24172
  const s = be();
23765
24173
  s.start("Starting studio...");
23766
- const studioPkgDir = join21(repoRoot, "packages", "studio");
24174
+ const studioPkgDir = join26(repoRoot, "packages", "studio");
23767
24175
  const child = spawn7("pnpm", ["exec", "vite"], {
23768
24176
  cwd: studioPkgDir,
23769
24177
  stdio: ["ignore", "pipe", "pipe"]
@@ -23776,12 +24184,12 @@ async function runDevMode(dir) {
23776
24184
  frontendUrl = localMatch[1] ?? "";
23777
24185
  s.stop(c.success("Studio running"));
23778
24186
  console.log();
23779
- console.log(` ${c.dim("Project")} ${c.accent(projectName)}`);
24187
+ console.log(` ${c.dim("Project")} ${c.accent(pName)}`);
23780
24188
  console.log(` ${c.dim("Studio")} ${c.accent(frontendUrl)}`);
23781
24189
  console.log();
23782
24190
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
23783
24191
  console.log();
23784
- const urlToOpen = `${frontendUrl}#/project/${projectName}`;
24192
+ const urlToOpen = `${frontendUrl}#/project/${pName}`;
23785
24193
  import("open").then((mod) => mod.default(urlToOpen)).catch(() => {
23786
24194
  });
23787
24195
  child.stdout?.removeListener("data", handleOutput);
@@ -23797,39 +24205,39 @@ async function runDevMode(dir) {
23797
24205
  if (createdSymlink) {
23798
24206
  process.on("exit", () => {
23799
24207
  try {
23800
- if (existsSync23(symlinkPath)) unlinkSync(symlinkPath);
24208
+ if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
23801
24209
  } catch {
23802
24210
  }
23803
24211
  });
23804
24212
  }
23805
- return new Promise((resolve17) => {
23806
- child.on("close", () => resolve17());
24213
+ return new Promise((resolve20) => {
24214
+ child.on("close", () => resolve20());
23807
24215
  });
23808
24216
  }
23809
24217
  function hasLocalStudio(dir) {
23810
24218
  try {
23811
- const req = createRequire(join21(dir, "package.json"));
24219
+ const req = createRequire(join26(dir, "package.json"));
23812
24220
  req.resolve("@hyperframes/studio/package.json");
23813
24221
  return true;
23814
24222
  } catch {
23815
24223
  return false;
23816
24224
  }
23817
24225
  }
23818
- async function runLocalStudioMode(dir) {
23819
- const req = createRequire(join21(dir, "package.json"));
24226
+ async function runLocalStudioMode(dir, projectName) {
24227
+ const req = createRequire(join26(dir, "package.json"));
23820
24228
  const studioPkgPath = dirname11(req.resolve("@hyperframes/studio/package.json"));
23821
- const projectName = basename3(dir);
23822
- const projectsDir = join21(studioPkgPath, "data", "projects");
23823
- const symlinkPath = join21(projectsDir, projectName);
23824
- mkdirSync15(projectsDir, { recursive: true });
24229
+ const pName = projectName ?? basename3(dir);
24230
+ const projectsDir = join26(studioPkgPath, "data", "projects");
24231
+ const symlinkPath = join26(projectsDir, pName);
24232
+ mkdirSync17(projectsDir, { recursive: true });
23825
24233
  let createdSymlink = false;
23826
24234
  if (dir !== symlinkPath) {
23827
- if (existsSync23(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
23828
- if (resolve11(readlinkSync(symlinkPath)) !== resolve11(dir)) {
23829
- unlinkSync(symlinkPath);
24235
+ if (existsSync28(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
24236
+ if (resolve14(readlinkSync(symlinkPath)) !== resolve14(dir)) {
24237
+ unlinkSync2(symlinkPath);
23830
24238
  }
23831
24239
  }
23832
- if (!existsSync23(symlinkPath)) {
24240
+ if (!existsSync28(symlinkPath)) {
23833
24241
  symlinkSync(dir, symlinkPath, "dir");
23834
24242
  createdSymlink = true;
23835
24243
  }
@@ -23850,12 +24258,12 @@ async function runLocalStudioMode(dir) {
23850
24258
  const url = localMatch[1] ?? "";
23851
24259
  s.stop(c.success("Studio running"));
23852
24260
  console.log();
23853
- console.log(` ${c.dim("Project")} ${c.accent(projectName)}`);
24261
+ console.log(` ${c.dim("Project")} ${c.accent(pName)}`);
23854
24262
  console.log(` ${c.dim("Studio")} ${c.accent(url)}`);
23855
24263
  console.log();
23856
24264
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
23857
24265
  console.log();
23858
- import("open").then((mod) => mod.default(`${url}#project/${projectName}`)).catch(() => {
24266
+ import("open").then((mod) => mod.default(`${url}#project/${pName}`)).catch(() => {
23859
24267
  });
23860
24268
  }
23861
24269
  }
@@ -23868,19 +24276,19 @@ async function runLocalStudioMode(dir) {
23868
24276
  if (createdSymlink) {
23869
24277
  process.on("exit", () => {
23870
24278
  try {
23871
- if (existsSync23(symlinkPath)) unlinkSync(symlinkPath);
24279
+ if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
23872
24280
  } catch {
23873
24281
  }
23874
24282
  });
23875
24283
  }
23876
- return new Promise((resolve17) => {
23877
- child.on("close", () => resolve17());
24284
+ return new Promise((resolve20) => {
24285
+ child.on("close", () => resolve20());
23878
24286
  });
23879
24287
  }
23880
- async function runEmbeddedMode(dir, startPort) {
24288
+ async function runEmbeddedMode(dir, startPort, projectName) {
23881
24289
  const { createStudioServer: createStudioServer2 } = await Promise.resolve().then(() => (init_studioServer(), studioServer_exports));
23882
- const projectName = basename3(dir);
23883
- const { app } = createStudioServer2({ projectDir: dir });
24290
+ const pName = projectName ?? basename3(dir);
24291
+ const { app } = createStudioServer2({ projectDir: dir, projectName: pName });
23884
24292
  Wt2(c.bold("hyperframes dev"));
23885
24293
  const s = be();
23886
24294
  s.start("Starting studio...");
@@ -23902,7 +24310,7 @@ async function runEmbeddedMode(dir, startPort) {
23902
24310
  console.log(` ${c.warn(`Port ${startPort} is in use, using ${actualPort} instead`)}`);
23903
24311
  console.log();
23904
24312
  }
23905
- console.log(` ${c.dim("Project")} ${c.accent(projectName)}`);
24313
+ console.log(` ${c.dim("Project")} ${c.accent(pName)}`);
23906
24314
  console.log(` ${c.dim("Studio")} ${c.accent(url)}`);
23907
24315
  console.log();
23908
24316
  console.log(` ${c.dim("Edit with your AI agent \u2014 it has HyperFrames skills installed.")}`);
@@ -23910,7 +24318,7 @@ async function runEmbeddedMode(dir, startPort) {
23910
24318
  console.log();
23911
24319
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
23912
24320
  console.log();
23913
- import("open").then((mod) => mod.default(`${url}#project/${projectName}`)).catch(() => {
24321
+ import("open").then((mod) => mod.default(`${url}#project/${pName}`)).catch(() => {
23914
24322
  });
23915
24323
  return new Promise(() => {
23916
24324
  });
@@ -23930,15 +24338,18 @@ var init_dev = __esm({
23930
24338
  port: { type: "string", description: "Port to run the dev server on", default: "3002" }
23931
24339
  },
23932
24340
  async run({ args }) {
23933
- const dir = resolve11(args.dir ?? ".");
24341
+ const rawArg = args.dir;
24342
+ const dir = resolve14(rawArg ?? ".");
23934
24343
  const startPort = parseInt(args.port ?? "3002", 10);
24344
+ const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
24345
+ const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
23935
24346
  if (isDevMode()) {
23936
- return runDevMode(dir);
24347
+ return runDevMode(dir, projectName);
23937
24348
  }
23938
24349
  if (hasLocalStudio(dir)) {
23939
- return runLocalStudioMode(dir);
24350
+ return runLocalStudioMode(dir, projectName);
23940
24351
  }
23941
- return runEmbeddedMode(dir, startPort);
24352
+ return runEmbeddedMode(dir, startPort, projectName);
23942
24353
  }
23943
24354
  });
23944
24355
  }
@@ -23977,17 +24388,17 @@ var init_format = __esm({
23977
24388
  });
23978
24389
 
23979
24390
  // src/utils/project.ts
23980
- import { existsSync as existsSync24, statSync as statSync8 } from "fs";
23981
- import { resolve as resolve12, basename as basename4 } from "path";
24391
+ import { existsSync as existsSync29, statSync as statSync10 } from "fs";
24392
+ import { resolve as resolve15, basename as basename4 } from "path";
23982
24393
  function resolveProject(dirArg) {
23983
- const dir = resolve12(dirArg ?? ".");
24394
+ const dir = resolve15(dirArg ?? ".");
23984
24395
  const name = basename4(dir);
23985
- const indexPath = resolve12(dir, "index.html");
23986
- if (!existsSync24(dir) || !statSync8(dir).isDirectory()) {
24396
+ const indexPath = resolve15(dir, "index.html");
24397
+ if (!existsSync29(dir) || !statSync10(dir).isDirectory()) {
23987
24398
  errorBox("Not a directory: " + dir);
23988
24399
  process.exit(1);
23989
24400
  }
23990
- if (!existsSync24(indexPath)) {
24401
+ if (!existsSync29(indexPath)) {
23991
24402
  errorBox(
23992
24403
  "No composition found in " + dir,
23993
24404
  "No index.html file found.",
@@ -24076,9 +24487,9 @@ var render_exports = {};
24076
24487
  __export(render_exports, {
24077
24488
  default: () => render_default
24078
24489
  });
24079
- import { existsSync as existsSync25, mkdirSync as mkdirSync16, statSync as statSync9 } from "fs";
24490
+ import { existsSync as existsSync30, mkdirSync as mkdirSync18, statSync as statSync11 } from "fs";
24080
24491
  import { cpus as cpus3, freemem as freemem3 } from "os";
24081
- import { resolve as resolve13, dirname as dirname12, join as join22 } from "path";
24492
+ import { resolve as resolve16, dirname as dirname12, join as join27 } from "path";
24082
24493
  function defaultWorkerCount() {
24083
24494
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
24084
24495
  }
@@ -24172,8 +24583,8 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
24172
24583
  function printRenderComplete(outputPath, elapsedMs, quiet) {
24173
24584
  if (quiet) return;
24174
24585
  let fileSize = "unknown";
24175
- if (existsSync25(outputPath)) {
24176
- const stat = statSync9(outputPath);
24586
+ if (existsSync30(outputPath)) {
24587
+ const stat = statSync11(outputPath);
24177
24588
  fileSize = formatBytes(stat.size);
24178
24589
  }
24179
24590
  const duration = formatDuration(elapsedMs);
@@ -24182,7 +24593,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
24182
24593
  console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
24183
24594
  }
24184
24595
  var VALID_FPS, VALID_QUALITY, VALID_FORMAT, CPU_CORE_COUNT, render_default;
24185
- var init_render = __esm({
24596
+ var init_render2 = __esm({
24186
24597
  "src/commands/render.ts"() {
24187
24598
  "use strict";
24188
24599
  init_dist();
@@ -24278,10 +24689,13 @@ Examples:
24278
24689
  }
24279
24690
  workers = parsed;
24280
24691
  }
24281
- const rendersDir = resolve13("renders");
24692
+ const rendersDir = resolve16("renders");
24282
24693
  const ext = format === "webm" ? ".webm" : ".mp4";
24283
- const outputPath = args.output ? resolve13(args.output) : join22(rendersDir, `${project.name}${ext}`);
24284
- mkdirSync16(dirname12(outputPath), { recursive: true });
24694
+ const now = /* @__PURE__ */ new Date();
24695
+ const datePart = now.toISOString().slice(0, 10);
24696
+ const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
24697
+ const outputPath = args.output ? resolve16(args.output) : join27(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
24698
+ mkdirSync18(dirname12(outputPath), { recursive: true });
24285
24699
  const useDocker = args.docker ?? false;
24286
24700
  const useGpu = args.gpu ?? false;
24287
24701
  const quiet = args.quiet ?? false;
@@ -24367,17 +24781,17 @@ __export(transcribe_exports, {
24367
24781
  transcribe: () => transcribe
24368
24782
  });
24369
24783
  import { execFileSync as execFileSync3 } from "child_process";
24370
- import { existsSync as existsSync26, readFileSync as readFileSync11, mkdirSync as mkdirSync17, unlinkSync as unlinkSync2 } from "fs";
24371
- import { join as join23, extname as extname5 } from "path";
24784
+ import { existsSync as existsSync31, readFileSync as readFileSync17, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
24785
+ import { join as join28, extname as extname4 } from "path";
24372
24786
  import { tmpdir as tmpdir2 } from "os";
24373
24787
  function isAudioFile(filePath) {
24374
- return AUDIO_EXTENSIONS.has(extname5(filePath).toLowerCase());
24788
+ return AUDIO_EXTENSIONS.has(extname4(filePath).toLowerCase());
24375
24789
  }
24376
24790
  function isVideoFile(filePath) {
24377
- return VIDEO_EXTENSIONS.has(extname5(filePath).toLowerCase());
24791
+ return VIDEO_EXTENSIONS.has(extname4(filePath).toLowerCase());
24378
24792
  }
24379
24793
  function extractAudio(videoPath) {
24380
- const wavPath = join23(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24794
+ const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24381
24795
  execFileSync3(
24382
24796
  "ffmpeg",
24383
24797
  ["-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -24400,10 +24814,10 @@ function isWav16kMono(filePath) {
24400
24814
  }
24401
24815
  }
24402
24816
  function prepareAudio(audioPath) {
24403
- if (extname5(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
24817
+ if (extname4(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
24404
24818
  return audioPath;
24405
24819
  }
24406
- const wavPath = join23(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24820
+ const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
24407
24821
  execFileSync3(
24408
24822
  "ffmpeg",
24409
24823
  ["-i", audioPath, "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
@@ -24420,7 +24834,7 @@ async function transcribe(inputPath, outputDir, options) {
24420
24834
  onProgress: options?.onProgress
24421
24835
  });
24422
24836
  let wavPath;
24423
- const ext = extname5(inputPath).toLowerCase();
24837
+ const ext = extname4(inputPath).toLowerCase();
24424
24838
  if (isAudioFile(inputPath)) {
24425
24839
  options?.onProgress?.("Preparing audio...");
24426
24840
  wavPath = prepareAudio(inputPath);
@@ -24436,8 +24850,8 @@ async function transcribe(inputPath, outputDir, options) {
24436
24850
  throw new Error(`Unsupported file type: ${ext}`);
24437
24851
  }
24438
24852
  options?.onProgress?.("Transcribing...");
24439
- const outputBase = join23(outputDir, "transcript");
24440
- mkdirSync17(outputDir, { recursive: true });
24853
+ const outputBase = join28(outputDir, "transcript");
24854
+ mkdirSync19(outputDir, { recursive: true });
24441
24855
  execFileSync3(
24442
24856
  whisper.executablePath,
24443
24857
  [
@@ -24454,10 +24868,10 @@ async function transcribe(inputPath, outputDir, options) {
24454
24868
  { stdio: "ignore", timeout: 3e5 }
24455
24869
  );
24456
24870
  const transcriptPath = `${outputBase}.json`;
24457
- if (!existsSync26(transcriptPath)) {
24871
+ if (!existsSync31(transcriptPath)) {
24458
24872
  throw new Error("Whisper did not produce output. Check the input file.");
24459
24873
  }
24460
- const transcript = JSON.parse(readFileSync11(transcriptPath, "utf-8"));
24874
+ const transcript = JSON.parse(readFileSync17(transcriptPath, "utf-8"));
24461
24875
  const segments = transcript.transcription ?? [];
24462
24876
  let wordCount = 0;
24463
24877
  let maxEnd = 0;
@@ -24470,7 +24884,7 @@ async function transcribe(inputPath, outputDir, options) {
24470
24884
  }
24471
24885
  if (wavPath !== inputPath) {
24472
24886
  try {
24473
- unlinkSync2(wavPath);
24887
+ unlinkSync3(wavPath);
24474
24888
  } catch {
24475
24889
  }
24476
24890
  }
@@ -24496,15 +24910,15 @@ __export(init_exports, {
24496
24910
  default: () => init_default
24497
24911
  });
24498
24912
  import {
24499
- existsSync as existsSync27,
24500
- mkdirSync as mkdirSync18,
24913
+ existsSync as existsSync32,
24914
+ mkdirSync as mkdirSync20,
24501
24915
  copyFileSync as copyFileSync3,
24502
24916
  cpSync as cpSync2,
24503
- writeFileSync as writeFileSync7,
24504
- readFileSync as readFileSync12,
24505
- readdirSync as readdirSync7
24917
+ writeFileSync as writeFileSync9,
24918
+ readFileSync as readFileSync18,
24919
+ readdirSync as readdirSync8
24506
24920
  } from "fs";
24507
- import { resolve as resolve14, basename as basename5, join as join24, dirname as dirname13 } from "path";
24921
+ import { resolve as resolve17, basename as basename5, join as join29, dirname as dirname13 } from "path";
24508
24922
  import { fileURLToPath as fileURLToPath5 } from "url";
24509
24923
  import { execFileSync as execFileSync4, spawn as spawn8 } from "child_process";
24510
24924
  async function installSkills(interactive) {
@@ -24625,9 +25039,9 @@ function transcodeToMp4(inputPath, outputPath) {
24625
25039
  }
24626
25040
  function resolveAssetDir(devSegments, builtSegments) {
24627
25041
  const base = dirname13(fileURLToPath5(import.meta.url));
24628
- const devPath = resolve14(base, ...devSegments);
24629
- const builtPath = resolve14(base, ...builtSegments);
24630
- return existsSync27(devPath) ? devPath : builtPath;
25042
+ const devPath = resolve17(base, ...devSegments);
25043
+ const builtPath = resolve17(base, ...builtSegments);
25044
+ return existsSync32(devPath) ? devPath : builtPath;
24631
25045
  }
24632
25046
  function getStaticTemplateDir(templateId) {
24633
25047
  return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
@@ -24639,9 +25053,9 @@ function getBundledSkillsDir() {
24639
25053
  return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
24640
25054
  }
24641
25055
  function patchVideoSrc(dir, videoFilename, durationSeconds) {
24642
- 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));
25056
+ 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));
24643
25057
  for (const file of htmlFiles) {
24644
- let content = readFileSync12(file, "utf-8");
25058
+ let content = readFileSync18(file, "utf-8");
24645
25059
  if (videoFilename) {
24646
25060
  content = content.replaceAll("__VIDEO_SRC__", videoFilename);
24647
25061
  } else {
@@ -24652,11 +25066,11 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
24652
25066
  }
24653
25067
  const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
24654
25068
  content = content.replaceAll("__VIDEO_DURATION__", dur);
24655
- writeFileSync7(file, content, "utf-8");
25069
+ writeFileSync9(file, content, "utf-8");
24656
25070
  }
24657
25071
  }
24658
25072
  function patchTranscript(dir, transcriptPath) {
24659
- const raw = JSON.parse(readFileSync12(transcriptPath, "utf-8"));
25073
+ const raw = JSON.parse(readFileSync18(transcriptPath, "utf-8"));
24660
25074
  const words = [];
24661
25075
  for (const seg of raw.transcription ?? []) {
24662
25076
  for (const token of seg.tokens ?? []) {
@@ -24678,9 +25092,9 @@ function patchTranscript(dir, transcriptPath) {
24678
25092
  }
24679
25093
  if (words.length === 0) return;
24680
25094
  const wordsJson = JSON.stringify(words, null, 10).replace(/^\[/, "[").replace(/\n {10}/g, "\n ");
24681
- 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));
25095
+ 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));
24682
25096
  for (const file of htmlFiles) {
24683
- let content = readFileSync12(file, "utf-8");
25097
+ let content = readFileSync18(file, "utf-8");
24684
25098
  const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
24685
25099
  let scriptMatch = null;
24686
25100
  let transcriptMatch = null;
@@ -24692,7 +25106,7 @@ function patchTranscript(dir, transcriptPath) {
24692
25106
  if (match) {
24693
25107
  const varName = scriptMatch ? "script" : "TRANSCRIPT";
24694
25108
  content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
24695
- writeFileSync7(file, content, "utf-8");
25109
+ writeFileSync9(file, content, "utf-8");
24696
25110
  }
24697
25111
  }
24698
25112
  }
@@ -24749,7 +25163,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24749
25163
  }
24750
25164
  if (shouldTranscode) {
24751
25165
  const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
24752
- const mp4Path = resolve14(destDir, mp4Name);
25166
+ const mp4Path = resolve17(destDir, mp4Name);
24753
25167
  const spin = be();
24754
25168
  spin.start("Transcoding to H.264 MP4...");
24755
25169
  const ok = await transcodeToMp4(videoPath, mp4Path);
@@ -24758,10 +25172,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24758
25172
  localVideoName = mp4Name;
24759
25173
  } else {
24760
25174
  spin.stop(c.warn("Transcode failed \u2014 copying original file"));
24761
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25175
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24762
25176
  }
24763
25177
  } else {
24764
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25178
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24765
25179
  }
24766
25180
  } else {
24767
25181
  if (interactive) {
@@ -24771,20 +25185,20 @@ async function handleVideoFile(videoPath, destDir, interactive) {
24771
25185
  console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
24772
25186
  console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
24773
25187
  }
24774
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25188
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24775
25189
  }
24776
25190
  } else {
24777
- copyFileSync3(videoPath, resolve14(destDir, localVideoName));
25191
+ copyFileSync3(videoPath, resolve17(destDir, localVideoName));
24778
25192
  }
24779
25193
  return { meta, localVideoName };
24780
25194
  }
24781
25195
  function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
24782
- mkdirSync18(destDir, { recursive: true });
25196
+ mkdirSync20(destDir, { recursive: true });
24783
25197
  const templateDir = getStaticTemplateDir(templateId);
24784
25198
  cpSync2(templateDir, destDir, { recursive: true });
24785
25199
  patchVideoSrc(destDir, localVideoName, durationSeconds);
24786
- writeFileSync7(
24787
- resolve14(destDir, "meta.json"),
25200
+ writeFileSync9(
25201
+ resolve17(destDir, "meta.json"),
24788
25202
  JSON.stringify(
24789
25203
  {
24790
25204
  id: name,
@@ -24797,23 +25211,23 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
24797
25211
  "utf-8"
24798
25212
  );
24799
25213
  const sharedDir = getSharedTemplateDir();
24800
- if (existsSync27(sharedDir)) {
24801
- for (const entry of readdirSync7(sharedDir, { withFileTypes: true })) {
24802
- const src = join24(sharedDir, entry.name);
24803
- const dest = resolve14(destDir, entry.name);
25214
+ if (existsSync32(sharedDir)) {
25215
+ for (const entry of readdirSync8(sharedDir, { withFileTypes: true })) {
25216
+ const src = join29(sharedDir, entry.name);
25217
+ const dest = resolve17(destDir, entry.name);
24804
25218
  if (entry.isFile() || entry.isSymbolicLink()) {
24805
25219
  copyFileSync3(src, dest);
24806
25220
  }
24807
25221
  }
24808
25222
  }
24809
25223
  const skillsSrcDir = getBundledSkillsDir();
24810
- if (existsSync27(skillsSrcDir)) {
25224
+ if (existsSync32(skillsSrcDir)) {
24811
25225
  const projectSkills = ["hyperframes-compose", "hyperframes-captions"];
24812
25226
  for (const skill of projectSkills) {
24813
- const src = join24(skillsSrcDir, skill);
24814
- if (existsSync27(src)) {
24815
- const dest = resolve14(destDir, ".claude", "skills", skill);
24816
- mkdirSync18(dest, { recursive: true });
25227
+ const src = join29(skillsSrcDir, skill);
25228
+ if (existsSync32(src)) {
25229
+ const dest = resolve17(destDir, ".claude", "skills", skill);
25230
+ mkdirSync20(dest, { recursive: true });
24817
25231
  cpSync2(src, dest, { recursive: true });
24818
25232
  }
24819
25233
  }
@@ -24842,7 +25256,7 @@ async function nextStepLoop(destDir) {
24842
25256
  const devCmd = await Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default);
24843
25257
  await runCommand(devCmd, { rawArgs: [destDir] });
24844
25258
  } else if (next === "render") {
24845
- const renderCmd = await Promise.resolve().then(() => (init_render(), render_exports)).then((m) => m.default);
25259
+ const renderCmd = await Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default);
24846
25260
  await runCommand(renderCmd, { rawArgs: [destDir] });
24847
25261
  }
24848
25262
  } catch {
@@ -24929,18 +25343,18 @@ Examples:
24929
25343
  }
24930
25344
  const templateId2 = resolvedTemplate;
24931
25345
  const name2 = args.name ?? "my-video";
24932
- const destDir2 = resolve14(name2);
24933
- if (existsSync27(destDir2) && readdirSync7(destDir2).length > 0) {
25346
+ const destDir2 = resolve17(name2);
25347
+ if (existsSync32(destDir2) && readdirSync8(destDir2).length > 0) {
24934
25348
  console.error(c.error(`Directory already exists and is not empty: ${name2}`));
24935
25349
  process.exit(1);
24936
25350
  }
24937
- mkdirSync18(destDir2, { recursive: true });
25351
+ mkdirSync20(destDir2, { recursive: true });
24938
25352
  let localVideoName2;
24939
25353
  let videoDuration2;
24940
25354
  let sourceFilePath2;
24941
25355
  if (videoFlag) {
24942
- const videoPath = resolve14(videoFlag);
24943
- if (!existsSync27(videoPath)) {
25356
+ const videoPath = resolve17(videoFlag);
25357
+ if (!existsSync32(videoPath)) {
24944
25358
  console.error(c.error(`Video file not found: ${videoFlag}`));
24945
25359
  process.exit(1);
24946
25360
  }
@@ -24953,13 +25367,13 @@ Examples:
24953
25367
  );
24954
25368
  }
24955
25369
  if (audioFlag) {
24956
- const audioPath = resolve14(audioFlag);
24957
- if (!existsSync27(audioPath)) {
25370
+ const audioPath = resolve17(audioFlag);
25371
+ if (!existsSync32(audioPath)) {
24958
25372
  console.error(c.error(`Audio file not found: ${audioFlag}`));
24959
25373
  process.exit(1);
24960
25374
  }
24961
25375
  sourceFilePath2 = audioPath;
24962
- copyFileSync3(audioPath, resolve14(destDir2, basename5(audioPath)));
25376
+ copyFileSync3(audioPath, resolve17(destDir2, basename5(audioPath)));
24963
25377
  console.log(`Audio: ${basename5(audioPath)}`);
24964
25378
  }
24965
25379
  if (sourceFilePath2 && !skipTranscribe) {
@@ -24980,15 +25394,15 @@ Examples:
24980
25394
  }
24981
25395
  scaffoldProject(destDir2, basename5(destDir2), templateId2, localVideoName2, videoDuration2);
24982
25396
  trackInitTemplate(templateId2);
24983
- const transcriptFile2 = resolve14(destDir2, "transcript.json");
24984
- if (existsSync27(transcriptFile2)) {
25397
+ const transcriptFile2 = resolve17(destDir2, "transcript.json");
25398
+ if (existsSync32(transcriptFile2)) {
24985
25399
  patchTranscript(destDir2, transcriptFile2);
24986
25400
  }
24987
25401
  if (!skipSkills) {
24988
25402
  await installSkills(false);
24989
25403
  }
24990
25404
  console.log(c.success(`Created ${c.accent(name2 + "/")}`));
24991
- for (const f of readdirSync7(destDir2).filter((f2) => !f2.startsWith("."))) {
25405
+ for (const f of readdirSync8(destDir2).filter((f2) => !f2.startsWith("."))) {
24992
25406
  console.log(` ${c.accent(f)}`);
24993
25407
  }
24994
25408
  console.log();
@@ -25028,8 +25442,8 @@ Examples:
25028
25442
  }
25029
25443
  name = nameResult;
25030
25444
  }
25031
- const destDir = resolve14(name);
25032
- if (existsSync27(destDir) && readdirSync7(destDir).length > 0) {
25445
+ const destDir = resolve17(name);
25446
+ if (existsSync32(destDir) && readdirSync8(destDir).length > 0) {
25033
25447
  const overwrite = await Rt({
25034
25448
  message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
25035
25449
  initialValue: false
@@ -25044,13 +25458,13 @@ Examples:
25044
25458
  let videoDuration;
25045
25459
  let isAudioOnly = false;
25046
25460
  if (videoFlag) {
25047
- const videoPath = resolve14(videoFlag);
25048
- if (!existsSync27(videoPath)) {
25461
+ const videoPath = resolve17(videoFlag);
25462
+ if (!existsSync32(videoPath)) {
25049
25463
  R2.error(`File not found: ${videoFlag}`);
25050
25464
  Nt("Setup cancelled.");
25051
25465
  process.exit(1);
25052
25466
  }
25053
- mkdirSync18(destDir, { recursive: true });
25467
+ mkdirSync20(destDir, { recursive: true });
25054
25468
  sourceFilePath = videoPath;
25055
25469
  const result = await handleVideoFile(videoPath, destDir, true);
25056
25470
  localVideoName = result.localVideoName;
@@ -25080,7 +25494,7 @@ Examples:
25080
25494
  validate(val) {
25081
25495
  const trimmed = val?.trim();
25082
25496
  if (!trimmed) return "Please enter a file path";
25083
- if (!existsSync27(resolve14(trimmed))) return "File not found";
25497
+ if (!existsSync32(resolve17(trimmed))) return "File not found";
25084
25498
  return void 0;
25085
25499
  }
25086
25500
  });
@@ -25088,16 +25502,16 @@ Examples:
25088
25502
  Nt("Setup cancelled.");
25089
25503
  process.exit(0);
25090
25504
  }
25091
- const filePath = resolve14(String(pathResult).trim());
25505
+ const filePath = resolve17(String(pathResult).trim());
25092
25506
  sourceFilePath = filePath;
25093
- mkdirSync18(destDir, { recursive: true });
25507
+ mkdirSync20(destDir, { recursive: true });
25094
25508
  if (mediaChoice === "video") {
25095
25509
  const result = await handleVideoFile(filePath, destDir, true);
25096
25510
  localVideoName = result.localVideoName;
25097
25511
  videoDuration = result.meta.durationSeconds;
25098
25512
  } else {
25099
25513
  isAudioOnly = true;
25100
- copyFileSync3(filePath, resolve14(destDir, basename5(filePath)));
25514
+ copyFileSync3(filePath, resolve17(destDir, basename5(filePath)));
25101
25515
  R2.info(`Audio copied to ${c.accent(basename5(filePath))}`);
25102
25516
  }
25103
25517
  }
@@ -25165,14 +25579,14 @@ Examples:
25165
25579
  }
25166
25580
  scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
25167
25581
  trackInitTemplate(templateId);
25168
- const transcriptFile = resolve14(destDir, "transcript.json");
25169
- if (existsSync27(transcriptFile)) {
25582
+ const transcriptFile = resolve17(destDir, "transcript.json");
25583
+ if (existsSync32(transcriptFile)) {
25170
25584
  patchTranscript(destDir, transcriptFile);
25171
25585
  }
25172
25586
  if (!skipSkills) {
25173
25587
  await installSkills(true);
25174
25588
  }
25175
- const files = readdirSync7(destDir);
25589
+ const files = readdirSync8(destDir);
25176
25590
  Vt2(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
25177
25591
  R2.message(
25178
25592
  `${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.
@@ -25185,17 +25599,19 @@ ${c.dim(" AI skills are installed \u2014 your agent knows how to create and
25185
25599
  });
25186
25600
 
25187
25601
  // src/commands/lint.ts
25188
- var lint_exports = {};
25189
- __export(lint_exports, {
25602
+ var lint_exports2 = {};
25603
+ __export(lint_exports2, {
25190
25604
  default: () => lint_default
25191
25605
  });
25192
- import { readFileSync as readFileSync13 } from "fs";
25606
+ import { readFileSync as readFileSync19 } from "fs";
25607
+ import { join as join30 } from "path";
25193
25608
  var lint_default;
25194
- var init_lint2 = __esm({
25609
+ var init_lint3 = __esm({
25195
25610
  "src/commands/lint.ts"() {
25196
25611
  "use strict";
25197
25612
  init_dist();
25198
- init_lint();
25613
+ init_lint2();
25614
+ init_studio_api();
25199
25615
  init_colors();
25200
25616
  init_project();
25201
25617
  init_updateCheck();
@@ -25207,32 +25623,57 @@ var init_lint2 = __esm({
25207
25623
  },
25208
25624
  async run({ args }) {
25209
25625
  const project = resolveProject(args.dir);
25210
- const html = readFileSync13(project.indexPath, "utf-8");
25211
- const result = lintHyperframeHtml(html, { filePath: project.indexPath });
25626
+ const htmlFiles = walkDir(project.dir).filter((f) => f.endsWith(".html"));
25627
+ const allFindings = [];
25628
+ let totalErrors = 0;
25629
+ let totalWarnings = 0;
25630
+ for (const file of htmlFiles) {
25631
+ const html = readFileSync19(join30(project.dir, file), "utf-8");
25632
+ const result = lintHyperframeHtml(html, { filePath: file });
25633
+ for (const f of result.findings) {
25634
+ allFindings.push({ ...f, file });
25635
+ }
25636
+ totalErrors += result.errorCount;
25637
+ totalWarnings += result.warningCount;
25638
+ }
25212
25639
  if (args.json) {
25213
- console.log(JSON.stringify(withMeta(result), null, 2));
25214
- process.exit(result.ok ? 0 : 1);
25640
+ console.log(
25641
+ JSON.stringify(
25642
+ withMeta({
25643
+ ok: totalErrors === 0,
25644
+ findings: allFindings,
25645
+ errorCount: totalErrors,
25646
+ warningCount: totalWarnings,
25647
+ filesScanned: htmlFiles.length
25648
+ }),
25649
+ null,
25650
+ 2
25651
+ )
25652
+ );
25653
+ process.exit(totalErrors > 0 ? 1 : 0);
25215
25654
  }
25216
- console.log(`${c.accent("\u25C6")} Linting ${c.accent(project.name + "/index.html")}`);
25655
+ console.log(
25656
+ `${c.accent("\u25C6")} Linting ${c.accent(project.name)} (${htmlFiles.length} HTML files)`
25657
+ );
25217
25658
  console.log();
25218
- if (result.ok) {
25659
+ if (allFindings.length === 0) {
25219
25660
  console.log(`${c.success("\u25C7")} ${c.success("0 errors, 0 warnings")}`);
25220
25661
  return;
25221
25662
  }
25222
- for (const finding of result.findings) {
25663
+ for (const finding of allFindings) {
25223
25664
  const prefix = finding.severity === "error" ? c.error("\u2717") : c.warn("\u26A0");
25224
25665
  const loc = finding.elementId ? ` ${c.accent(`[${finding.elementId}]`)}` : "";
25225
- console.log(`${prefix} ${c.bold(finding.code)}${loc}: ${finding.message}`);
25666
+ console.log(
25667
+ `${prefix} ${c.bold(finding.code)}${loc}: ${finding.message} ${c.dim(finding.file)}`
25668
+ );
25226
25669
  if (finding.fixHint) {
25227
25670
  console.log(` ${c.dim(`Fix: ${finding.fixHint}`)}`);
25228
25671
  }
25229
25672
  }
25230
- const summaryIcon = result.errorCount > 0 ? c.error("\u25C7") : c.success("\u25C7");
25231
- console.log(
25232
- `
25233
- ${summaryIcon} ${result.errorCount} error(s), ${result.warningCount} warning(s)`
25234
- );
25235
- process.exit(result.errorCount > 0 ? 1 : 0);
25673
+ const summaryIcon = totalErrors > 0 ? c.error("\u25C7") : c.success("\u25C7");
25674
+ console.log(`
25675
+ ${summaryIcon} ${totalErrors} error(s), ${totalWarnings} warning(s)`);
25676
+ process.exit(totalErrors > 0 ? 1 : 0);
25236
25677
  }
25237
25678
  });
25238
25679
  }
@@ -25256,16 +25697,16 @@ var info_exports = {};
25256
25697
  __export(info_exports, {
25257
25698
  default: () => info_default
25258
25699
  });
25259
- import { readFileSync as readFileSync14, readdirSync as readdirSync8, statSync as statSync10 } from "fs";
25260
- import { join as join25 } from "path";
25700
+ import { readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync12 } from "fs";
25701
+ import { join as join31 } from "path";
25261
25702
  function totalSize(dir) {
25262
25703
  let total = 0;
25263
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
25264
- const path = join25(dir, entry.name);
25704
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
25705
+ const path = join31(dir, entry.name);
25265
25706
  if (entry.isDirectory()) {
25266
25707
  total += totalSize(path);
25267
25708
  } else {
25268
- total += statSync10(path).size;
25709
+ total += statSync12(path).size;
25269
25710
  }
25270
25711
  }
25271
25712
  return total;
@@ -25289,7 +25730,7 @@ var init_info = __esm({
25289
25730
  },
25290
25731
  async run({ args }) {
25291
25732
  const project = resolveProject(args.dir);
25292
- const html = readFileSync14(project.indexPath, "utf-8");
25733
+ const html = readFileSync20(project.indexPath, "utf-8");
25293
25734
  ensureDOMParser();
25294
25735
  const parsed = parseHtml(html);
25295
25736
  const tracks = new Set(parsed.elements.map((el) => el.zIndex));
@@ -25340,7 +25781,7 @@ var compositions_exports = {};
25340
25781
  __export(compositions_exports, {
25341
25782
  default: () => compositions_default
25342
25783
  });
25343
- import { readFileSync as readFileSync15 } from "fs";
25784
+ import { readFileSync as readFileSync21 } from "fs";
25344
25785
  function parseCompositions(html) {
25345
25786
  const parser = new DOMParser();
25346
25787
  const doc = parser.parseFromString(html, "text/html");
@@ -25397,7 +25838,7 @@ var init_compositions = __esm({
25397
25838
  },
25398
25839
  async run({ args }) {
25399
25840
  const project = resolveProject(args.dir);
25400
- const html = readFileSync15(project.indexPath, "utf-8");
25841
+ const html = readFileSync21(project.indexPath, "utf-8");
25401
25842
  ensureDOMParser();
25402
25843
  const compositions = parseCompositions(html);
25403
25844
  if (compositions.length === 0) {
@@ -25433,8 +25874,8 @@ var benchmark_exports = {};
25433
25874
  __export(benchmark_exports, {
25434
25875
  default: () => benchmark_default
25435
25876
  });
25436
- import { existsSync as existsSync28, statSync as statSync11 } from "fs";
25437
- import { resolve as resolve15, join as join26 } from "path";
25877
+ import { existsSync as existsSync33, statSync as statSync13 } from "fs";
25878
+ import { resolve as resolve18, join as join32 } from "path";
25438
25879
  var DEFAULT_CONFIGS, benchmark_default;
25439
25880
  var init_benchmark = __esm({
25440
25881
  "src/commands/benchmark.ts"() {
@@ -25471,7 +25912,7 @@ var init_benchmark = __esm({
25471
25912
  process.exit(1);
25472
25913
  }
25473
25914
  const jsonOutput = args.json ?? false;
25474
- const benchDir = resolve15("renders", ".benchmark");
25915
+ const benchDir = resolve18("renders", ".benchmark");
25475
25916
  let producer = null;
25476
25917
  try {
25477
25918
  producer = await loadProducer();
@@ -25504,7 +25945,7 @@ var init_benchmark = __esm({
25504
25945
  s?.start(`Benchmarking ${config.label}...`);
25505
25946
  for (let i = 0; i < runsPerConfig; i++) {
25506
25947
  s?.message(`${config.label} \u2014 run ${i + 1}/${runsPerConfig}`);
25507
- const outputPath = join26(
25948
+ const outputPath = join32(
25508
25949
  benchDir,
25509
25950
  `${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i}.mp4`
25510
25951
  );
@@ -25518,8 +25959,8 @@ var init_benchmark = __esm({
25518
25959
  await producer.executeRenderJob(job, project.dir, outputPath);
25519
25960
  const elapsedMs = Date.now() - startTime;
25520
25961
  let fileSize = null;
25521
- if (existsSync28(outputPath)) {
25522
- const stat = statSync11(outputPath);
25962
+ if (existsSync33(outputPath)) {
25963
+ const stat = statSync13(outputPath);
25523
25964
  fileSize = stat.size;
25524
25965
  }
25525
25966
  runs.push({ elapsedMs, fileSize });
@@ -25727,15 +26168,15 @@ var docs_exports = {};
25727
26168
  __export(docs_exports, {
25728
26169
  default: () => docs_default
25729
26170
  });
25730
- import { readFileSync as readFileSync16, existsSync as existsSync29 } from "fs";
25731
- import { resolve as resolve16, dirname as dirname14, join as join27 } from "path";
26171
+ import { readFileSync as readFileSync22, existsSync as existsSync34 } from "fs";
26172
+ import { resolve as resolve19, dirname as dirname14, join as join33 } from "path";
25732
26173
  import { fileURLToPath as fileURLToPath6 } from "url";
25733
26174
  function docsDir() {
25734
26175
  const thisFile = fileURLToPath6(import.meta.url);
25735
26176
  const dir = dirname14(thisFile);
25736
- const devPath = resolve16(dir, "..", "docs");
25737
- const builtPath = resolve16(dir, "docs");
25738
- return existsSync29(devPath) ? devPath : builtPath;
26177
+ const devPath = resolve19(dir, "..", "docs");
26178
+ const builtPath = resolve19(dir, "docs");
26179
+ return existsSync34(devPath) ? devPath : builtPath;
25739
26180
  }
25740
26181
  function formatInlineCode(line) {
25741
26182
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -25826,12 +26267,12 @@ var init_docs = __esm({
25826
26267
  }
25827
26268
  process.exit(1);
25828
26269
  }
25829
- const filePath = join27(docsDir(), entry.file);
25830
- if (!existsSync29(filePath)) {
26270
+ const filePath = join33(docsDir(), entry.file);
26271
+ if (!existsSync34(filePath)) {
25831
26272
  console.error(c.error(`Doc file not found: ${filePath}`));
25832
26273
  process.exit(1);
25833
26274
  }
25834
- const content = readFileSync16(filePath, "utf-8");
26275
+ const content = readFileSync22(filePath, "utf-8");
25835
26276
  console.log();
25836
26277
  renderMarkdown(content);
25837
26278
  }
@@ -26225,8 +26666,8 @@ init_updateCheck();
26225
26666
  var subCommands = {
26226
26667
  init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
26227
26668
  dev: () => Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default),
26228
- render: () => Promise.resolve().then(() => (init_render(), render_exports)).then((m) => m.default),
26229
- lint: () => Promise.resolve().then(() => (init_lint2(), lint_exports)).then((m) => m.default),
26669
+ render: () => Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default),
26670
+ lint: () => Promise.resolve().then(() => (init_lint3(), lint_exports2)).then((m) => m.default),
26230
26671
  info: () => Promise.resolve().then(() => (init_info(), info_exports)).then((m) => m.default),
26231
26672
  compositions: () => Promise.resolve().then(() => (init_compositions(), compositions_exports)).then((m) => m.default),
26232
26673
  benchmark: () => Promise.resolve().then(() => (init_benchmark(), benchmark_exports)).then((m) => m.default),