@vibeframe/cli 0.115.8 → 0.115.9

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.
@@ -3370,14 +3370,14 @@ async function loadKokoroRuntime() {
3370
3370
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
3371
3371
  try {
3372
3372
  const { readFile: readFile45 } = await import("node:fs/promises");
3373
- const { join: join30, resolve: resolve63 } = await import("node:path");
3373
+ const { join: join31, resolve: resolve63 } = await import("node:path");
3374
3374
  const { pathToFileURL } = await import("node:url");
3375
3375
  const pkgDir = resolve63(baseDir, "node_modules", "kokoro-js");
3376
- const pkg = JSON.parse(await readFile45(join30(pkgDir, "package.json"), "utf-8"));
3376
+ const pkg = JSON.parse(await readFile45(join31(pkgDir, "package.json"), "utf-8"));
3377
3377
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
3378
3378
  if (!entry)
3379
3379
  return null;
3380
- const mod = await import(pathToFileURL(join30(pkgDir, entry)).href);
3380
+ const mod = await import(pathToFileURL(join31(pkgDir, entry)).href);
3381
3381
  return mod.KokoroTTS ?? null;
3382
3382
  } catch {
3383
3383
  return null;
@@ -3388,20 +3388,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
3388
3388
  if (!base)
3389
3389
  return null;
3390
3390
  try {
3391
- const { readFile: readFile45, writeFile: writeFile45, mkdir: mkdir27 } = await import("node:fs/promises");
3392
- const { join: join30, resolve: resolve63 } = await import("node:path");
3391
+ const { readFile: readFile45, writeFile: writeFile45, mkdir: mkdir28 } = await import("node:fs/promises");
3392
+ const { join: join31, resolve: resolve63 } = await import("node:path");
3393
3393
  const { pathToFileURL } = await import("node:url");
3394
3394
  const { homedir: homedir5 } = await import("node:os");
3395
3395
  const transformersDir = resolve63(base, "node_modules", "@huggingface", "transformers");
3396
3396
  const ortDistDir = resolve63(base, "node_modules", "onnxruntime-web", "dist");
3397
- const transformers = await import(pathToFileURL(join30(transformersDir, "dist", "transformers.web.js")).href);
3398
- const cacheRoot = join30(homedir5(), ".cache", "vibeframe", "models");
3397
+ const transformers = await import(pathToFileURL(join31(transformersDir, "dist", "transformers.web.js")).href);
3398
+ const cacheRoot = join31(homedir5(), ".cache", "vibeframe", "models");
3399
3399
  transformers.env.allowLocalModels = false;
3400
3400
  transformers.env.useCustomCache = true;
3401
3401
  transformers.env.customCache = {
3402
3402
  async match(key) {
3403
3403
  try {
3404
- return new Response(await readFile45(join30(cacheRoot, encodeURIComponent(String(key)))));
3404
+ return new Response(await readFile45(join31(cacheRoot, encodeURIComponent(String(key)))));
3405
3405
  } catch {
3406
3406
  return void 0;
3407
3407
  }
@@ -3409,8 +3409,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
3409
3409
  async put(key, response) {
3410
3410
  try {
3411
3411
  const buf = Buffer.from(await response.arrayBuffer());
3412
- await mkdir27(cacheRoot, { recursive: true });
3413
- await writeFile45(join30(cacheRoot, encodeURIComponent(String(key))), buf);
3412
+ await mkdir28(cacheRoot, { recursive: true });
3413
+ await writeFile45(join31(cacheRoot, encodeURIComponent(String(key))), buf);
3414
3414
  } catch {
3415
3415
  }
3416
3416
  }
@@ -30224,20 +30224,95 @@ var init_cost_tier = __esm({
30224
30224
  }
30225
30225
  });
30226
30226
 
30227
- // src/commands/_shared/scene-lint.ts
30228
- import { readdir as readdir3, readFile as readFile8, writeFile as writeFile9, stat as stat2 } from "node:fs/promises";
30227
+ // src/commands/_shared/lottie.ts
30229
30228
  import { existsSync as existsSync5 } from "node:fs";
30230
- import { resolve as resolve9, relative as relative2, join as join5 } from "node:path";
30229
+ import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile8 } from "node:fs/promises";
30230
+ import { createRequire } from "node:module";
30231
+ import { basename as basename3, join as join5 } from "node:path";
30232
+ import { fileURLToPath } from "node:url";
30233
+ async function checkLottieAsset(path) {
30234
+ const bytes = await readFile8(path);
30235
+ const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
30236
+ if (isZip) return void 0;
30237
+ try {
30238
+ const json = JSON.parse(bytes.toString("utf-8"));
30239
+ if (Array.isArray(json.layers)) return void 0;
30240
+ } catch {
30241
+ }
30242
+ return `${basename3(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
30243
+ }
30244
+ function overlayBox(layer, position, scale) {
30245
+ if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
30246
+ const width = Math.max(1, Math.round(layer.width * scale));
30247
+ const height = Math.max(1, Math.round(layer.height * scale));
30248
+ const marginX = Math.round(layer.width * 0.04);
30249
+ const marginY = Math.round(layer.height * 0.04);
30250
+ const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
30251
+ const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
30252
+ return { left, top, width, height };
30253
+ }
30254
+ function dotLottieDist() {
30255
+ try {
30256
+ return join5(createRequire(import.meta.url).resolve("@lottiefiles/dotlottie-web"), "..");
30257
+ } catch {
30258
+ const bundled = fileURLToPath(new URL(`./${BUNDLED_DOTLOTTIE_DIR}/`, import.meta.url));
30259
+ if (existsSync5(join5(bundled, DOTLOTTIE_WASM))) return bundled;
30260
+ throw new Error("The dotLottie runtime (@lottiefiles/dotlottie-web) is not installed. Reinstall VibeFrame.");
30261
+ }
30262
+ }
30263
+ async function copyDotLottieRuntime(dir) {
30264
+ const dist = dotLottieDist();
30265
+ await mkdir7(dir, { recursive: true });
30266
+ await copyFile2(join5(dist, "index.js"), join5(dir, DOTLOTTIE_SCRIPT));
30267
+ await copyFile2(join5(dist, DOTLOTTIE_WASM), join5(dir, DOTLOTTIE_WASM));
30268
+ }
30269
+ var LOTTIE_POSITIONS, DOTLOTTIE_SCRIPT, DOTLOTTIE_WASM, BUNDLED_DOTLOTTIE_DIR, DOTLOTTIE_BROWSER_HELPERS;
30270
+ var init_lottie = __esm({
30271
+ "src/commands/_shared/lottie.ts"() {
30272
+ "use strict";
30273
+ LOTTIE_POSITIONS = [
30274
+ "full",
30275
+ "center",
30276
+ "top-left",
30277
+ "top-right",
30278
+ "bottom-left",
30279
+ "bottom-right"
30280
+ ];
30281
+ DOTLOTTIE_SCRIPT = "dotlottie-web.js";
30282
+ DOTLOTTIE_WASM = "dotlottie-player.wasm";
30283
+ BUNDLED_DOTLOTTIE_DIR = "dotlottie";
30284
+ DOTLOTTIE_BROWSER_HELPERS = `async function loadDotLottie(vendorDir) {
30285
+ const url = (file) => new URL(vendorDir + "/" + file, document.baseURI).href;
30286
+ const { DotLottie } = await import(url("${DOTLOTTIE_SCRIPT}"));
30287
+ const wasm = await (await fetch(url("${DOTLOTTIE_WASM}"))).arrayBuffer();
30288
+ DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
30289
+ return DotLottie;
30290
+ }
30291
+ function setLottieTime(player, t, loop) {
30292
+ const length = player.duration;
30293
+ const frames = player.totalFrames;
30294
+ if (!(length > 0) || !(frames > 0)) return;
30295
+ const local = Math.max(0, Number(t) || 0);
30296
+ const at = loop ? local % length : Math.min(local, length);
30297
+ player.setFrame(Math.min(frames - 1, (at / length) * frames));
30298
+ }`;
30299
+ }
30300
+ });
30301
+
30302
+ // src/commands/_shared/scene-lint.ts
30303
+ import { readdir as readdir3, readFile as readFile9, writeFile as writeFile9, stat as stat2 } from "node:fs/promises";
30304
+ import { existsSync as existsSync6 } from "node:fs";
30305
+ import { resolve as resolve9, relative as relative2, join as join6 } from "node:path";
30231
30306
  import {
30232
30307
  runHyperframeLint
30233
30308
  } from "@hyperframes/producer";
30234
30309
  async function discoverSceneFiles(opts) {
30235
30310
  const projectDir = resolve9(opts.projectDir);
30236
30311
  const rootAbs = resolve9(projectDir, opts.rootRel ?? "index.html");
30237
- const root = existsSync5(rootAbs) ? rootAbs : null;
30312
+ const root = existsSync6(rootAbs) ? rootAbs : null;
30238
30313
  const compsDir = resolve9(projectDir, "compositions");
30239
30314
  const subs = [];
30240
- if (existsSync5(compsDir)) {
30315
+ if (existsSync6(compsDir)) {
30241
30316
  await collectHtmlRecursive(compsDir, subs);
30242
30317
  subs.sort();
30243
30318
  }
@@ -30246,7 +30321,7 @@ async function discoverSceneFiles(opts) {
30246
30321
  async function collectHtmlRecursive(dir, into) {
30247
30322
  const entries = await readdir3(dir, { withFileTypes: true });
30248
30323
  for (const entry of entries) {
30249
- const full = join5(dir, entry.name);
30324
+ const full = join6(dir, entry.name);
30250
30325
  if (entry.isDirectory()) {
30251
30326
  await collectHtmlRecursive(full, into);
30252
30327
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
@@ -30330,7 +30405,7 @@ async function runProjectLint(opts) {
30330
30405
  for (const sub of subs) targets.push({ abs: sub, isSub: true });
30331
30406
  for (const { abs, isSub } of targets) {
30332
30407
  const rel = relative2(projectDir, abs) || abs;
30333
- const html = await readFile8(abs, "utf-8");
30408
+ const html = await readFile9(abs, "utf-8");
30334
30409
  const prepared = {
30335
30410
  html,
30336
30411
  entryFile: rel,
@@ -30537,8 +30612,8 @@ var init_image_file = __esm({
30537
30612
  });
30538
30613
 
30539
30614
  // src/commands/_shared/image-jobs.ts
30540
- import { existsSync as existsSync6 } from "node:fs";
30541
- import { mkdir as mkdir7, readFile as readFile9 } from "node:fs/promises";
30615
+ import { existsSync as existsSync7 } from "node:fs";
30616
+ import { mkdir as mkdir8, readFile as readFile10 } from "node:fs/promises";
30542
30617
  import { dirname as dirname5, resolve as resolve10 } from "node:path";
30543
30618
  async function openImageGenerator(provider, apiKey) {
30544
30619
  const envVar = IMAGE_PROVIDER_ENV[provider];
@@ -30555,14 +30630,14 @@ async function openImageGenerator(provider, apiKey) {
30555
30630
  }
30556
30631
  async function readImageInput(path) {
30557
30632
  const absPath = resolve10(process.cwd(), path);
30558
- if (!existsSync6(absPath)) {
30633
+ if (!existsSync7(absPath)) {
30559
30634
  throw new ProviderError({ kind: "invalid-request", provider: "local", message: `Image not found: ${absPath}` });
30560
30635
  }
30561
- return { bytes: new Uint8Array(await readFile9(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
30636
+ return { bytes: new Uint8Array(await readFile10(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
30562
30637
  }
30563
30638
  async function saveImage(output, bytes) {
30564
30639
  const outputPath = resolve10(process.cwd(), output);
30565
- await mkdir7(dirname5(outputPath), { recursive: true });
30640
+ await mkdir8(dirname5(outputPath), { recursive: true });
30566
30641
  await writeImageFile(outputPath, Buffer.from(bytes));
30567
30642
  return outputPath;
30568
30643
  }
@@ -30620,19 +30695,19 @@ var init_audio = __esm({
30620
30695
  });
30621
30696
 
30622
30697
  // src/commands/_shared/install-skill.ts
30623
- import { existsSync as existsSync7 } from "node:fs";
30698
+ import { existsSync as existsSync8 } from "node:fs";
30624
30699
  import { homedir as homedir3 } from "node:os";
30625
- import { join as join6, resolve as resolve11 } from "node:path";
30700
+ import { join as join7, resolve as resolve11 } from "node:path";
30626
30701
  function hasGlobalHyperframesSkill() {
30627
- return existsSync7(join6(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
30702
+ return existsSync8(join7(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
30628
30703
  }
30629
30704
  function hasProjectHyperframesSkill(projectDir) {
30630
- return existsSync7(join6(resolve11(projectDir), PROJECT_SKILL_PATH));
30705
+ return existsSync8(join7(resolve11(projectDir), PROJECT_SKILL_PATH));
30631
30706
  }
30632
30707
  function resolveSkillReference(projectDir) {
30633
30708
  const dir = resolve11(projectDir);
30634
- if (existsSync7(join6(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
30635
- if (existsSync7(join6(dir, "SKILL.md"))) return "SKILL.md";
30709
+ if (existsSync8(join7(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
30710
+ if (existsSync8(join7(dir, "SKILL.md"))) return "SKILL.md";
30636
30711
  return null;
30637
30712
  }
30638
30713
  function hyperframesSkillAvailable(projectDir) {
@@ -30693,7 +30768,7 @@ var init_install_skill = __esm({
30693
30768
  init_exec_safe();
30694
30769
  HYPERFRAMES_SKILL_PACKAGE = "heygen-com/hyperframes";
30695
30770
  HYPERFRAMES_SKILL_INSTALL_COMMAND = `npx skills add ${HYPERFRAMES_SKILL_PACKAGE}`;
30696
- PROJECT_SKILL_PATH = join6(".agents", "skills", "hyperframes", "SKILL.md");
30771
+ PROJECT_SKILL_PATH = join7(".agents", "skills", "hyperframes", "SKILL.md");
30697
30772
  }
30698
30773
  });
30699
30774
 
@@ -30946,17 +31021,17 @@ var init_compose_beat_prompt = __esm({
30946
31021
  });
30947
31022
 
30948
31023
  // src/commands/_shared/root-sync.ts
30949
- import { existsSync as existsSync8 } from "node:fs";
30950
- import { readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
30951
- import { join as join7, resolve as resolve12 } from "node:path";
31024
+ import { existsSync as existsSync9 } from "node:fs";
31025
+ import { readFile as readFile11, writeFile as writeFile11 } from "node:fs/promises";
31026
+ import { join as join8, resolve as resolve12 } from "node:path";
30952
31027
  async function createRootSyncPlan(opts) {
30953
31028
  const projectDir = resolve12(opts.projectDir);
30954
31029
  const rootRel = opts.rootRel ?? "index.html";
30955
31030
  const rootPath = resolve12(projectDir, rootRel);
30956
- if (!existsSync8(rootPath)) {
31031
+ if (!existsSync9(rootPath)) {
30957
31032
  return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
30958
31033
  }
30959
- const html = await readFile10(rootPath, "utf-8");
31034
+ const html = await readFile11(rootPath, "utf-8");
30960
31035
  if (opts.beats.length === 0) {
30961
31036
  return { rootPath, rootRel, exists: true, changed: false, fixCodes: [], issues: [] };
30962
31037
  }
@@ -31015,8 +31090,8 @@ async function syncRootComposition(opts) {
31015
31090
  }
31016
31091
  async function loadProjectRootSyncBeats(projectDir) {
31017
31092
  const root = resolve12(projectDir);
31018
- const storyboardPath = join7(root, "STORYBOARD.md");
31019
- if (!existsSync8(storyboardPath)) return [];
31093
+ const storyboardPath = join8(root, "STORYBOARD.md");
31094
+ if (!existsSync9(storyboardPath)) return [];
31020
31095
  const parsed = parseStoryboard(await loadStoryboardMarkdown(root));
31021
31096
  const reportBeats = await readBuildReportBeats(root);
31022
31097
  return parsed.beats.map((beat) => {
@@ -31084,7 +31159,7 @@ async function resolveSyncedBeatDuration(opts) {
31084
31159
  if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
31085
31160
  const probe = opts.probeAudioDuration ?? getAudioDuration;
31086
31161
  try {
31087
- const audioDuration = await probe(join7(opts.projectDir, opts.narrationPath));
31162
+ const audioDuration = await probe(join8(opts.projectDir, opts.narrationPath));
31088
31163
  return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
31089
31164
  } catch {
31090
31165
  return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
@@ -31242,17 +31317,17 @@ function hasExpectedAudioRef(html, ref) {
31242
31317
  return ref.volume === void 0 || tag.includes(`data-volume="${ref.volume}"`);
31243
31318
  }
31244
31319
  async function readBuildReportBeats(projectDir) {
31245
- const reportPath = join7(projectDir, "build-report.json");
31246
- if (!existsSync8(reportPath)) return [];
31320
+ const reportPath = join8(projectDir, "build-report.json");
31321
+ if (!existsSync9(reportPath)) return [];
31247
31322
  try {
31248
- const report = JSON.parse(await readFile10(reportPath, "utf-8"));
31323
+ const report = JSON.parse(await readFile11(reportPath, "utf-8"));
31249
31324
  return Array.isArray(report.beats) ? report.beats : [];
31250
31325
  } catch {
31251
31326
  return [];
31252
31327
  }
31253
31328
  }
31254
31329
  function firstExisting(projectDir, rels) {
31255
- return rels.find((rel) => existsSync8(join7(projectDir, rel)));
31330
+ return rels.find((rel) => existsSync9(join8(projectDir, rel)));
31256
31331
  }
31257
31332
  function stringOrUndefined(value) {
31258
31333
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -31287,13 +31362,13 @@ var init_root_sync = __esm({
31287
31362
  });
31288
31363
 
31289
31364
  // src/commands/_shared/transcription.ts
31290
- import { readFile as readFile11 } from "node:fs/promises";
31291
- import { basename as basename3 } from "node:path";
31365
+ import { readFile as readFile12 } from "node:fs/promises";
31366
+ import { basename as basename4 } from "node:path";
31292
31367
  async function transcribeAudioFile(path, options) {
31293
31368
  try {
31294
31369
  const transcriber = await createTranscriber("openai", options.apiKey);
31295
- const audio = new Uint8Array(await readFile11(path));
31296
- const name = basename3(path);
31370
+ const audio = new Uint8Array(await readFile12(path));
31371
+ const name = basename4(path);
31297
31372
  const transcript = await transcriber.transcribeAudio({
31298
31373
  audio,
31299
31374
  // A real extension helps Whisper; temp files without one are sniffed from the bytes.
@@ -31326,9 +31401,9 @@ var init_transcription2 = __esm({
31326
31401
  });
31327
31402
 
31328
31403
  // src/commands/_shared/transcribe-narration.ts
31329
- import { existsSync as existsSync9 } from "node:fs";
31330
- import { readFile as readFile12 } from "node:fs/promises";
31331
- import { join as join8 } from "node:path";
31404
+ import { existsSync as existsSync10 } from "node:fs";
31405
+ import { readFile as readFile13 } from "node:fs/promises";
31406
+ import { join as join9 } from "node:path";
31332
31407
  async function transcribeNarrationWords(audioAbsPath, opts) {
31333
31408
  try {
31334
31409
  const transcript = await transcribeAudioFile(audioAbsPath, { apiKey: opts.apiKey, granularity: "word", language: opts.language });
@@ -31344,10 +31419,10 @@ function beatTranscriptRelPath(beatId) {
31344
31419
  return `assets/transcript-${beatId}.json`;
31345
31420
  }
31346
31421
  async function readBeatTranscript(projectDir, beatId) {
31347
- const abs = join8(projectDir, beatTranscriptRelPath(beatId));
31348
- if (!existsSync9(abs)) return void 0;
31422
+ const abs = join9(projectDir, beatTranscriptRelPath(beatId));
31423
+ if (!existsSync10(abs)) return void 0;
31349
31424
  try {
31350
- const parsed = JSON.parse(await readFile12(abs, "utf-8"));
31425
+ const parsed = JSON.parse(await readFile13(abs, "utf-8"));
31351
31426
  if (!Array.isArray(parsed)) return void 0;
31352
31427
  const words = parsed.filter(
31353
31428
  (w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
@@ -31365,15 +31440,15 @@ var init_transcribe_narration = __esm({
31365
31440
  });
31366
31441
 
31367
31442
  // src/commands/_shared/compose-prompts.ts
31368
- import { existsSync as existsSync10 } from "node:fs";
31369
- import { join as join9, relative as relative3, resolve as resolve13 } from "node:path";
31443
+ import { existsSync as existsSync11 } from "node:fs";
31444
+ import { join as join10, relative as relative3, resolve as resolve13 } from "node:path";
31370
31445
  async function getComposePrompts(opts) {
31371
31446
  const projectDir = resolve13(opts.projectDir);
31372
- const designPath = join9(projectDir, "DESIGN.md");
31373
- const compositionPath = join9(projectDir, "COMPOSITION.md");
31374
- const storyboardPath = join9(projectDir, "STORYBOARD.md");
31375
- const compositionsDir = join9(projectDir, "compositions");
31376
- const compositionReference = existsSync10(compositionPath) ? "COMPOSITION.md" : null;
31447
+ const designPath = join10(projectDir, "DESIGN.md");
31448
+ const compositionPath = join10(projectDir, "COMPOSITION.md");
31449
+ const storyboardPath = join10(projectDir, "STORYBOARD.md");
31450
+ const compositionsDir = join10(projectDir, "compositions");
31451
+ const compositionReference = existsSync11(compositionPath) ? "COMPOSITION.md" : null;
31377
31452
  const warnings = [];
31378
31453
  const baseError = (msg) => ({
31379
31454
  success: false,
@@ -31388,10 +31463,10 @@ async function getComposePrompts(opts) {
31388
31463
  warnings,
31389
31464
  error: msg
31390
31465
  });
31391
- if (!existsSync10(designPath)) {
31466
+ if (!existsSync11(designPath)) {
31392
31467
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
31393
31468
  }
31394
- if (!existsSync10(storyboardPath)) {
31469
+ if (!existsSync11(storyboardPath)) {
31395
31470
  return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
31396
31471
  }
31397
31472
  if (!hyperframesSkillAvailable(projectDir)) {
@@ -31423,7 +31498,7 @@ async function getComposePrompts(opts) {
31423
31498
  }
31424
31499
  const result = await Promise.all(
31425
31500
  beats.map(async (beat) => {
31426
- const outputPathAbs = join9(compositionsDir, `scene-${beat.id}.html`);
31501
+ const outputPathAbs = join10(compositionsDir, `scene-${beat.id}.html`);
31427
31502
  const outputPathRel = relative3(projectDir, outputPathAbs);
31428
31503
  const finalDurationSec = finalDurations.get(beat.id);
31429
31504
  const transcript = await readBeatTranscript(projectDir, beat.id);
@@ -31443,7 +31518,7 @@ async function getComposePrompts(opts) {
31443
31518
  body: beat.body,
31444
31519
  userPrompt,
31445
31520
  transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
31446
- exists: existsSync10(outputPathAbs)
31521
+ exists: existsSync11(outputPathAbs)
31447
31522
  };
31448
31523
  })
31449
31524
  );
@@ -31510,9 +31585,9 @@ var init_compose_prompts = __esm({
31510
31585
  });
31511
31586
 
31512
31587
  // src/commands/_shared/sub-comp-duration-sync.ts
31513
- import { existsSync as existsSync12 } from "node:fs";
31514
- import { readFile as readFile13 } from "node:fs/promises";
31515
- import { join as join11, resolve as resolve15 } from "node:path";
31588
+ import { existsSync as existsSync13 } from "node:fs";
31589
+ import { readFile as readFile14 } from "node:fs/promises";
31590
+ import { join as join12, resolve as resolve15 } from "node:path";
31516
31591
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
31517
31592
  const compositionId = `scene-${beatId}`;
31518
31593
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -31604,15 +31679,15 @@ async function createSubCompDurationSyncPlans(opts) {
31604
31679
  const results = [];
31605
31680
  for (const beat of opts.beats) {
31606
31681
  const fileRel = `compositions/scene-${beat.id}.html`;
31607
- const filePath = join11(projectDir, fileRel);
31608
- if (!existsSync12(filePath)) continue;
31682
+ const filePath = join12(projectDir, fileRel);
31683
+ if (!existsSync13(filePath)) continue;
31609
31684
  const newDurationSec = await resolveSyncedBeatDuration({
31610
31685
  projectDir,
31611
31686
  beatDuration: beat.duration,
31612
31687
  narrationPath: beat.narrationPath,
31613
31688
  sceneDurationSec: beat.sceneDurationSec
31614
31689
  });
31615
- const html = await readFile13(filePath, "utf-8");
31690
+ const html = await readFile14(filePath, "utf-8");
31616
31691
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
31617
31692
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
31618
31693
  }
@@ -31636,7 +31711,7 @@ var init_sub_comp_duration_sync = __esm({
31636
31711
  });
31637
31712
 
31638
31713
  // src/commands/_shared/scene-repair.ts
31639
- import { readFile as readFile14, writeFile as writeFile13 } from "node:fs/promises";
31714
+ import { readFile as readFile15, writeFile as writeFile13 } from "node:fs/promises";
31640
31715
  import { relative as relative4, resolve as resolve16 } from "node:path";
31641
31716
  import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
31642
31717
  async function executeSceneRepair(opts) {
@@ -31682,7 +31757,7 @@ async function executeSceneRepair(opts) {
31682
31757
  }
31683
31758
  for (const target of targets) {
31684
31759
  const rel = relative4(projectDir, target.abs) || target.abs;
31685
- const html = await readFile14(target.abs, "utf-8");
31760
+ const html = await readFile15(target.abs, "utf-8");
31686
31761
  const findings = await lintHtml(html, rel, target.isSub);
31687
31762
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
31688
31763
  if (fixedCodes.length > 0) {
@@ -31762,15 +31837,15 @@ var init_scene_repair = __esm({
31762
31837
  });
31763
31838
 
31764
31839
  // src/pipeline/renderers/chrome.ts
31765
- import { existsSync as existsSync14 } from "node:fs";
31840
+ import { existsSync as existsSync15 } from "node:fs";
31766
31841
  import { homedir as homedir4 } from "node:os";
31767
- import { join as join12 } from "node:path";
31842
+ import { join as join13 } from "node:path";
31768
31843
  function findChrome() {
31769
31844
  const candidates = [
31770
31845
  process.env.HYPERFRAMES_CHROME_PATH,
31771
31846
  process.env.CHROME_PATH,
31772
31847
  // puppeteer auto-downloaded headless shell
31773
- join12(
31848
+ join13(
31774
31849
  homedir4(),
31775
31850
  ".cache",
31776
31851
  "puppeteer",
@@ -31788,7 +31863,7 @@ function findChrome() {
31788
31863
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
31789
31864
  ];
31790
31865
  for (const c of candidates) {
31791
- if (c && existsSync14(c)) return c;
31866
+ if (c && existsSync15(c)) return c;
31792
31867
  }
31793
31868
  return void 0;
31794
31869
  }
@@ -31836,7 +31911,7 @@ var init_ffmpeg_gate = __esm({
31836
31911
  });
31837
31912
 
31838
31913
  // src/commands/_shared/scene-audio-scan.ts
31839
- import { readFile as readFile16 } from "node:fs/promises";
31914
+ import { readFile as readFile17 } from "node:fs/promises";
31840
31915
  import { resolve as resolve18 } from "node:path";
31841
31916
  function parseRootClips(rootHtml) {
31842
31917
  const clips = [];
@@ -31877,7 +31952,7 @@ function makeFsCompositionReader(projectDir) {
31877
31952
  return async (compositionSrcRel) => {
31878
31953
  const abs = resolve18(projectDir, compositionSrcRel);
31879
31954
  try {
31880
- return await readFile16(abs, "utf-8");
31955
+ return await readFile17(abs, "utf-8");
31881
31956
  } catch {
31882
31957
  return null;
31883
31958
  }
@@ -31944,7 +32019,7 @@ var init_scene_audio_scan = __esm({
31944
32019
 
31945
32020
  // src/commands/_shared/scene-audio-mux.ts
31946
32021
  import { rename as rename3, unlink } from "node:fs/promises";
31947
- import { resolve as resolve19, dirname as dirname7, extname as extname3, basename as basename4 } from "node:path";
32022
+ import { resolve as resolve19, dirname as dirname7, extname as extname3, basename as basename5 } from "node:path";
31948
32023
  function buildAudioMuxFilter(audios) {
31949
32024
  if (audios.length === 0) return null;
31950
32025
  const labels = [];
@@ -32006,7 +32081,7 @@ async function muxAudioIntoVideo(opts) {
32006
32081
  const ext = extname3(opts.videoPath) || `.${opts.format}`;
32007
32082
  const tmpPath = resolve19(
32008
32083
  dirname7(opts.videoPath),
32009
- `.${basename4(opts.videoPath, ext)}.muxing${ext}`
32084
+ `.${basename5(opts.videoPath, ext)}.muxing${ext}`
32010
32085
  );
32011
32086
  const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
32012
32087
  for (const a of opts.audios) {
@@ -32067,15 +32142,15 @@ var init_scene_audio_mux = __esm({
32067
32142
  });
32068
32143
 
32069
32144
  // src/commands/_shared/scene-assemble.ts
32070
- import { existsSync as existsSync15 } from "node:fs";
32071
- import { readFile as readFile17 } from "node:fs/promises";
32145
+ import { existsSync as existsSync16 } from "node:fs";
32146
+ import { readFile as readFile18 } from "node:fs/promises";
32072
32147
  import { resolve as resolve20, isAbsolute } from "node:path";
32073
32148
  async function executeSceneAssemble(opts) {
32074
32149
  const projectDir = resolve20(opts.projectDir);
32075
32150
  const root = opts.root ?? "index.html";
32076
32151
  const format = opts.format ?? "mp4";
32077
32152
  const videoPath = isAbsolute(opts.videoPath) ? opts.videoPath : resolve20(projectDir, opts.videoPath);
32078
- if (!existsSync15(videoPath)) {
32153
+ if (!existsSync16(videoPath)) {
32079
32154
  return {
32080
32155
  success: false,
32081
32156
  audioCount: 0,
@@ -32085,7 +32160,7 @@ async function executeSceneAssemble(opts) {
32085
32160
  };
32086
32161
  }
32087
32162
  const rootPath = resolve20(projectDir, root);
32088
- if (!existsSync15(rootPath)) {
32163
+ if (!existsSync16(rootPath)) {
32089
32164
  return {
32090
32165
  success: false,
32091
32166
  audioCount: 0,
@@ -32099,7 +32174,7 @@ async function executeSceneAssemble(opts) {
32099
32174
  let audioMuxWarning;
32100
32175
  try {
32101
32176
  opts.onProgress?.(0.1, "Scanning audio");
32102
- const rootHtml = await readFile17(rootPath, "utf-8");
32177
+ const rootHtml = await readFile18(rootPath, "utf-8");
32103
32178
  const audios = await scanSceneAudio({ projectDir, rootHtml });
32104
32179
  audioCount = audios.length;
32105
32180
  if (audios.length > 0) {
@@ -32152,9 +32227,9 @@ __export(scene_render_exports, {
32152
32227
  qualityToCrf: () => qualityToCrf
32153
32228
  });
32154
32229
  import { execFile as execFile2 } from "node:child_process";
32155
- import { existsSync as existsSync16 } from "node:fs";
32156
- import { mkdir as mkdir10, readFile as readFile18, stat as stat3, writeFile as writeFile15 } from "node:fs/promises";
32157
- import { resolve as resolve21, relative as relative6, dirname as dirname8, basename as basename5, join as join13, isAbsolute as isAbsolute2 } from "node:path";
32230
+ import { existsSync as existsSync17 } from "node:fs";
32231
+ import { mkdir as mkdir11, readFile as readFile19, stat as stat3, writeFile as writeFile15 } from "node:fs/promises";
32232
+ import { resolve as resolve21, relative as relative6, dirname as dirname8, basename as basename6, join as join14, isAbsolute as isAbsolute2 } from "node:path";
32158
32233
  import { promisify as promisify2 } from "node:util";
32159
32234
  import {
32160
32235
  createRenderJob,
@@ -32167,7 +32242,7 @@ function defaultOutputPath(opts) {
32167
32242
  const fmt = opts.format ?? "mp4";
32168
32243
  const now = opts.now ?? /* @__PURE__ */ new Date();
32169
32244
  const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
32170
- const name = (opts.projectName ?? basename5(resolve21(opts.projectDir))) || "scene";
32245
+ const name = (opts.projectName ?? basename6(resolve21(opts.projectDir))) || "scene";
32171
32246
  const beat = opts.beatId ? `-${sanitizeFileSegment(opts.beatId)}` : "";
32172
32247
  return resolve21(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
32173
32248
  }
@@ -32246,7 +32321,7 @@ async function executeSceneRender(opts = {}) {
32246
32321
  }
32247
32322
  let autoSyncApplied = false;
32248
32323
  let autoSyncWarning;
32249
- if (!opts.beatId && existsSync16(join13(projectDir, "STORYBOARD.md"))) {
32324
+ if (!opts.beatId && existsSync17(join14(projectDir, "STORYBOARD.md"))) {
32250
32325
  try {
32251
32326
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
32252
32327
  const subPlans = await createSubCompDurationSyncPlans({
@@ -32303,7 +32378,7 @@ async function executeSceneRender(opts = {}) {
32303
32378
  }
32304
32379
  const projectName = projectConfig.config.name;
32305
32380
  const outputPath = opts.output ? resolve21(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
32306
- await mkdir10(dirname8(outputPath), { recursive: true });
32381
+ await mkdir11(dirname8(outputPath), { recursive: true });
32307
32382
  const config2 = buildRenderConfig({
32308
32383
  fps: opts.fps,
32309
32384
  quality: opts.quality,
@@ -32415,11 +32490,11 @@ async function safeStat(p) {
32415
32490
  }
32416
32491
  async function prepareBeatRenderRoot(opts) {
32417
32492
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
32418
- const reportPath = join13(opts.projectDir, "build-report.json");
32493
+ const reportPath = join14(opts.projectDir, "build-report.json");
32419
32494
  const retryWith = [
32420
32495
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
32421
32496
  ];
32422
- if (!existsSync16(reportPath)) {
32497
+ if (!existsSync17(reportPath)) {
32423
32498
  return {
32424
32499
  success: false,
32425
32500
  root,
@@ -32430,7 +32505,7 @@ async function prepareBeatRenderRoot(opts) {
32430
32505
  }
32431
32506
  let report;
32432
32507
  try {
32433
- report = JSON.parse(await readFile18(reportPath, "utf-8"));
32508
+ report = JSON.parse(await readFile19(reportPath, "utf-8"));
32434
32509
  } catch (error) {
32435
32510
  return {
32436
32511
  success: false,
@@ -32499,8 +32574,8 @@ async function prepareBeatRenderRoot(opts) {
32499
32574
  durationSec,
32500
32575
  narrationPath
32501
32576
  });
32502
- await mkdir10(dirname8(join13(opts.projectDir, root)), { recursive: true });
32503
- await writeFile15(join13(opts.projectDir, root), html, "utf-8");
32577
+ await mkdir11(dirname8(join14(opts.projectDir, root)), { recursive: true });
32578
+ await writeFile15(join14(opts.projectDir, root), html, "utf-8");
32504
32579
  return {
32505
32580
  success: true,
32506
32581
  root,
@@ -32564,7 +32639,7 @@ function reportedBeatAssetPaths(beat) {
32564
32639
  ]);
32565
32640
  }
32566
32641
  async function writeRenderReport(projectDir, result) {
32567
- const reportPath = join13(projectDir, "render-report.json");
32642
+ const reportPath = join14(projectDir, "render-report.json");
32568
32643
  try {
32569
32644
  await writeFile15(
32570
32645
  reportPath,
@@ -32606,7 +32681,7 @@ async function writeRenderReport(projectDir, result) {
32606
32681
  }
32607
32682
  function pathExists3(projectDir, value) {
32608
32683
  const abs = isAbsolute2(value) ? value : resolve21(projectDir, value);
32609
- return existsSync16(abs);
32684
+ return existsSync17(abs);
32610
32685
  }
32611
32686
  function stringValue(value) {
32612
32687
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -32782,9 +32857,9 @@ var init_design_parse = __esm({
32782
32857
  });
32783
32858
 
32784
32859
  // src/commands/_shared/compose-aivideo.ts
32785
- import { existsSync as existsSync17 } from "node:fs";
32786
- import { mkdir as mkdir11, readFile as readFile19, writeFile as writeFile16 } from "node:fs/promises";
32787
- import { join as join14, resolve as resolve22 } from "node:path";
32860
+ import { existsSync as existsSync18 } from "node:fs";
32861
+ import { mkdir as mkdir12, readFile as readFile20, writeFile as writeFile16 } from "node:fs/promises";
32862
+ import { join as join15, resolve as resolve22 } from "node:path";
32788
32863
  function headingDescriptor(heading) {
32789
32864
  const stripped = heading.replace(/^\s*beat\s+/i, "");
32790
32865
  const m = stripped.match(/^[A-Za-z0-9][\w-]*(?:\s*[—:]\s*|\s+-\s+)(.+)$/);
@@ -32941,20 +33016,20 @@ async function composeAiVideo(opts) {
32941
33016
  const clipRels = [];
32942
33017
  for (const beat of beats) {
32943
33018
  const rel = clipRelFor(beat);
32944
- if (!existsSync17(join14(projectDir, rel))) {
33019
+ if (!existsSync18(join15(projectDir, rel))) {
32945
33020
  throw new Error(`Missing clip for beat "${beat.id}" at ${rel} \u2014 run the assets stage first.`);
32946
33021
  }
32947
33022
  clipRels.push(rel);
32948
33023
  }
32949
- await mkdir11(join14(projectDir, "assets"), { recursive: true });
33024
+ await mkdir12(join15(projectDir, "assets"), { recursive: true });
32950
33025
  const bgRel = "assets/bg-full.mp4";
32951
33026
  const concatArgs = buildBgConcatArgs(
32952
- clipRels.map((r) => join14(projectDir, r)),
32953
- join14(projectDir, bgRel)
33027
+ clipRels.map((r) => join15(projectDir, r)),
33028
+ join15(projectDir, bgRel)
32954
33029
  );
32955
33030
  await execSafe("ffmpeg", concatArgs, { timeout: 6e5 });
32956
- const totalDurationSec = await probeDurationSec(join14(projectDir, bgRel));
32957
- await mkdir11(join14(projectDir, "compositions"), { recursive: true });
33031
+ const totalDurationSec = await probeDurationSec(join15(projectDir, bgRel));
33032
+ await mkdir12(join15(projectDir, "compositions"), { recursive: true });
32958
33033
  const sceneFiles = [];
32959
33034
  for (let i = 0; i < beats.length; i++) {
32960
33035
  const beat = beats[i];
@@ -32971,11 +33046,11 @@ async function composeAiVideo(opts) {
32971
33046
  isLast: i === beats.length - 1
32972
33047
  });
32973
33048
  const rel = `compositions/scene-${beat.id}.html`;
32974
- await writeFile16(join14(projectDir, rel), html, "utf-8");
33049
+ await writeFile16(join15(projectDir, rel), html, "utf-8");
32975
33050
  sceneFiles.push(rel);
32976
33051
  }
32977
- const indexPath = join14(projectDir, "index.html");
32978
- const next = existsSync17(indexPath) ? injectBgVideo(await readFile19(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
33052
+ const indexPath = join15(projectDir, "index.html");
33053
+ const next = existsSync18(indexPath) ? injectBgVideo(await readFile20(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
32979
33054
  await writeFile16(indexPath, next, "utf-8");
32980
33055
  return { bgVideoRel: bgRel, sceneFiles, totalDurationSec, beats: beats.length };
32981
33056
  }
@@ -33024,10 +33099,10 @@ async function probeDurationSec(absPath) {
33024
33099
  }
33025
33100
  }
33026
33101
  async function loadProjectTokens(projectDir) {
33027
- const designPath = join14(projectDir, "DESIGN.md");
33028
- if (!existsSync17(designPath)) return DEFAULT_AIVIDEO_TOKENS;
33102
+ const designPath = join15(projectDir, "DESIGN.md");
33103
+ if (!existsSync18(designPath)) return DEFAULT_AIVIDEO_TOKENS;
33029
33104
  try {
33030
- return extractDesignTokens(await readFile19(designPath, "utf-8"));
33105
+ return extractDesignTokens(await readFile20(designPath, "utf-8"));
33031
33106
  } catch {
33032
33107
  return DEFAULT_AIVIDEO_TOKENS;
33033
33108
  }
@@ -33102,9 +33177,9 @@ var init_compose_aivideo = __esm({
33102
33177
  });
33103
33178
 
33104
33179
  // src/commands/_shared/footage-assemble.ts
33105
- import { existsSync as existsSync18 } from "node:fs";
33106
- import { mkdir as mkdir12, writeFile as writeFile17 } from "node:fs/promises";
33107
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join15, resolve as resolve23 } from "node:path";
33180
+ import { existsSync as existsSync19 } from "node:fs";
33181
+ import { mkdir as mkdir13, writeFile as writeFile17 } from "node:fs/promises";
33182
+ import { dirname as dirname9, isAbsolute as isAbsolute3, join as join16, resolve as resolve23 } from "node:path";
33108
33183
  function planFootageTimeline(inputs, opts = {}) {
33109
33184
  if (inputs.length === 0) {
33110
33185
  throw new Error("No clips to assemble.");
@@ -33330,12 +33405,12 @@ async function executeFootageAssemble(opts) {
33330
33405
  const kenburnsBeatIds = [];
33331
33406
  for (const beat of beats) {
33332
33407
  const clipRel = `assets/video-${beat.id}.mp4`;
33333
- if (existsSync18(join15(projectDir, clipRel))) {
33408
+ if (existsSync19(join16(projectDir, clipRel))) {
33334
33409
  sourceRels.push({ rel: clipRel, kind: "video" });
33335
33410
  continue;
33336
33411
  }
33337
33412
  const keyframeRel = `assets/keyframe-${beat.id}.png`;
33338
- if (existsSync18(join15(projectDir, keyframeRel))) {
33413
+ if (existsSync19(join16(projectDir, keyframeRel))) {
33339
33414
  sourceRels.push({ rel: keyframeRel, kind: "image" });
33340
33415
  kenburnsBeatIds.push(beat.id);
33341
33416
  continue;
@@ -33354,7 +33429,7 @@ async function executeFootageAssemble(opts) {
33354
33429
  }
33355
33430
  const narrationRels = /* @__PURE__ */ new Map();
33356
33431
  for (let i = 0; i < beats.length; i++) {
33357
- const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync18(join15(projectDir, r)));
33432
+ const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync19(join16(projectDir, r)));
33358
33433
  if (rel) narrationRels.set(i, rel);
33359
33434
  }
33360
33435
  const music = resolveMusicBed(projectDir, beats[0]?.id, opts.music);
@@ -33368,9 +33443,9 @@ async function executeFootageAssemble(opts) {
33368
33443
  }
33369
33444
  const inputs = [];
33370
33445
  for (let i = 0; i < beats.length; i++) {
33371
- const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join15(projectDir, sourceRels[i].rel)));
33446
+ const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join16(projectDir, sourceRels[i].rel)));
33372
33447
  const narrationRel = narrationRels.get(i);
33373
- const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join15(projectDir, narrationRel))) : void 0;
33448
+ const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join16(projectDir, narrationRel))) : void 0;
33374
33449
  inputs.push({ id: beats[i].id, clipDurationSec, narrationDurationSec });
33375
33450
  }
33376
33451
  const plan = planFootageTimeline(inputs, {
@@ -33433,14 +33508,14 @@ async function executeFootageAssemble(opts) {
33433
33508
  if (opts.dryRun) {
33434
33509
  return { success: true, dryRun: true, outputPath, report };
33435
33510
  }
33436
- await mkdir12(dirname9(outputPath), { recursive: true });
33511
+ await mkdir13(dirname9(outputPath), { recursive: true });
33437
33512
  const args = buildFootageAssembleArgs({
33438
- sources: sourceRels.map((s) => ({ path: join15(projectDir, s.rel), kind: s.kind })),
33513
+ sources: sourceRels.map((s) => ({ path: join16(projectDir, s.rel), kind: s.kind })),
33439
33514
  plan,
33440
33515
  narrationPaths: new Map(
33441
- [...narrationRels.entries()].map(([i, rel]) => [i, join15(projectDir, rel)])
33516
+ [...narrationRels.entries()].map(([i, rel]) => [i, join16(projectDir, rel)])
33442
33517
  ),
33443
- musicPath: music.rel ? isAbsolute3(music.rel) ? music.rel : join15(projectDir, music.rel) : void 0,
33518
+ musicPath: music.rel ? isAbsolute3(music.rel) ? music.rel : join16(projectDir, music.rel) : void 0,
33444
33519
  outputPath,
33445
33520
  musicVolume,
33446
33521
  finish: opts.finish
@@ -33457,7 +33532,7 @@ async function executeFootageAssemble(opts) {
33457
33532
  suggestion: "Re-run with --dry-run --json to inspect the planned timeline."
33458
33533
  };
33459
33534
  }
33460
- const reportPath = join15(projectDir, "assemble-report.json");
33535
+ const reportPath = join16(projectDir, "assemble-report.json");
33461
33536
  await writeFile17(reportPath, `${JSON.stringify(report, null, 2)}
33462
33537
  `, "utf-8");
33463
33538
  return { success: true, dryRun: false, outputPath, reportPath, report };
@@ -33465,15 +33540,15 @@ async function executeFootageAssemble(opts) {
33465
33540
  function resolveMusicBed(projectDir, firstBeatId, flag) {
33466
33541
  if (flag === "none") return { ok: true, source: "flag" };
33467
33542
  if (flag) {
33468
- const abs = isAbsolute3(flag) ? flag : join15(projectDir, flag);
33469
- if (!existsSync18(abs)) return { ok: false, error: `Music file not found: ${abs}` };
33543
+ const abs = isAbsolute3(flag) ? flag : join16(projectDir, flag);
33544
+ if (!existsSync19(abs)) return { ok: false, error: `Music file not found: ${abs}` };
33470
33545
  return { ok: true, rel: flag, source: "flag" };
33471
33546
  }
33472
33547
  const candidates = ["assets/music.mp3", "assets/music.wav"];
33473
33548
  if (firstBeatId) {
33474
33549
  candidates.push(`assets/music-${firstBeatId}.mp3`, `assets/music-${firstBeatId}.wav`);
33475
33550
  }
33476
- const rel = candidates.find((r) => existsSync18(join15(projectDir, r)));
33551
+ const rel = candidates.find((r) => existsSync19(join16(projectDir, r)));
33477
33552
  return { ok: true, rel, source: "auto" };
33478
33553
  }
33479
33554
  var FOOTAGE_DEFAULTS, round3, round6, DEFAULT_FOOTAGE_OUTPUT;
@@ -33515,7 +33590,7 @@ var init_footage_assemble = __esm({
33515
33590
  });
33516
33591
 
33517
33592
  // src/commands/_shared/build-asset-reference.ts
33518
- import { existsSync as existsSync19 } from "node:fs";
33593
+ import { existsSync as existsSync20 } from "node:fs";
33519
33594
  import { extname as extname4, isAbsolute as isAbsolute4, relative as relative7, resolve as resolve24, sep } from "node:path";
33520
33595
  function resolveTypedAssetReference(projectDir, kind, value) {
33521
33596
  const raw = stringOrUndefined2(value);
@@ -33546,7 +33621,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
33546
33621
  const absPath = isAbsolute4(raw) ? resolve24(raw) : resolve24(projectDir, raw);
33547
33622
  const rel = relative7(resolve24(projectDir), absPath);
33548
33623
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute4(rel);
33549
- const exists = withinProject && existsSync19(absPath);
33624
+ const exists = withinProject && existsSync20(absPath);
33550
33625
  let error = forcedError;
33551
33626
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
33552
33627
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -33826,8 +33901,8 @@ var init_build_video_providers = __esm({
33826
33901
  });
33827
33902
 
33828
33903
  // src/commands/_shared/build-plan.ts
33829
- import { existsSync as existsSync20 } from "node:fs";
33830
- import { dirname as dirname10, join as join16, resolve as resolve25 } from "node:path";
33904
+ import { existsSync as existsSync21 } from "node:fs";
33905
+ import { dirname as dirname10, join as join17, resolve as resolve25 } from "node:path";
33831
33906
  function backdropCostUsd(quality) {
33832
33907
  return quality === "hd" ? 0.2 : 0.05;
33833
33908
  }
@@ -33846,11 +33921,11 @@ async function createBuildPlan(opts) {
33846
33921
  const skipBackdrop = opts.skipBackdrop ?? (composerIsFootage ? true : kindPolicy.skipBackdrop ?? false);
33847
33922
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
33848
33923
  const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
33849
- const storyboardPath = join16(projectDir, "STORYBOARD.md");
33924
+ const storyboardPath = join17(projectDir, "STORYBOARD.md");
33850
33925
  const warnings = [];
33851
33926
  const retryWith = [];
33852
33927
  const providerResolution = [];
33853
- if (!existsSync20(storyboardPath)) {
33928
+ if (!existsSync21(storyboardPath)) {
33854
33929
  const validation2 = {
33855
33930
  ok: false,
33856
33931
  issues: [
@@ -33910,7 +33985,7 @@ async function createBuildPlan(opts) {
33910
33985
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
33911
33986
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
33912
33987
  const imageRatio = imageRatioForSize(imageSize2);
33913
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync20(join16(projectDir, `compositions/scene-${beat.id}.html`)));
33988
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync21(join17(projectDir, `compositions/scene-${beat.id}.html`)));
33914
33989
  const resolved = await resolvePlanProviders({
33915
33990
  projectDir,
33916
33991
  config: config2,
@@ -34060,7 +34135,7 @@ async function createBuildPlan(opts) {
34060
34135
  active: includeAssets
34061
34136
  }) : null;
34062
34137
  const compositionPath = `compositions/scene-${beat.id}.html`;
34063
- const compositionExists = existsSync20(join16(projectDir, compositionPath));
34138
+ const compositionExists = existsSync21(join17(projectDir, compositionPath));
34064
34139
  for (const asset of [narration, backdrop, keyframe, video, music]) {
34065
34140
  if (!asset) continue;
34066
34141
  if (asset.referenceError) {
@@ -34133,7 +34208,7 @@ async function createBuildPlan(opts) {
34133
34208
  }
34134
34209
  }
34135
34210
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
34136
- if (!existsSync20(join16(projectDir, config2.config.composition.entry))) {
34211
+ if (!existsSync21(join17(projectDir, config2.config.composition.entry))) {
34137
34212
  missing.add("root-composition");
34138
34213
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
34139
34214
  }
@@ -34167,7 +34242,7 @@ async function createBuildPlan(opts) {
34167
34242
  }
34168
34243
  if (referenced.size > 0) {
34169
34244
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
34170
- (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync20(join16(projectDir, `assets/character-${c.name}.png`)))
34245
+ (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync21(join17(projectDir, `assets/character-${c.name}.png`)))
34171
34246
  );
34172
34247
  if (toGenerate.length > 0) {
34173
34248
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -34280,8 +34355,8 @@ function assetPlan(opts) {
34280
34355
  reason: ready ? "referenced-asset" : "invalid-reference"
34281
34356
  };
34282
34357
  }
34283
- const canonicalExists = existsSync20(join16(opts.projectDir, opts.path));
34284
- const cacheHit = opts.cache ? existsSync20(join16(opts.projectDir, opts.cache.path)) : false;
34358
+ const canonicalExists = existsSync21(join17(opts.projectDir, opts.path));
34359
+ const cacheHit = opts.cache ? existsSync21(join17(opts.projectDir, opts.cache.path)) : false;
34285
34360
  const freshness = assetFreshnessFromMetadata({
34286
34361
  projectDir: opts.projectDir,
34287
34362
  kind: opts.kind,
@@ -34490,7 +34565,7 @@ async function firstConfiguredComposer(projectDir) {
34490
34565
  }
34491
34566
  function firstExisting2(projectDir, paths) {
34492
34567
  for (const path of paths) {
34493
- if (existsSync20(join16(projectDir, path))) return path;
34568
+ if (existsSync21(join17(projectDir, path))) return path;
34494
34569
  }
34495
34570
  return null;
34496
34571
  }
@@ -34526,12 +34601,12 @@ function unique3(items) {
34526
34601
  return [...new Set(items.filter((item) => item.length > 0))];
34527
34602
  }
34528
34603
  function loadPlanEnv(projectDir) {
34529
- (0, import_dotenv2.config)({ path: join16(projectDir, ".env"), quiet: true });
34604
+ (0, import_dotenv2.config)({ path: join17(projectDir, ".env"), quiet: true });
34530
34605
  (0, import_dotenv2.config)({ path: resolve25(process.cwd(), ".env"), quiet: true });
34531
34606
  let dir = process.cwd();
34532
34607
  while (dir !== dirname10(dir)) {
34533
- if (existsSync20(join16(dir, "pnpm-workspace.yaml"))) {
34534
- (0, import_dotenv2.config)({ path: join16(dir, ".env"), quiet: true });
34608
+ if (existsSync21(join17(dir, "pnpm-workspace.yaml"))) {
34609
+ (0, import_dotenv2.config)({ path: join17(dir, ".env"), quiet: true });
34535
34610
  return;
34536
34611
  }
34537
34612
  dir = dirname10(dir);
@@ -34597,7 +34672,7 @@ var init_validate = __esm({
34597
34672
  // src/commands/generate/music.ts
34598
34673
  import { resolve as resolve27 } from "node:path";
34599
34674
  import { writeFile as writeFile18 } from "node:fs/promises";
34600
- import { existsSync as existsSync21 } from "node:fs";
34675
+ import { existsSync as existsSync22 } from "node:fs";
34601
34676
  async function executeMusic(options) {
34602
34677
  try {
34603
34678
  const provider = options.provider || "elevenlabs";
@@ -34679,7 +34754,7 @@ function registerMusicCommand(parent) {
34679
34754
  exitWithError(usageError(`Invalid provider: ${provider}`, "Available providers: elevenlabs, replicate"));
34680
34755
  }
34681
34756
  if (options.melody) {
34682
- if (!existsSync21(resolve27(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
34757
+ if (!existsSync22(resolve27(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
34683
34758
  exitWithError(usageError("Melody conditioning requires a publicly accessible URL", "Please upload your melody file and provide the URL."));
34684
34759
  }
34685
34760
  if (options.dryRun) {
@@ -34784,12 +34859,12 @@ __export(scene_build_exports, {
34784
34859
  isLikenessRejection: () => isLikenessRejection,
34785
34860
  resolveSceneBuildMode: () => resolveSceneBuildMode
34786
34861
  });
34787
- import { existsSync as existsSync22, readFileSync as readFileSync2 } from "node:fs";
34788
- import { copyFile as copyFile3, mkdir as mkdir13, readFile as readFile20, rm as rm3, writeFile as writeFile19 } from "node:fs/promises";
34789
- import { dirname as dirname11, join as join17, resolve as resolve28 } from "node:path";
34862
+ import { existsSync as existsSync23, readFileSync as readFileSync2 } from "node:fs";
34863
+ import { copyFile as copyFile4, mkdir as mkdir14, readFile as readFile21, rm as rm3, writeFile as writeFile19 } from "node:fs/promises";
34864
+ import { dirname as dirname11, join as join18, resolve as resolve28 } from "node:path";
34790
34865
  function readProjectKindSync(projectDir) {
34791
34866
  try {
34792
- const raw = readFileSync2(join17(projectDir, "vibe.config.json"), "utf-8");
34867
+ const raw = readFileSync2(join18(projectDir, "vibe.config.json"), "utf-8");
34793
34868
  const k = JSON.parse(raw).kind;
34794
34869
  return typeof k === "string" && isSceneKind(k) ? k : "cinema";
34795
34870
  } catch {
@@ -34821,7 +34896,7 @@ async function executeSceneBuild(opts) {
34821
34896
  );
34822
34897
  }
34823
34898
  let sceneRepair = skippedSceneRepairSummary();
34824
- const storyboardPath = join17(projectDir, "STORYBOARD.md");
34899
+ const storyboardPath = join18(projectDir, "STORYBOARD.md");
34825
34900
  const buildPlan = await createBuildPlan({
34826
34901
  projectDir,
34827
34902
  stage: selectedStage,
@@ -34893,7 +34968,7 @@ async function executeSceneBuild(opts) {
34893
34968
  }
34894
34969
  if (!buildPlan.validation.ok) {
34895
34970
  let invalidBeats = [];
34896
- if (existsSync22(storyboardPath)) {
34971
+ if (existsSync23(storyboardPath)) {
34897
34972
  invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
34898
34973
  }
34899
34974
  return finishBuildResult({
@@ -34918,7 +34993,7 @@ async function executeSceneBuild(opts) {
34918
34993
  totalLatencyMs: Date.now() - startedAt
34919
34994
  });
34920
34995
  }
34921
- if (!existsSync22(storyboardPath)) {
34996
+ if (!existsSync23(storyboardPath)) {
34922
34997
  return finishBuildResult({
34923
34998
  success: false,
34924
34999
  phase: "failed",
@@ -35154,9 +35229,9 @@ async function executeSceneBuild(opts) {
35154
35229
  }
35155
35230
  if (shouldRunStage(selectedStage, "compose")) {
35156
35231
  if (mode === "agent") {
35157
- const compositionsDir = join17(projectDir, "compositions");
35232
+ const compositionsDir = join18(projectDir, "compositions");
35158
35233
  const missingBeats = activeBeats.filter(
35159
- (b) => !existsSync22(join17(compositionsDir, `scene-${b.id}.html`))
35234
+ (b) => !existsSync23(join18(compositionsDir, `scene-${b.id}.html`))
35160
35235
  );
35161
35236
  if (missingBeats.length > 0) {
35162
35237
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -35231,8 +35306,8 @@ async function executeSceneBuild(opts) {
35231
35306
  if (stageReports.compose.status === "done") {
35232
35307
  for (const beat of composer === "template" ? [] : activeBeats) {
35233
35308
  const videoRel = `assets/video-${beat.id}.mp4`;
35234
- const compositionAbs = join17(projectDir, "compositions", `scene-${beat.id}.html`);
35235
- if (!existsSync22(join17(projectDir, videoRel)) || !existsSync22(compositionAbs)) continue;
35309
+ const compositionAbs = join18(projectDir, "compositions", `scene-${beat.id}.html`);
35310
+ if (!existsSync23(join18(projectDir, videoRel)) || !existsSync23(compositionAbs)) continue;
35236
35311
  const html = readFileSync2(compositionAbs, "utf-8");
35237
35312
  if (!html.includes(videoRel)) {
35238
35313
  warnings.push(
@@ -35288,7 +35363,7 @@ async function executeSceneBuild(opts) {
35288
35363
  });
35289
35364
  }
35290
35365
  if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
35291
- if (!existsSync22(join17(projectDir, "index.html"))) {
35366
+ if (!existsSync23(join18(projectDir, "index.html"))) {
35292
35367
  await scaffoldSceneProject({
35293
35368
  dir: projectDir,
35294
35369
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -35590,7 +35665,7 @@ async function dispatchNarration(beat, ctx) {
35590
35665
  return {
35591
35666
  status: "cached",
35592
35667
  path: existingRel,
35593
- durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35668
+ durationSec: await safeAudioDuration(join18(ctx.projectDir, existingRel)),
35594
35669
  provider: metadata?.provider,
35595
35670
  cachePath: metadata?.cachePath,
35596
35671
  cacheKey: metadata?.cacheKey,
@@ -35614,9 +35689,9 @@ async function dispatchNarration(beat, ctx) {
35614
35689
  voice,
35615
35690
  ext: resolution.audioExtension
35616
35691
  });
35617
- const cacheAbs = join17(ctx.projectDir, cache.path);
35692
+ const cacheAbs = join18(ctx.projectDir, cache.path);
35618
35693
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
35619
- const abs = join17(ctx.projectDir, rel);
35694
+ const abs = join18(ctx.projectDir, rel);
35620
35695
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
35621
35696
  projectDir: ctx.projectDir,
35622
35697
  kind: "narration",
@@ -35630,7 +35705,7 @@ async function dispatchNarration(beat, ctx) {
35630
35705
  return {
35631
35706
  status: "cached",
35632
35707
  path: existingRel,
35633
- durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35708
+ durationSec: await safeAudioDuration(join18(ctx.projectDir, existingRel)),
35634
35709
  provider: resolution.provider,
35635
35710
  cachePath: cache.path,
35636
35711
  cacheKey: cache.key,
@@ -35638,9 +35713,9 @@ async function dispatchNarration(beat, ctx) {
35638
35713
  freshness: "fresh"
35639
35714
  };
35640
35715
  }
35641
- if (existsSync22(cacheAbs) && !ctx.force) {
35642
- await mkdir13(dirname11(abs), { recursive: true });
35643
- await copyFile3(cacheAbs, abs);
35716
+ if (existsSync23(cacheAbs) && !ctx.force) {
35717
+ await mkdir14(dirname11(abs), { recursive: true });
35718
+ await copyFile4(cacheAbs, abs);
35644
35719
  await writeAssetMetadata({
35645
35720
  projectDir: ctx.projectDir,
35646
35721
  kind: "narration",
@@ -35670,13 +35745,13 @@ async function dispatchNarration(beat, ctx) {
35670
35745
  ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
35671
35746
  return { status: "failed", error };
35672
35747
  }
35673
- await mkdir13(dirname11(abs), { recursive: true });
35748
+ await mkdir14(dirname11(abs), { recursive: true });
35674
35749
  await writeFile19(abs, result.audioBuffer);
35675
35750
  for (const otherExt of ["mp3", "wav"]) {
35676
- const sibling = join17(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35677
- if (sibling !== abs && existsSync22(sibling)) await rm3(sibling, { force: true });
35751
+ const sibling = join18(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35752
+ if (sibling !== abs && existsSync23(sibling)) await rm3(sibling, { force: true });
35678
35753
  }
35679
- await mkdir13(dirname11(cacheAbs), { recursive: true });
35754
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
35680
35755
  await writeFile19(cacheAbs, result.audioBuffer);
35681
35756
  await writeAssetMetadata({
35682
35757
  projectDir: ctx.projectDir,
@@ -35709,8 +35784,8 @@ async function dispatchNarration(beat, ctx) {
35709
35784
  async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
35710
35785
  if (!narrationPath) return "skipped";
35711
35786
  const relPath = beatTranscriptRelPath(beat.id);
35712
- const abs = join17(ctx.projectDir, relPath);
35713
- if (existsSync22(abs) && !ctx.force && !narrationFresh) {
35787
+ const abs = join18(ctx.projectDir, relPath);
35788
+ if (existsSync23(abs) && !ctx.force && !narrationFresh) {
35714
35789
  ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
35715
35790
  return "cached";
35716
35791
  }
@@ -35723,7 +35798,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
35723
35798
  });
35724
35799
  return "skipped";
35725
35800
  }
35726
- const words = await transcribeNarrationWords(join17(ctx.projectDir, narrationPath), { apiKey });
35801
+ const words = await transcribeNarrationWords(join18(ctx.projectDir, narrationPath), { apiKey });
35727
35802
  if (words.length === 0) {
35728
35803
  ctx.onProgress({
35729
35804
  type: "transcript-skipped",
@@ -35733,7 +35808,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
35733
35808
  return "skipped";
35734
35809
  }
35735
35810
  try {
35736
- await mkdir13(dirname11(abs), { recursive: true });
35811
+ await mkdir14(dirname11(abs), { recursive: true });
35737
35812
  await writeFile19(abs, JSON.stringify(words, null, 2), "utf-8");
35738
35813
  } catch (err) {
35739
35814
  ctx.onProgress({
@@ -35781,7 +35856,7 @@ async function buildCharacters(characters, ctx) {
35781
35856
  const size = ctx.imageSize ?? "1536x1024";
35782
35857
  for (const character of characters) {
35783
35858
  if (character.imagePath) {
35784
- if (existsSync22(join17(ctx.projectDir, character.imagePath))) {
35859
+ if (existsSync23(join18(ctx.projectDir, character.imagePath))) {
35785
35860
  paths.set(character.name, character.imagePath);
35786
35861
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
35787
35862
  } else {
@@ -35794,7 +35869,7 @@ async function buildCharacters(characters, ctx) {
35794
35869
  if (!character.prompt) continue;
35795
35870
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
35796
35871
  const rel = `assets/character-${character.name}.png`;
35797
- const abs = join17(ctx.projectDir, rel);
35872
+ const abs = join18(ctx.projectDir, rel);
35798
35873
  const metadataOptions = { quality: ctx.imageQuality, size };
35799
35874
  const cache = characterCacheDescriptor({
35800
35875
  name: character.name,
@@ -35804,7 +35879,7 @@ async function buildCharacters(characters, ctx) {
35804
35879
  size,
35805
35880
  model: ctx.imageModel
35806
35881
  });
35807
- if (existsSync22(abs) && !ctx.force) {
35882
+ if (existsSync23(abs) && !ctx.force) {
35808
35883
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
35809
35884
  if (!metadata || isFreshCanonicalAsset({
35810
35885
  projectDir: ctx.projectDir,
@@ -35820,10 +35895,10 @@ async function buildCharacters(characters, ctx) {
35820
35895
  continue;
35821
35896
  }
35822
35897
  }
35823
- const cacheAbs = join17(ctx.projectDir, cache.path);
35824
- if (existsSync22(cacheAbs) && !ctx.force) {
35825
- await mkdir13(dirname11(abs), { recursive: true });
35826
- await copyFile3(cacheAbs, abs);
35898
+ const cacheAbs = join18(ctx.projectDir, cache.path);
35899
+ if (existsSync23(cacheAbs) && !ctx.force) {
35900
+ await mkdir14(dirname11(abs), { recursive: true });
35901
+ await copyFile4(cacheAbs, abs);
35827
35902
  await writeAssetMetadata({
35828
35903
  projectDir: ctx.projectDir,
35829
35904
  kind: "character",
@@ -35855,9 +35930,9 @@ async function buildCharacters(characters, ctx) {
35855
35930
  ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
35856
35931
  continue;
35857
35932
  }
35858
- await mkdir13(dirname11(abs), { recursive: true });
35933
+ await mkdir14(dirname11(abs), { recursive: true });
35859
35934
  await writeImageFile(abs, generated.buffer);
35860
- await mkdir13(dirname11(cacheAbs), { recursive: true });
35935
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
35861
35936
  await writeImageFile(cacheAbs, generated.buffer);
35862
35937
  await writeAssetMetadata({
35863
35938
  projectDir: ctx.projectDir,
@@ -35888,7 +35963,7 @@ async function dispatchBackdrop(beat, ctx) {
35888
35963
  if (!cue) return { status: "no-cue" };
35889
35964
  const prompt3 = augmentBackdropPrompt(cue);
35890
35965
  const rel = `assets/backdrop-${beat.id}.png`;
35891
- const abs = join17(ctx.projectDir, rel);
35966
+ const abs = join18(ctx.projectDir, rel);
35892
35967
  const size = ctx.imageSize ?? "1536x1024";
35893
35968
  const ratio = imageRatioForSize(size);
35894
35969
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -35902,7 +35977,7 @@ async function dispatchBackdrop(beat, ctx) {
35902
35977
  model: ctx.imageModel
35903
35978
  });
35904
35979
  const metadataPath = assetMetadataPath("backdrop", beat.id);
35905
- if (existsSync22(abs) && !ctx.force) {
35980
+ if (existsSync23(abs) && !ctx.force) {
35906
35981
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
35907
35982
  if (!metadata || isFreshCanonicalAsset({
35908
35983
  projectDir: ctx.projectDir,
@@ -35925,10 +36000,10 @@ async function dispatchBackdrop(beat, ctx) {
35925
36000
  };
35926
36001
  }
35927
36002
  }
35928
- const cacheAbs = join17(ctx.projectDir, cache.path);
35929
- if (existsSync22(cacheAbs) && !ctx.force) {
35930
- await mkdir13(dirname11(abs), { recursive: true });
35931
- await copyFile3(cacheAbs, abs);
36003
+ const cacheAbs = join18(ctx.projectDir, cache.path);
36004
+ if (existsSync23(cacheAbs) && !ctx.force) {
36005
+ await mkdir14(dirname11(abs), { recursive: true });
36006
+ await copyFile4(cacheAbs, abs);
35932
36007
  await writeAssetMetadata({
35933
36008
  projectDir: ctx.projectDir,
35934
36009
  kind: "backdrop",
@@ -35957,10 +36032,10 @@ async function dispatchBackdrop(beat, ctx) {
35957
36032
  ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
35958
36033
  return { status: "failed", error };
35959
36034
  }
35960
- await mkdir13(dirname11(abs), { recursive: true });
36035
+ await mkdir14(dirname11(abs), { recursive: true });
35961
36036
  const buffer = generated.buffer;
35962
36037
  await writeImageFile(abs, buffer);
35963
- await mkdir13(dirname11(cacheAbs), { recursive: true });
36038
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
35964
36039
  await writeImageFile(cacheAbs, buffer);
35965
36040
  await writeAssetMetadata({
35966
36041
  projectDir: ctx.projectDir,
@@ -36023,7 +36098,7 @@ async function buildImage(prompt3, ctx, ratio, sheetBuffers) {
36023
36098
  }
36024
36099
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
36025
36100
  const rel = `assets/keyframe-${beat.id}.png`;
36026
- const abs = join17(ctx.projectDir, rel);
36101
+ const abs = join18(ctx.projectDir, rel);
36027
36102
  const size = ctx.imageSize ?? "1536x1024";
36028
36103
  const ratio = imageRatioForSize(size);
36029
36104
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -36037,7 +36112,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
36037
36112
  characters: characterRefsRel,
36038
36113
  model: ctx.imageModel
36039
36114
  });
36040
- if (existsSync22(abs) && !ctx.force) {
36115
+ if (existsSync23(abs) && !ctx.force) {
36041
36116
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
36042
36117
  if (!metadata || isFreshCanonicalAsset({
36043
36118
  projectDir: ctx.projectDir,
@@ -36051,10 +36126,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
36051
36126
  return { status: "cached", rel };
36052
36127
  }
36053
36128
  }
36054
- const cacheAbs = join17(ctx.projectDir, cache.path);
36055
- if (existsSync22(cacheAbs) && !ctx.force) {
36056
- await mkdir13(dirname11(abs), { recursive: true });
36057
- await copyFile3(cacheAbs, abs);
36129
+ const cacheAbs = join18(ctx.projectDir, cache.path);
36130
+ if (existsSync23(cacheAbs) && !ctx.force) {
36131
+ await mkdir14(dirname11(abs), { recursive: true });
36132
+ await copyFile4(cacheAbs, abs);
36058
36133
  await writeAssetMetadata({
36059
36134
  projectDir: ctx.projectDir,
36060
36135
  kind: "keyframe",
@@ -36070,8 +36145,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
36070
36145
  }
36071
36146
  const sheetBuffers = [];
36072
36147
  for (const relPath of characterRefsRel) {
36073
- const sheetAbs = join17(ctx.projectDir, relPath);
36074
- if (existsSync22(sheetAbs)) sheetBuffers.push(await readFile20(sheetAbs));
36148
+ const sheetAbs = join18(ctx.projectDir, relPath);
36149
+ if (existsSync23(sheetAbs)) sheetBuffers.push(await readFile21(sheetAbs));
36075
36150
  }
36076
36151
  const imgCtx = {
36077
36152
  projectDir: ctx.projectDir,
@@ -36083,9 +36158,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
36083
36158
  const identityLock = "Keep the EXACT same person and face as the reference image(s): identical facial features (eyes, nose shape, jawline), skin tone and complexion, hairstyle, hair color, and wardrobe. Do not restyle or change the face. Change only the scene, pose, framing, and lighting. ";
36084
36159
  const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
36085
36160
  if (!generated.success) return { status: "failed", error: generated.error };
36086
- await mkdir13(dirname11(abs), { recursive: true });
36161
+ await mkdir14(dirname11(abs), { recursive: true });
36087
36162
  await writeImageFile(abs, generated.buffer);
36088
- await mkdir13(dirname11(cacheAbs), { recursive: true });
36163
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
36089
36164
  await writeImageFile(cacheAbs, generated.buffer);
36090
36165
  await writeAssetMetadata({
36091
36166
  projectDir: ctx.projectDir,
@@ -36128,9 +36203,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
36128
36203
  const providerCue = stringOrUndefined4(beat.cues?.provider);
36129
36204
  const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
36130
36205
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
36131
- const characterRefsAbs = characterRefsRel.map((p) => join17(ctx.projectDir, p));
36206
+ const characterRefsAbs = characterRefsRel.map((p) => join18(ctx.projectDir, p));
36132
36207
  const rel = `assets/video-${beat.id}.mp4`;
36133
- const abs = join17(ctx.projectDir, rel);
36208
+ const abs = join18(ctx.projectDir, rel);
36134
36209
  const cache = videoCacheDescriptor({
36135
36210
  beatId: beat.id,
36136
36211
  cue: prompt3,
@@ -36142,7 +36217,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
36142
36217
  keyframe: keyframeCue
36143
36218
  });
36144
36219
  const metadataPath = assetMetadataPath("video", beat.id);
36145
- if (existsSync22(abs) && !ctx.force) {
36220
+ if (existsSync23(abs) && !ctx.force) {
36146
36221
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
36147
36222
  if (!metadata || isFreshCanonicalAsset({
36148
36223
  projectDir: ctx.projectDir,
@@ -36165,10 +36240,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
36165
36240
  };
36166
36241
  }
36167
36242
  }
36168
- const cacheAbs = join17(ctx.projectDir, cache.path);
36169
- if (existsSync22(cacheAbs) && !ctx.force) {
36170
- await mkdir13(dirname11(abs), { recursive: true });
36171
- await copyFile3(cacheAbs, abs);
36243
+ const cacheAbs = join18(ctx.projectDir, cache.path);
36244
+ if (existsSync23(cacheAbs) && !ctx.force) {
36245
+ await mkdir14(dirname11(abs), { recursive: true });
36246
+ await copyFile4(cacheAbs, abs);
36172
36247
  await writeAssetMetadata({
36173
36248
  projectDir: ctx.projectDir,
36174
36249
  kind: "video",
@@ -36197,7 +36272,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
36197
36272
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
36198
36273
  return { status: "skipped" };
36199
36274
  }
36200
- const keyframeImageAbs = keyframeImageRel ? join17(ctx.projectDir, keyframeImageRel) : void 0;
36275
+ const keyframeImageAbs = keyframeImageRel ? join18(ctx.projectDir, keyframeImageRel) : void 0;
36201
36276
  loadSceneBuildEnv(ctx.projectDir);
36202
36277
  let job;
36203
36278
  const recordJob = async (provider, submitted) => {
@@ -36275,10 +36350,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
36275
36350
  return { status: "failed", error: fallbackError };
36276
36351
  }
36277
36352
  }
36278
- if (result.status === "completed" && existsSync22(abs)) {
36353
+ if (result.status === "completed" && existsSync23(abs)) {
36279
36354
  await settleJob("completed");
36280
- await mkdir13(dirname11(cacheAbs), { recursive: true });
36281
- await copyFile3(abs, cacheAbs);
36355
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
36356
+ await copyFile4(abs, cacheAbs);
36282
36357
  await writeAssetMetadata({
36283
36358
  projectDir: ctx.projectDir,
36284
36359
  kind: "video",
@@ -36327,7 +36402,7 @@ async function dispatchMusic(beat, ctx) {
36327
36402
  const prompt3 = stringOrUndefined4(beat.cues?.music);
36328
36403
  if (!prompt3) return { status: "no-cue" };
36329
36404
  const rel = `assets/music-${beat.id}.mp3`;
36330
- const abs = join17(ctx.projectDir, rel);
36405
+ const abs = join18(ctx.projectDir, rel);
36331
36406
  const duration = normalizeMusicDuration(beat.duration);
36332
36407
  const cache = musicCacheDescriptor({
36333
36408
  beatId: beat.id,
@@ -36336,7 +36411,7 @@ async function dispatchMusic(beat, ctx) {
36336
36411
  duration
36337
36412
  });
36338
36413
  const metadataPath = assetMetadataPath("music", beat.id);
36339
- if (existsSync22(abs) && !ctx.force) {
36414
+ if (existsSync23(abs) && !ctx.force) {
36340
36415
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
36341
36416
  if (!metadata || isFreshCanonicalAsset({
36342
36417
  projectDir: ctx.projectDir,
@@ -36360,10 +36435,10 @@ async function dispatchMusic(beat, ctx) {
36360
36435
  };
36361
36436
  }
36362
36437
  }
36363
- const cacheAbs = join17(ctx.projectDir, cache.path);
36364
- if (existsSync22(cacheAbs) && !ctx.force) {
36365
- await mkdir13(dirname11(abs), { recursive: true });
36366
- await copyFile3(cacheAbs, abs);
36438
+ const cacheAbs = join18(ctx.projectDir, cache.path);
36439
+ if (existsSync23(cacheAbs) && !ctx.force) {
36440
+ await mkdir14(dirname11(abs), { recursive: true });
36441
+ await copyFile4(cacheAbs, abs);
36367
36442
  await writeAssetMetadata({
36368
36443
  projectDir: ctx.projectDir,
36369
36444
  kind: "music",
@@ -36435,13 +36510,13 @@ async function dispatchMusic(beat, ctx) {
36435
36510
  metadataPath
36436
36511
  };
36437
36512
  }
36438
- if (!existsSync22(abs)) {
36513
+ if (!existsSync23(abs)) {
36439
36514
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
36440
36515
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
36441
36516
  return { status: "failed", error };
36442
36517
  }
36443
- await mkdir13(dirname11(cacheAbs), { recursive: true });
36444
- await copyFile3(abs, cacheAbs);
36518
+ await mkdir14(dirname11(cacheAbs), { recursive: true });
36519
+ await copyFile4(abs, cacheAbs);
36445
36520
  await writeAssetMetadata({
36446
36521
  projectDir: ctx.projectDir,
36447
36522
  kind: "music",
@@ -36471,12 +36546,12 @@ async function dispatchMusic(beat, ctx) {
36471
36546
  };
36472
36547
  }
36473
36548
  function loadSceneBuildEnv(projectDir) {
36474
- (0, import_dotenv3.config)({ path: join17(projectDir, ".env"), quiet: true });
36549
+ (0, import_dotenv3.config)({ path: join18(projectDir, ".env"), quiet: true });
36475
36550
  (0, import_dotenv3.config)({ path: resolve28(process.cwd(), ".env"), quiet: true });
36476
36551
  let dir = process.cwd();
36477
36552
  while (dir !== dirname11(dir)) {
36478
- if (existsSync22(join17(dir, "pnpm-workspace.yaml"))) {
36479
- (0, import_dotenv3.config)({ path: join17(dir, ".env"), quiet: true });
36553
+ if (existsSync23(join18(dir, "pnpm-workspace.yaml"))) {
36554
+ (0, import_dotenv3.config)({ path: join18(dir, ".env"), quiet: true });
36480
36555
  return;
36481
36556
  }
36482
36557
  dir = dirname11(dir);
@@ -36693,7 +36768,7 @@ function mergeBeatOutcomes(base, updates) {
36693
36768
  }
36694
36769
  function firstExisting3(projectDir, relPaths) {
36695
36770
  for (const rel of relPaths) {
36696
- if (existsSync22(join17(projectDir, rel))) return rel;
36771
+ if (existsSync23(join18(projectDir, rel))) return rel;
36697
36772
  }
36698
36773
  return null;
36699
36774
  }
@@ -36718,7 +36793,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
36718
36793
  return getApiKeyFromConfig(videoProviderConfigKey(provider), { cwd: projectDir });
36719
36794
  }
36720
36795
  async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
36721
- const reportPath = join17(projectDir, "build-report.json");
36796
+ const reportPath = join18(projectDir, "build-report.json");
36722
36797
  for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
36723
36798
  if (source.stageReports) {
36724
36799
  for (const report of Object.values(source.stageReports)) {
@@ -36881,7 +36956,7 @@ function normalizeReportDuration(value) {
36881
36956
  }
36882
36957
  function buildReportComposition(projectDir, result, beatId) {
36883
36958
  const path = `compositions/scene-${beatId}.html`;
36884
- const exists = existsSync22(join17(projectDir, path));
36959
+ const exists = existsSync23(join18(projectDir, path));
36885
36960
  if (result.stageReports?.compose.status === "failed") return { path, exists, status: "failed" };
36886
36961
  if (result.phase === "needs-author" && !exists) return { path, exists, status: "needs-author" };
36887
36962
  if (exists) return { path, exists, status: "exists" };
@@ -36928,8 +37003,8 @@ function summarizeBuildBeats(projectDir, beats) {
36928
37003
  let compositionsReady = 0;
36929
37004
  for (const beat of beats) {
36930
37005
  if (beatAssetsReady(beat)) assetsReady += 1;
36931
- const compositionPath = join17(projectDir, "compositions", `scene-${beat.beatId}.html`);
36932
- if (existsSync22(compositionPath)) {
37006
+ const compositionPath = join18(projectDir, "compositions", `scene-${beat.beatId}.html`);
37007
+ if (existsSync23(compositionPath)) {
36933
37008
  compositionsReady += 1;
36934
37009
  } else {
36935
37010
  needsAuthor.push(beat.beatId);
@@ -37182,15 +37257,15 @@ __export(ai_audio_exports, {
37182
37257
  executeTranscribe: () => executeTranscribe,
37183
37258
  executeVoiceClone: () => executeVoiceClone
37184
37259
  });
37185
- import { resolve as resolve30, dirname as dirname12, basename as basename6, extname as extname5, join as join18 } from "node:path";
37186
- import { mkdir as mkdir14, mkdtemp, readFile as readFile21, rm as rm4, writeFile as writeFile20 } from "node:fs/promises";
37260
+ import { resolve as resolve30, dirname as dirname12, basename as basename7, extname as extname5, join as join19 } from "node:path";
37261
+ import { mkdir as mkdir15, mkdtemp, readFile as readFile22, rm as rm4, writeFile as writeFile20 } from "node:fs/promises";
37187
37262
  import { tmpdir } from "node:os";
37188
- import { existsSync as existsSync23 } from "node:fs";
37263
+ import { existsSync as existsSync24 } from "node:fs";
37189
37264
  async function executeTranscribe(options) {
37190
37265
  const { audioPath, language, output, format, apiKey } = options;
37191
37266
  try {
37192
37267
  const absPath = resolve30(process.cwd(), audioPath);
37193
- if (!existsSync23(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37268
+ if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37194
37269
  const key = await getConfiguredApiKey("OPENAI_API_KEY", apiKey);
37195
37270
  if (!key) return { success: false, error: "OPENAI_API_KEY required", errorKind: "auth" };
37196
37271
  const result = await transcribeAudioFile(absPath, { apiKey: key, language });
@@ -37220,10 +37295,10 @@ async function executeIsolate(options) {
37220
37295
  const { audioPath, output = "vocals.mp3", apiKey } = options;
37221
37296
  try {
37222
37297
  const absPath = resolve30(process.cwd(), audioPath);
37223
- if (!existsSync23(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37298
+ if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37224
37299
  const key = await getConfiguredApiKey("ELEVENLABS_API_KEY", apiKey);
37225
37300
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required", errorKind: "auth" };
37226
- const audioBuffer = await readFile21(absPath);
37301
+ const audioBuffer = await readFile22(absPath);
37227
37302
  const elevenlabs = new ElevenLabsProvider();
37228
37303
  await elevenlabs.initialize({ apiKey: key });
37229
37304
  const result = await elevenlabs.isolateVocals(audioBuffer);
@@ -37248,8 +37323,8 @@ async function executeVoiceClone(options) {
37248
37323
  const audioBuffers = [];
37249
37324
  for (const samplePath of samplePaths) {
37250
37325
  const absPath = resolve30(process.cwd(), samplePath);
37251
- if (!existsSync23(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
37252
- const buffer = await readFile21(absPath);
37326
+ if (!existsSync24(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
37327
+ const buffer = await readFile22(absPath);
37253
37328
  audioBuffers.push(buffer);
37254
37329
  }
37255
37330
  const elevenlabs = new ElevenLabsProvider();
@@ -37269,16 +37344,16 @@ async function executeVoiceClone(options) {
37269
37344
  async function executeDub(options) {
37270
37345
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
37271
37346
  const absPath = resolve30(process.cwd(), mediaPath);
37272
- if (!existsSync23(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37347
+ if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
37273
37348
  const openaiKey = await getConfiguredApiKey("OPENAI_API_KEY");
37274
37349
  if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription", errorKind: "auth" };
37275
- const workDir = await mkdtemp(join18(tmpdir(), "vibe-dub-"));
37350
+ const workDir = await mkdtemp(join19(tmpdir(), "vibe-dub-"));
37276
37351
  try {
37277
37352
  const ext = extname5(absPath).toLowerCase();
37278
37353
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
37279
37354
  let audioPath = absPath;
37280
37355
  if (isVideo) {
37281
- audioPath = join18(workDir, "source.mp3");
37356
+ audioPath = join19(workDir, "source.mp3");
37282
37357
  await execSafe("ffmpeg", ["-y", "-loglevel", "error", "-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "4", audioPath]);
37283
37358
  }
37284
37359
  const transcript = await transcribeAudioFile(audioPath, { apiKey: openaiKey, language: source });
@@ -37319,7 +37394,7 @@ async function executeDub(options) {
37319
37394
  if (!spoken.success || !spoken.audioBuffer) {
37320
37395
  return { success: false, error: `Speech failed on segment ${i + 1}: ${spoken.error}`, errorKind: spoken.errorKind };
37321
37396
  }
37322
- const clipPath = join18(workDir, `line-${i}.${tts.audioExtension}`);
37397
+ const clipPath = join19(workDir, `line-${i}.${tts.audioExtension}`);
37323
37398
  await writeFile20(clipPath, spoken.audioBuffer);
37324
37399
  const slot = Math.max(0.1, (report[i + 1]?.startTime ?? seg.endTime) - seg.startTime);
37325
37400
  const length = await getAudioDuration(clipPath);
@@ -37328,7 +37403,7 @@ async function executeDub(options) {
37328
37403
  const outputExt = isVideo ? ".mp3" : ext || ".mp3";
37329
37404
  const finalOutputPath = resolve30(
37330
37405
  process.cwd(),
37331
- output || resolve30(dirname12(absPath), `${basename6(absPath, extname5(absPath))}-${language}${outputExt}`)
37406
+ output || resolve30(dirname12(absPath), `${basename7(absPath, extname5(absPath))}-${language}${outputExt}`)
37332
37407
  );
37333
37408
  await mixAtOffsets(clips, finalOutputPath);
37334
37409
  return { ...base, outputPath: finalOutputPath };
@@ -37346,7 +37421,7 @@ async function mixAtOffsets(clips, outputPath) {
37346
37421
  return `[${i}:a]${tempo}adelay=${delay}|${delay}[a${i}]`;
37347
37422
  });
37348
37423
  const mix = `${clips.map((_, i) => `[a${i}]`).join("")}amix=inputs=${clips.length}:normalize=0:dropout_transition=0[out]`;
37349
- await mkdir14(dirname12(outputPath), { recursive: true });
37424
+ await mkdir15(dirname12(outputPath), { recursive: true });
37350
37425
  await execSafe("ffmpeg", ["-y", "-loglevel", "error", ...inputs, "-filter_complex", [...chains, mix].join(";"), "-map", "[out]", outputPath]);
37351
37426
  }
37352
37427
  async function executeDuck(options) {
@@ -37363,9 +37438,9 @@ async function executeDuck(options) {
37363
37438
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
37364
37439
  const absMusicPath = resolve30(process.cwd(), musicPath);
37365
37440
  const absVoicePath = resolve30(process.cwd(), voicePath);
37366
- if (!existsSync23(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
37367
- if (!existsSync23(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
37368
- const defaultOutput = resolve30(dirname12(absMusicPath), `${basename6(absMusicPath, extname5(absMusicPath))}-ducked${extname5(absMusicPath)}`);
37441
+ if (!existsSync24(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
37442
+ if (!existsSync24(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
37443
+ const defaultOutput = resolve30(dirname12(absMusicPath), `${basename7(absMusicPath, extname5(absMusicPath))}-ducked${extname5(absMusicPath)}`);
37369
37444
  const outputPath = resolve30(process.cwd(), output || defaultOutput);
37370
37445
  const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
37371
37446
  await execSafe("ffmpeg", [
@@ -37378,7 +37453,7 @@ async function executeDuck(options) {
37378
37453
  "-y",
37379
37454
  outputPath
37380
37455
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
37381
- if (!existsSync23(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
37456
+ if (!existsSync24(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
37382
37457
  return { success: true, outputPath };
37383
37458
  } catch (error) {
37384
37459
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -37401,8 +37476,8 @@ var init_ai_audio = __esm({
37401
37476
  });
37402
37477
 
37403
37478
  // src/commands/_shared/edit/silence-cut.ts
37404
- import { existsSync as existsSync24 } from "node:fs";
37405
- import { readFile as readFile22 } from "node:fs/promises";
37479
+ import { existsSync as existsSync25 } from "node:fs";
37480
+ import { readFile as readFile23 } from "node:fs/promises";
37406
37481
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
37407
37482
  const totalDuration = await getVideoDuration(videoPath);
37408
37483
  const { stdout, stderr } = await execSafe(
@@ -37452,7 +37527,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
37452
37527
  }
37453
37528
  const gemini = new GeminiProvider();
37454
37529
  await gemini.initialize({ apiKey: geminiApiKey });
37455
- const videoBuffer = await readFile22(videoPath);
37530
+ const videoBuffer = await readFile23(videoPath);
37456
37531
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
37457
37532
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
37458
37533
 
@@ -37528,7 +37603,7 @@ async function executeSilenceCut(options) {
37528
37603
  analyzeOnly = false,
37529
37604
  useGemini = false
37530
37605
  } = options;
37531
- if (!existsSync24(videoPath)) {
37606
+ if (!existsSync25(videoPath)) {
37532
37607
  return { success: false, error: `Video not found: ${videoPath}` };
37533
37608
  }
37534
37609
  if (!commandExists("ffmpeg")) {
@@ -37636,9 +37711,9 @@ var init_silence_cut = __esm({
37636
37711
  });
37637
37712
 
37638
37713
  // src/commands/_shared/edit/jump-cut.ts
37639
- import { existsSync as existsSync25 } from "node:fs";
37640
- import { writeFile as writeFile21, mkdir as mkdir15 } from "node:fs/promises";
37641
- import { join as join19 } from "node:path";
37714
+ import { existsSync as existsSync26 } from "node:fs";
37715
+ import { writeFile as writeFile21, mkdir as mkdir16 } from "node:fs/promises";
37716
+ import { join as join20 } from "node:path";
37642
37717
  async function transcribeWithWords(audioPath, apiKey, language) {
37643
37718
  const result = await transcribeAudioFile(audioPath, { apiKey, language, granularity: "word" });
37644
37719
  if (result.status !== "completed") {
@@ -37681,7 +37756,7 @@ async function executeJumpCut(options) {
37681
37756
  analyzeOnly = false,
37682
37757
  apiKey
37683
37758
  } = options;
37684
- if (!existsSync25(videoPath)) {
37759
+ if (!existsSync26(videoPath)) {
37685
37760
  return { success: false, error: `Video not found: ${videoPath}` };
37686
37761
  }
37687
37762
  if (!commandExists("ffmpeg")) {
@@ -37699,8 +37774,8 @@ async function executeJumpCut(options) {
37699
37774
  }
37700
37775
  try {
37701
37776
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
37702
- await mkdir15(tmpDir, { recursive: true });
37703
- const audioPath = join19(tmpDir, "audio.wav");
37777
+ await mkdir16(tmpDir, { recursive: true });
37778
+ const audioPath = join20(tmpDir, "audio.wav");
37704
37779
  try {
37705
37780
  await execSafe(
37706
37781
  "ffmpeg",
@@ -37754,7 +37829,7 @@ async function executeJumpCut(options) {
37754
37829
  const segmentPaths = [];
37755
37830
  for (let i = 0; i < segments.length; i++) {
37756
37831
  const seg = segments[i];
37757
- const segPath = join19(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37832
+ const segPath = join20(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37758
37833
  const duration = seg.end - seg.start;
37759
37834
  await execSafe(
37760
37835
  "ffmpeg",
@@ -37777,7 +37852,7 @@ async function executeJumpCut(options) {
37777
37852
  segmentPaths.push(segPath);
37778
37853
  }
37779
37854
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
37780
- const listPath = join19(tmpDir, "concat.txt");
37855
+ const listPath = join20(tmpDir, "concat.txt");
37781
37856
  await writeFile21(listPath, concatList);
37782
37857
  await execSafe(
37783
37858
  "ffmpeg",
@@ -37883,9 +37958,9 @@ __export(remotion_exports, {
37883
37958
  wrapComponentWithImage: () => wrapComponentWithImage,
37884
37959
  wrapComponentWithVideo: () => wrapComponentWithVideo
37885
37960
  });
37886
- import { writeFile as writeFile22, mkdir as mkdir16, rm as rm5, copyFile as copyFile4 } from "node:fs/promises";
37887
- import { existsSync as existsSync26 } from "node:fs";
37888
- import { join as join20 } from "node:path";
37961
+ import { writeFile as writeFile22, mkdir as mkdir17, rm as rm5, copyFile as copyFile5 } from "node:fs/promises";
37962
+ import { existsSync as existsSync27 } from "node:fs";
37963
+ import { join as join21 } from "node:path";
37889
37964
  import { tmpdir as tmpdir2 } from "node:os";
37890
37965
  import { createHash as createHash4 } from "node:crypto";
37891
37966
  async function ensureRemotionInstalled() {
@@ -37902,8 +37977,8 @@ async function ensureRemotionInstalled() {
37902
37977
  }
37903
37978
  }
37904
37979
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
37905
- const dir = join20(tmpdir2(), `vibe_motion_${Date.now()}`);
37906
- await mkdir16(dir, { recursive: true });
37980
+ const dir = join21(tmpdir2(), `vibe_motion_${Date.now()}`);
37981
+ await mkdir17(dir, { recursive: true });
37907
37982
  const deps = {
37908
37983
  remotion: REMOTION_VERSION,
37909
37984
  "@remotion/cli": REMOTION_VERSION,
@@ -37921,7 +37996,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37921
37996
  dependencies: deps
37922
37997
  };
37923
37998
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
37924
- await writeFile22(join20(dir, "package.json"), packageJsonStr);
37999
+ await writeFile22(join21(dir, "package.json"), packageJsonStr);
37925
38000
  const tsconfig = {
37926
38001
  compilerOptions: {
37927
38002
  target: "ES2020",
@@ -37933,8 +38008,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37933
38008
  skipLibCheck: true
37934
38009
  }
37935
38010
  };
37936
- await writeFile22(join20(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37937
- await writeFile22(join20(dir, "Component.tsx"), componentCode);
38011
+ await writeFile22(join21(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
38012
+ await writeFile22(join21(dir, "Component.tsx"), componentCode);
37938
38013
  const rootCode = `import { registerRoot, Composition } from "remotion";
37939
38014
  import { ${componentName} } from "./Component";
37940
38015
 
@@ -37953,27 +38028,27 @@ const Root = () => {
37953
38028
 
37954
38029
  registerRoot(Root);
37955
38030
  `;
37956
- await writeFile22(join20(dir, "Root.tsx"), rootCode);
38031
+ await writeFile22(join21(dir, "Root.tsx"), rootCode);
37957
38032
  const depsHash = createHash4("md5").update(packageJsonStr).digest("hex").slice(0, 12);
37958
- const cacheMarker = join20(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
37959
- const cachedModules = join20(REMOTION_CACHE_DIR, "node_modules");
37960
- if (existsSync26(cacheMarker) && existsSync26(cachedModules)) {
38033
+ const cacheMarker = join21(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
38034
+ const cachedModules = join21(REMOTION_CACHE_DIR, "node_modules");
38035
+ if (existsSync27(cacheMarker) && existsSync27(cachedModules)) {
37961
38036
  const { symlink } = await import("node:fs/promises");
37962
38037
  try {
37963
- await symlink(cachedModules, join20(dir, "node_modules"), "dir");
38038
+ await symlink(cachedModules, join21(dir, "node_modules"), "dir");
37964
38039
  } catch {
37965
38040
  await this_npmInstall(dir);
37966
38041
  }
37967
38042
  } else {
37968
38043
  await this_npmInstall(dir);
37969
38044
  try {
37970
- await mkdir16(REMOTION_CACHE_DIR, { recursive: true });
37971
- await writeFile22(join20(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37972
- if (existsSync26(join20(dir, "node_modules"))) {
38045
+ await mkdir17(REMOTION_CACHE_DIR, { recursive: true });
38046
+ await writeFile22(join21(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
38047
+ if (existsSync27(join21(dir, "node_modules"))) {
37973
38048
  const { rename: rename5, symlink } = await import("node:fs/promises");
37974
- await rename5(join20(dir, "node_modules"), cachedModules).catch(() => {
38049
+ await rename5(join21(dir, "node_modules"), cachedModules).catch(() => {
37975
38050
  });
37976
- await symlink(cachedModules, join20(dir, "node_modules"), "dir").catch(() => {
38051
+ await symlink(cachedModules, join21(dir, "node_modules"), "dir").catch(() => {
37977
38052
  });
37978
38053
  await writeFile22(cacheMarker, depsHash);
37979
38054
  }
@@ -38010,7 +38085,7 @@ async function renderMotion(options) {
38010
38085
  }
38011
38086
  );
38012
38087
  try {
38013
- const entryPoint = join20(dir, "Root.tsx");
38088
+ const entryPoint = join21(dir, "Root.tsx");
38014
38089
  const { execFile: execFile3 } = await import("node:child_process");
38015
38090
  const { promisify: promisify3 } = await import("node:util");
38016
38091
  const execFileAsync3 = promisify3(execFile3);
@@ -38124,10 +38199,10 @@ async function renderWithEmbeddedImage(options) {
38124
38199
  }
38125
38200
  );
38126
38201
  try {
38127
- const publicDir = join20(dir, "public");
38128
- await mkdir16(publicDir, { recursive: true });
38129
- await copyFile4(options.imagePath, join20(publicDir, options.imageFileName));
38130
- const entryPoint = join20(dir, "Root.tsx");
38202
+ const publicDir = join21(dir, "public");
38203
+ await mkdir17(publicDir, { recursive: true });
38204
+ await copyFile5(options.imagePath, join21(publicDir, options.imageFileName));
38205
+ const entryPoint = join21(dir, "Root.tsx");
38131
38206
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
38132
38207
  const { execFile: execFileImg } = await import("node:child_process");
38133
38208
  const { promisify: promisifyImg } = await import("node:util");
@@ -38188,10 +38263,10 @@ async function renderWithEmbeddedVideo(options) {
38188
38263
  }
38189
38264
  );
38190
38265
  try {
38191
- const publicDir = join20(dir, "public");
38192
- await mkdir16(publicDir, { recursive: true });
38193
- await copyFile4(options.videoPath, join20(publicDir, options.videoFileName));
38194
- const entryPoint = join20(dir, "Root.tsx");
38266
+ const publicDir = join21(dir, "public");
38267
+ await mkdir17(publicDir, { recursive: true });
38268
+ await copyFile5(options.videoPath, join21(publicDir, options.videoFileName));
38269
+ const entryPoint = join21(dir, "Root.tsx");
38195
38270
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
38196
38271
  const { execFile: execFileVid } = await import("node:child_process");
38197
38272
  const { promisify: promisifyVid } = await import("node:util");
@@ -38607,14 +38682,14 @@ var init_remotion = __esm({
38607
38682
  "use strict";
38608
38683
  init_exec_safe();
38609
38684
  REMOTION_VERSION = "4.0.447";
38610
- REMOTION_CACHE_DIR = join20(tmpdir2(), "vibe_remotion_cache");
38685
+ REMOTION_CACHE_DIR = join21(tmpdir2(), "vibe_remotion_cache");
38611
38686
  }
38612
38687
  });
38613
38688
 
38614
38689
  // src/commands/_shared/edit/caption.ts
38615
- import { existsSync as existsSync27 } from "node:fs";
38616
- import { writeFile as writeFile23, mkdir as mkdir17 } from "node:fs/promises";
38617
- import { dirname as dirname13, basename as basename7, extname as extname6, join as join21 } from "node:path";
38690
+ import { existsSync as existsSync28 } from "node:fs";
38691
+ import { writeFile as writeFile23, mkdir as mkdir18 } from "node:fs/promises";
38692
+ import { dirname as dirname13, basename as basename8, extname as extname6, join as join22 } from "node:path";
38618
38693
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
38619
38694
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
38620
38695
  const marginV = position === "center" ? 0 : 30;
@@ -38642,7 +38717,7 @@ async function executeCaption(options) {
38642
38717
  position = "bottom",
38643
38718
  apiKey
38644
38719
  } = options;
38645
- if (!existsSync27(videoPath)) {
38720
+ if (!existsSync28(videoPath)) {
38646
38721
  return { success: false, error: `Video not found: ${videoPath}` };
38647
38722
  }
38648
38723
  if (!commandExists("ffmpeg")) {
@@ -38660,9 +38735,9 @@ async function executeCaption(options) {
38660
38735
  }
38661
38736
  try {
38662
38737
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
38663
- await mkdir17(tmpDir, { recursive: true });
38664
- const audioPath = join21(tmpDir, "audio.wav");
38665
- const srtPath = join21(tmpDir, "captions.srt");
38738
+ await mkdir18(tmpDir, { recursive: true });
38739
+ const audioPath = join22(tmpDir, "audio.wav");
38740
+ const srtPath = join22(tmpDir, "captions.srt");
38666
38741
  try {
38667
38742
  await execSafe(
38668
38743
  "ffmpeg",
@@ -38727,9 +38802,9 @@ async function executeCaption(options) {
38727
38802
  const remotionErr = await ensureRemotionInstalled2();
38728
38803
  if (remotionErr) {
38729
38804
  const outputDir2 = dirname13(outputPath);
38730
- const outputSrtPath2 = join21(
38805
+ const outputSrtPath2 = join22(
38731
38806
  outputDir2,
38732
- basename7(outputPath, extname6(outputPath)) + ".srt"
38807
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38733
38808
  );
38734
38809
  await writeFile23(outputSrtPath2, srtContent);
38735
38810
  return { success: false, error: `${remotionErr}
@@ -38766,9 +38841,9 @@ SRT saved to: ${outputSrtPath2}` };
38766
38841
  });
38767
38842
  if (!renderResult.success) {
38768
38843
  const outputDir2 = dirname13(outputPath);
38769
- const outputSrtPath2 = join21(
38844
+ const outputSrtPath2 = join22(
38770
38845
  outputDir2,
38771
- basename7(outputPath, extname6(outputPath)) + ".srt"
38846
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38772
38847
  );
38773
38848
  await writeFile23(outputSrtPath2, srtContent);
38774
38849
  return {
@@ -38779,9 +38854,9 @@ SRT saved to: ${outputSrtPath2}`
38779
38854
  }
38780
38855
  }
38781
38856
  const outputDir = dirname13(outputPath);
38782
- const outputSrtPath = join21(
38857
+ const outputSrtPath = join22(
38783
38858
  outputDir,
38784
- basename7(outputPath, extname6(outputPath)) + ".srt"
38859
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38785
38860
  );
38786
38861
  await writeFile23(outputSrtPath, srtContent);
38787
38862
  return {
@@ -38816,10 +38891,10 @@ var init_caption = __esm({
38816
38891
  });
38817
38892
 
38818
38893
  // src/commands/_shared/edit/noise-reduce.ts
38819
- import { existsSync as existsSync28 } from "node:fs";
38894
+ import { existsSync as existsSync29 } from "node:fs";
38820
38895
  async function executeNoiseReduce(options) {
38821
38896
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
38822
- if (!existsSync28(inputPath)) {
38897
+ if (!existsSync29(inputPath)) {
38823
38898
  return { success: false, error: `File not found: ${inputPath}` };
38824
38899
  }
38825
38900
  if (!commandExists("ffmpeg")) {
@@ -38876,7 +38951,7 @@ var init_noise_reduce = __esm({
38876
38951
  });
38877
38952
 
38878
38953
  // src/commands/_shared/edit/fade.ts
38879
- import { existsSync as existsSync29 } from "node:fs";
38954
+ import { existsSync as existsSync30 } from "node:fs";
38880
38955
  async function executeFade(options) {
38881
38956
  const {
38882
38957
  videoPath,
@@ -38886,7 +38961,7 @@ async function executeFade(options) {
38886
38961
  audioOnly = false,
38887
38962
  videoOnly = false
38888
38963
  } = options;
38889
- if (!existsSync29(videoPath)) {
38964
+ if (!existsSync30(videoPath)) {
38890
38965
  return { success: false, error: `Video not found: ${videoPath}` };
38891
38966
  }
38892
38967
  if (!commandExists("ffmpeg")) {
@@ -38953,8 +39028,8 @@ var init_fade = __esm({
38953
39028
  });
38954
39029
 
38955
39030
  // src/commands/_shared/edit/translate-srt.ts
38956
- import { existsSync as existsSync30 } from "node:fs";
38957
- import { readFile as readFile23, writeFile as writeFile24 } from "node:fs/promises";
39031
+ import { existsSync as existsSync31 } from "node:fs";
39032
+ import { readFile as readFile24, writeFile as writeFile24 } from "node:fs/promises";
38958
39033
  async function executeTranslateSrt(options) {
38959
39034
  const {
38960
39035
  srtPath,
@@ -38964,11 +39039,11 @@ async function executeTranslateSrt(options) {
38964
39039
  sourceLanguage,
38965
39040
  apiKey
38966
39041
  } = options;
38967
- if (!existsSync30(srtPath)) {
39042
+ if (!existsSync31(srtPath)) {
38968
39043
  return { success: false, error: `SRT file not found: ${srtPath}` };
38969
39044
  }
38970
39045
  try {
38971
- const srtContent = await readFile23(srtPath, "utf-8");
39046
+ const srtContent = await readFile24(srtPath, "utf-8");
38972
39047
  const segments = parseSRT(srtContent);
38973
39048
  if (segments.length === 0) {
38974
39049
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -39008,7 +39083,7 @@ var init_translate_srt = __esm({
39008
39083
  });
39009
39084
 
39010
39085
  // src/commands/_shared/edit/text-overlay.ts
39011
- import { existsSync as existsSync31 } from "node:fs";
39086
+ import { existsSync as existsSync32 } from "node:fs";
39012
39087
  import { resolve as resolve31 } from "node:path";
39013
39088
  function detectSystemFont() {
39014
39089
  const platform = process.platform;
@@ -39019,7 +39094,7 @@ function detectSystemFont() {
39019
39094
  "/Library/Fonts/Arial.ttf"
39020
39095
  ];
39021
39096
  for (const f of candidates) {
39022
- if (existsSync31(f)) return f;
39097
+ if (existsSync32(f)) return f;
39023
39098
  }
39024
39099
  } else if (platform === "linux") {
39025
39100
  const candidates = [
@@ -39028,7 +39103,7 @@ function detectSystemFont() {
39028
39103
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
39029
39104
  ];
39030
39105
  for (const f of candidates) {
39031
- if (existsSync31(f)) return f;
39106
+ if (existsSync32(f)) return f;
39032
39107
  }
39033
39108
  } else if (platform === "win32") {
39034
39109
  const candidates = [
@@ -39036,7 +39111,7 @@ function detectSystemFont() {
39036
39111
  "C:\\Windows\\Fonts\\segoeui.ttf"
39037
39112
  ];
39038
39113
  for (const f of candidates) {
39039
- if (existsSync31(f)) return f;
39114
+ if (existsSync32(f)) return f;
39040
39115
  }
39041
39116
  }
39042
39117
  return null;
@@ -39057,7 +39132,7 @@ async function applyTextOverlays(options) {
39057
39132
  }
39058
39133
  const absVideoPath = resolve31(process.cwd(), videoPath);
39059
39134
  const absOutputPath = resolve31(process.cwd(), outputPath);
39060
- if (!existsSync31(absVideoPath)) {
39135
+ if (!existsSync32(absVideoPath)) {
39061
39136
  return { success: false, error: `Video not found: ${absVideoPath}` };
39062
39137
  }
39063
39138
  if (!commandExists("ffmpeg")) {
@@ -39288,8 +39363,8 @@ var init_ai_helpers = __esm({
39288
39363
  });
39289
39364
 
39290
39365
  // src/commands/ai-edit-cli.ts
39291
- import { resolve as resolve32, extname as extname7, basename as basename8 } from "node:path";
39292
- import { existsSync as existsSync32 } from "node:fs";
39366
+ import { resolve as resolve32, extname as extname7, basename as basename9 } from "node:path";
39367
+ import { existsSync as existsSync33 } from "node:fs";
39293
39368
  function registerEditCommands(aiCommand) {
39294
39369
  aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "(deprecated - use `vibe detect silence`) Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
39295
39370
  Examples:
@@ -39306,14 +39381,14 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
39306
39381
  validateOutputPath(options.output);
39307
39382
  }
39308
39383
  const absVideoPath = resolve32(process.cwd(), videoPath);
39309
- if (!existsSync32(absVideoPath)) {
39384
+ if (!existsSync33(absVideoPath)) {
39310
39385
  exitWithError(notFoundError(absVideoPath));
39311
39386
  }
39312
39387
  if (!commandExists("ffmpeg")) {
39313
39388
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
39314
39389
  }
39315
39390
  const ext = extname7(videoPath);
39316
- const name = basename8(videoPath, ext);
39391
+ const name = basename9(videoPath, ext);
39317
39392
  const outputPath = options.output || `${name}-cut${ext}`;
39318
39393
  const useGemini = options.useGemini || false;
39319
39394
  if (options.analyzeOnly) {
@@ -39414,7 +39489,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39414
39489
  validateOutputPath(options.output);
39415
39490
  }
39416
39491
  const absVideoPath = resolve32(process.cwd(), videoPath);
39417
- if (!existsSync32(absVideoPath)) {
39492
+ if (!existsSync33(absVideoPath)) {
39418
39493
  exitWithError(notFoundError(absVideoPath));
39419
39494
  }
39420
39495
  if (!commandExists("ffmpeg")) {
@@ -39443,7 +39518,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39443
39518
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39444
39519
  }
39445
39520
  const ext = extname7(videoPath);
39446
- const name = basename8(videoPath, ext);
39521
+ const name = basename9(videoPath, ext);
39447
39522
  const outputPath = options.output || `${name}-captioned${ext}`;
39448
39523
  const spinner2 = ora("Starting caption process...").start();
39449
39524
  const result = await executeCaption({
@@ -39495,7 +39570,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39495
39570
  validateOutputPath(options.output);
39496
39571
  }
39497
39572
  const absInputPath = resolve32(process.cwd(), inputPath);
39498
- if (!existsSync32(absInputPath)) {
39573
+ if (!existsSync33(absInputPath)) {
39499
39574
  exitWithError(notFoundError(absInputPath));
39500
39575
  }
39501
39576
  if (!commandExists("ffmpeg")) {
@@ -39517,7 +39592,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39517
39592
  return;
39518
39593
  }
39519
39594
  const ext = extname7(inputPath);
39520
- const name = basename8(inputPath, ext);
39595
+ const name = basename9(inputPath, ext);
39521
39596
  const outputPath = options.output || `${name}-denoised${ext}`;
39522
39597
  const spinner2 = ora("Applying noise reduction...").start();
39523
39598
  const result = await executeNoiseReduce({
@@ -39561,7 +39636,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39561
39636
  validateOutputPath(options.output);
39562
39637
  }
39563
39638
  const absVideoPath = resolve32(process.cwd(), videoPath);
39564
- if (!existsSync32(absVideoPath)) {
39639
+ if (!existsSync33(absVideoPath)) {
39565
39640
  exitWithError(notFoundError(absVideoPath));
39566
39641
  }
39567
39642
  if (!commandExists("ffmpeg")) {
@@ -39585,7 +39660,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39585
39660
  return;
39586
39661
  }
39587
39662
  const ext = extname7(videoPath);
39588
- const name = basename8(videoPath, ext);
39663
+ const name = basename9(videoPath, ext);
39589
39664
  const outputPath = options.output || `${name}-faded${ext}`;
39590
39665
  const spinner2 = ora("Applying fade effects...").start();
39591
39666
  const result = await executeFade({
@@ -39636,7 +39711,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39636
39711
  exitWithError(usageError("Target language required. Use -t or --target"));
39637
39712
  }
39638
39713
  const absSrtPath = resolve32(process.cwd(), srtPath);
39639
- if (!existsSync32(absSrtPath)) {
39714
+ if (!existsSync33(absSrtPath)) {
39640
39715
  exitWithError(notFoundError(absSrtPath));
39641
39716
  }
39642
39717
  if (options.dryRun) {
@@ -39663,7 +39738,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39663
39738
  exitWithError(authError(envKey, providerName));
39664
39739
  }
39665
39740
  const ext = extname7(srtPath);
39666
- const name = basename8(srtPath, ext);
39741
+ const name = basename9(srtPath, ext);
39667
39742
  const outputPath = options.output || `${name}-${options.target}${ext}`;
39668
39743
  const spinner2 = ora(`Translating to ${options.target}...`).start();
39669
39744
  const result = await executeTranslateSrt({
@@ -39712,7 +39787,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39712
39787
  }
39713
39788
  if (options.fillers) rejectControlChars(options.fillers);
39714
39789
  const absVideoPath = resolve32(process.cwd(), videoPath);
39715
- if (!existsSync32(absVideoPath)) {
39790
+ if (!existsSync33(absVideoPath)) {
39716
39791
  exitWithError(notFoundError(absVideoPath));
39717
39792
  }
39718
39793
  if (!commandExists("ffmpeg")) {
@@ -39741,7 +39816,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39741
39816
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39742
39817
  }
39743
39818
  const ext = extname7(videoPath);
39744
- const name = basename8(videoPath, ext);
39819
+ const name = basename9(videoPath, ext);
39745
39820
  const outputPath = options.output || `${name}-jumpcut${ext}`;
39746
39821
  const fillers = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
39747
39822
  const spinner2 = ora("Transcribing with word-level timestamps...").start();
@@ -40200,8 +40275,8 @@ var init_project_resolver = __esm({
40200
40275
  });
40201
40276
 
40202
40277
  // src/commands/_shared/execute-fill-gaps.ts
40203
- import { existsSync as existsSync33 } from "node:fs";
40204
- import { readFile as readFile24, writeFile as writeFile25, mkdir as mkdir18, rename as renameFs } from "node:fs/promises";
40278
+ import { existsSync as existsSync34 } from "node:fs";
40279
+ import { readFile as readFile25, writeFile as writeFile25, mkdir as mkdir19, rename as renameFs } from "node:fs/promises";
40205
40280
  import { resolve as resolve34, dirname as dirname14 } from "node:path";
40206
40281
  function detectVideoGaps(videoClips, totalDuration) {
40207
40282
  const gaps = [];
@@ -40258,7 +40333,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
40258
40333
  }
40259
40334
  async function uploadFrame(host, framePath) {
40260
40335
  try {
40261
- const upload = await host.uploadImage(await readFile24(framePath), {
40336
+ const upload = await host.uploadImage(await readFile25(framePath), {
40262
40337
  filename: framePath,
40263
40338
  mimeType: "image/png"
40264
40339
  });
@@ -40291,14 +40366,14 @@ async function executeFillGaps(options) {
40291
40366
  try {
40292
40367
  onProgress("Loading timeline...");
40293
40368
  const filePath = await resolveTimelineFile(options.projectPath);
40294
- if (!existsSync33(filePath)) {
40369
+ if (!existsSync34(filePath)) {
40295
40370
  return {
40296
40371
  success: false,
40297
40372
  error: `Timeline file not found: ${filePath}`,
40298
40373
  humanLines
40299
40374
  };
40300
40375
  }
40301
- const content = await readFile24(filePath, "utf-8");
40376
+ const content = await readFile25(filePath, "utf-8");
40302
40377
  const data = JSON.parse(content);
40303
40378
  const project = Project.fromJSON(data);
40304
40379
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -40418,8 +40493,8 @@ async function executeFillGaps(options) {
40418
40493
  const kling = await openVideoGenerator("kling", apiKey);
40419
40494
  const projectDir = dirname14(filePath);
40420
40495
  const footageDir = options.dir ? resolve34(process.cwd(), options.dir) : resolve34(projectDir, "footage");
40421
- if (!existsSync33(footageDir)) {
40422
- await mkdir18(footageDir, { recursive: true });
40496
+ if (!existsSync34(footageDir)) {
40497
+ await mkdir19(footageDir, { recursive: true });
40423
40498
  }
40424
40499
  humanLines.push("Generating AI Videos");
40425
40500
  let uploadHost;
@@ -40716,8 +40791,8 @@ __export(ai_motion_exports, {
40716
40791
  registerMotionCommand: () => registerMotionCommand
40717
40792
  });
40718
40793
  import { resolve as resolve35 } from "node:path";
40719
- import { existsSync as existsSync34 } from "node:fs";
40720
- import { readFile as readFile25, writeFile as writeFile26 } from "node:fs/promises";
40794
+ import { existsSync as existsSync35 } from "node:fs";
40795
+ import { readFile as readFile26, writeFile as writeFile26 } from "node:fs/promises";
40721
40796
  function resolveMotionModel(alias) {
40722
40797
  const claude = findModel("claude", "llm", alias);
40723
40798
  if (claude) return { provider: "claude", modelId: claude.id };
@@ -40771,7 +40846,7 @@ async function executeMotion(options) {
40771
40846
  };
40772
40847
  }
40773
40848
  const imagePath = resolve35(process.cwd(), options.image);
40774
- const imageBuffer = await readFile25(imagePath);
40849
+ const imageBuffer = await readFile26(imagePath);
40775
40850
  const gemini = new GeminiProvider();
40776
40851
  await gemini.initialize({ apiKey: geminiApiKey });
40777
40852
  const analysisResult = await gemini.analyzeImage(
@@ -40808,7 +40883,7 @@ Use this image analysis to inform the color palette, typography placement, and o
40808
40883
  }
40809
40884
  if (geminiApiKey) {
40810
40885
  const videoPath = resolve35(process.cwd(), options.video);
40811
- const videoBuffer = await readFile25(videoPath);
40886
+ const videoBuffer = await readFile26(videoPath);
40812
40887
  const gemini = new GeminiProvider();
40813
40888
  await gemini.initialize({ apiKey: geminiApiKey });
40814
40889
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -40839,10 +40914,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
40839
40914
  let result;
40840
40915
  if (options.fromTsx) {
40841
40916
  const tsxPath = resolve35(process.cwd(), options.fromTsx);
40842
- if (!existsSync34(tsxPath)) {
40917
+ if (!existsSync35(tsxPath)) {
40843
40918
  return { success: false, error: `TSX file not found: ${tsxPath}` };
40844
40919
  }
40845
- const existingCode = await readFile25(tsxPath, "utf-8");
40920
+ const existingCode = await readFile26(tsxPath, "utf-8");
40846
40921
  if (useGemini) {
40847
40922
  const gemini = new GeminiProvider();
40848
40923
  await gemini.initialize({ apiKey });
@@ -41132,10 +41207,9 @@ var init_ai_motion = __esm({
41132
41207
  });
41133
41208
 
41134
41209
  // src/commands/edit/lottie-overlay.ts
41135
- import { createRequire } from "node:module";
41136
- import { copyFile as copyFile5, mkdir as mkdir19, mkdtemp as mkdtemp2, readFile as readFile26, rm as rm6, writeFile as writeFile27 } from "node:fs/promises";
41210
+ import { copyFile as copyFile6, mkdir as mkdir20, mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile27 } from "node:fs/promises";
41137
41211
  import { tmpdir as tmpdir3 } from "node:os";
41138
- import { basename as basename9, extname as extname8, join as join22 } from "node:path";
41212
+ import { basename as basename10, extname as extname8, join as join23 } from "node:path";
41139
41213
  async function probeVideoStream(path) {
41140
41214
  const { stdout } = await execSafe("ffprobe", [
41141
41215
  "-v",
@@ -41162,27 +41236,6 @@ async function probeVideoStream(path) {
41162
41236
  duration: Number(info.format?.duration ?? 0)
41163
41237
  };
41164
41238
  }
41165
- async function checkLottieAsset(path) {
41166
- const bytes = await readFile26(path);
41167
- const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
41168
- if (isZip) return void 0;
41169
- try {
41170
- const json = JSON.parse(bytes.toString("utf-8"));
41171
- if (Array.isArray(json.layers)) return void 0;
41172
- } catch {
41173
- }
41174
- return `${basename9(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
41175
- }
41176
- function overlayBox(layer, position, scale) {
41177
- if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
41178
- const width = Math.max(1, Math.round(layer.width * scale));
41179
- const height = Math.max(1, Math.round(layer.height * scale));
41180
- const marginX = Math.round(layer.width * 0.04);
41181
- const marginY = Math.round(layer.height * 0.04);
41182
- const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
41183
- const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
41184
- return { left, top, width, height };
41185
- }
41186
41239
  function lottieLayerHtml(opts) {
41187
41240
  const { box } = opts;
41188
41241
  return `<!DOCTYPE html>
@@ -41195,11 +41248,8 @@ function lottieLayerHtml(opts) {
41195
41248
  </head><body>
41196
41249
  <canvas id="lottie" width="${box.width}" height="${box.height}"></canvas>
41197
41250
  <script type="module">
41198
- import { DotLottie } from "./vendor/dotlottie-web.js";
41199
- // The render server sends .wasm without application/wasm, which makes
41200
- // WebAssembly.instantiateStreaming reject it; a typed Blob URL avoids that.
41201
- const wasm = await (await fetch("./vendor/dotlottie-player.wasm")).arrayBuffer();
41202
- DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
41251
+ ${DOTLOTTIE_BROWSER_HELPERS}
41252
+ const DotLottie = await loadDotLottie("./vendor");
41203
41253
  const LOOP = ${opts.loop};
41204
41254
  const DURATION = ${opts.duration};
41205
41255
  const player = new DotLottie({
@@ -41216,12 +41266,7 @@ function lottieLayerHtml(opts) {
41216
41266
  window.__hf = {
41217
41267
  duration: DURATION,
41218
41268
  seek(t) {
41219
- const length = player.duration;
41220
- const frames = player.totalFrames;
41221
- if (!(length > 0) || !(frames > 0)) return;
41222
- const local = Math.max(0, Number(t) || 0);
41223
- const at = LOOP ? local % length : Math.min(local, length);
41224
- player.setFrame(Math.min(frames - 1, (at / length) * frames));
41269
+ setLottieTime(player, t, LOOP);
41225
41270
  },
41226
41271
  };
41227
41272
  window.__hf.seek(0);
@@ -41230,21 +41275,12 @@ function lottieLayerHtml(opts) {
41230
41275
  </body></html>
41231
41276
  `;
41232
41277
  }
41233
- function vendorPaths() {
41234
- const require2 = createRequire(import.meta.url);
41235
- const script = require2.resolve("@lottiefiles/dotlottie-web");
41236
- const dist = join22(script, "..");
41237
- return { script: join22(dist, "index.js"), wasm: join22(dist, "dotlottie-player.wasm") };
41238
- }
41239
41278
  async function renderLayer(opts) {
41240
41279
  const { dir, info, options } = opts;
41241
- const vendor = vendorPaths();
41242
- await mkdir19(join22(dir, "vendor"), { recursive: true });
41243
- await mkdir19(join22(dir, "assets"), { recursive: true });
41244
- await copyFile5(vendor.script, join22(dir, "vendor", "dotlottie-web.js"));
41245
- await copyFile5(vendor.wasm, join22(dir, "vendor", "dotlottie-player.wasm"));
41280
+ await copyDotLottieRuntime(join23(dir, "vendor"));
41281
+ await mkdir20(join23(dir, "assets"), { recursive: true });
41246
41282
  const assetName = `animation${extname8(options.assetPath).toLowerCase()}`;
41247
- await copyFile5(options.assetPath, join22(dir, "assets", assetName));
41283
+ await copyFile6(options.assetPath, join23(dir, "assets", assetName));
41248
41284
  const html = lottieLayerHtml({
41249
41285
  width: info.width,
41250
41286
  height: info.height,
@@ -41254,8 +41290,8 @@ async function renderLayer(opts) {
41254
41290
  opacity: options.opacity,
41255
41291
  loop: options.loop
41256
41292
  });
41257
- await writeFile27(join22(dir, "index.html"), html, "utf-8");
41258
- const layerPath = join22(dir, "layer.mov");
41293
+ await writeFile27(join23(dir, "index.html"), html, "utf-8");
41294
+ const layerPath = join23(dir, "layer.mov");
41259
41295
  const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await import("@hyperframes/producer");
41260
41296
  const job = createRenderJob2({
41261
41297
  fps: Math.min(60, Math.max(1, Math.round(info.fps))),
@@ -41308,7 +41344,7 @@ async function renderLottieOverlay(options) {
41308
41344
  const assetProblem = await checkLottieAsset(options.assetPath);
41309
41345
  if (assetProblem) return { success: false, error: assetProblem };
41310
41346
  const info = await probeVideoStream(options.videoPath);
41311
- const dir = await mkdtemp2(join22(tmpdir3(), "vibeframe-lottie-"));
41347
+ const dir = await mkdtemp2(join23(tmpdir3(), "vibeframe-lottie-"));
41312
41348
  try {
41313
41349
  let layerPath;
41314
41350
  try {
@@ -41318,7 +41354,7 @@ async function renderLottieOverlay(options) {
41318
41354
  if (/__hf not ready/.test(message)) {
41319
41355
  return {
41320
41356
  success: false,
41321
- error: `Could not load the Lottie animation ${basename9(options.assetPath)}.`,
41357
+ error: `Could not load the Lottie animation ${basename10(options.assetPath)}.`,
41322
41358
  suggestion: "Check that it opens in a Lottie player (https://lottiefiles.com/preview)."
41323
41359
  };
41324
41360
  }
@@ -41339,6 +41375,7 @@ var init_lottie_overlay = __esm({
41339
41375
  init_chrome();
41340
41376
  init_exec_safe();
41341
41377
  init_stdout_guard();
41378
+ init_lottie();
41342
41379
  FFMPEG_MISSING = "FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details.";
41343
41380
  }
41344
41381
  });
@@ -41349,8 +41386,8 @@ __export(motion_overlay_exports, {
41349
41386
  executeMotionOverlay: () => executeMotionOverlay,
41350
41387
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
41351
41388
  });
41352
- import { mkdir as mkdir20 } from "node:fs/promises";
41353
- import { existsSync as existsSync35 } from "node:fs";
41389
+ import { mkdir as mkdir21 } from "node:fs/promises";
41390
+ import { existsSync as existsSync36 } from "node:fs";
41354
41391
  import { dirname as dirname15, extname as extname9, resolve as resolve36 } from "node:path";
41355
41392
  function normalizeUnderstand2(value) {
41356
41393
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -41385,11 +41422,11 @@ async function executeMotionOverlay(options) {
41385
41422
  const validationError = assertValidRequest(options);
41386
41423
  if (validationError) return { success: false, error: validationError };
41387
41424
  const absVideo = resolve36(process.cwd(), options.videoPath);
41388
- if (!existsSync35(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
41425
+ if (!existsSync36(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
41389
41426
  const outputPath = options.output ? resolve36(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
41390
41427
  if (options.asset) {
41391
41428
  const absAsset = resolve36(process.cwd(), options.asset);
41392
- if (!existsSync35(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
41429
+ if (!existsSync36(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
41393
41430
  const videoDuration = await ffprobeDuration(absVideo);
41394
41431
  const start = Math.max(0, options.start ?? 0);
41395
41432
  if (start >= videoDuration) {
@@ -41397,7 +41434,7 @@ async function executeMotionOverlay(options) {
41397
41434
  }
41398
41435
  const remaining = videoDuration - start;
41399
41436
  const position = normalizePosition(options.position);
41400
- await mkdir20(dirname15(outputPath), { recursive: true });
41437
+ await mkdir21(dirname15(outputPath), { recursive: true });
41401
41438
  const result2 = await renderLottieOverlay({
41402
41439
  videoPath: absVideo,
41403
41440
  assetPath: absAsset,
@@ -41415,7 +41452,7 @@ async function executeMotionOverlay(options) {
41415
41452
  return { success: true, outputPath, provider: "lottie" };
41416
41453
  }
41417
41454
  const duration = options.duration ?? await ffprobeDuration(absVideo).catch(() => void 0);
41418
- await mkdir20(dirname15(outputPath), { recursive: true });
41455
+ await mkdir21(dirname15(outputPath), { recursive: true });
41419
41456
  const result = await executeMotion({
41420
41457
  description: options.description.trim(),
41421
41458
  video: absVideo,
@@ -41542,7 +41579,7 @@ __export(ai_image_exports, {
41542
41579
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
41543
41580
  });
41544
41581
  import { readFile as readFile27 } from "node:fs/promises";
41545
- import { existsSync as existsSync36 } from "node:fs";
41582
+ import { existsSync as existsSync37 } from "node:fs";
41546
41583
  function numberedPath(path, n) {
41547
41584
  const dot = path.lastIndexOf(".");
41548
41585
  return dot > path.lastIndexOf("/") ? `${path.slice(0, dot)}-${n}${path.slice(dot)}` : `${path}-${n}`;
@@ -41614,7 +41651,7 @@ async function executeThumbnailBestFrame(options) {
41614
41651
  model = "flash",
41615
41652
  apiKey
41616
41653
  } = options;
41617
- if (!existsSync36(videoPath)) {
41654
+ if (!existsSync37(videoPath)) {
41618
41655
  return { success: false, error: `Video not found: ${videoPath}` };
41619
41656
  }
41620
41657
  if (!commandExists("ffmpeg")) {
@@ -41648,7 +41685,7 @@ async function executeThumbnailBestFrame(options) {
41648
41685
  reason = reasonMatch[1];
41649
41686
  }
41650
41687
  await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
41651
- if (!existsSync36(outputPath)) {
41688
+ if (!existsSync37(outputPath)) {
41652
41689
  return { success: false, error: "FFmpeg failed to extract frame" };
41653
41690
  }
41654
41691
  return {
@@ -42589,7 +42626,7 @@ __export(ai_analyze_exports, {
42589
42626
  });
42590
42627
  import { readFile as readFile29 } from "node:fs/promises";
42591
42628
  import { extname as extname10, resolve as resolve39 } from "node:path";
42592
- import { existsSync as existsSync38 } from "node:fs";
42629
+ import { existsSync as existsSync39 } from "node:fs";
42593
42630
  async function executeGeminiVideo(options) {
42594
42631
  try {
42595
42632
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -42603,7 +42640,7 @@ async function executeGeminiVideo(options) {
42603
42640
  videoData = options.source;
42604
42641
  } else {
42605
42642
  const absPath = resolve39(process.cwd(), options.source);
42606
- if (!existsSync38(absPath)) {
42643
+ if (!existsSync39(absPath)) {
42607
42644
  return { success: false, error: `File not found: ${absPath}` };
42608
42645
  }
42609
42646
  videoData = await readFile29(absPath);
@@ -42671,7 +42708,7 @@ async function executeAnalyze(options) {
42671
42708
  imageBuffer = Buffer.from(await response.arrayBuffer());
42672
42709
  } else {
42673
42710
  const absPath = resolve39(process.cwd(), source);
42674
- if (!existsSync38(absPath)) {
42711
+ if (!existsSync39(absPath)) {
42675
42712
  return { success: false, error: `File not found: ${absPath}` };
42676
42713
  }
42677
42714
  imageBuffer = await readFile29(absPath);
@@ -42706,7 +42743,7 @@ async function executeAnalyze(options) {
42706
42743
  videoData = Buffer.from(await response.arrayBuffer());
42707
42744
  } else {
42708
42745
  const absPath = resolve39(process.cwd(), source);
42709
- if (!existsSync38(absPath)) {
42746
+ if (!existsSync39(absPath)) {
42710
42747
  return { success: false, error: `File not found: ${absPath}` };
42711
42748
  }
42712
42749
  videoData = await readFile29(absPath);
@@ -42752,7 +42789,7 @@ __export(ai_review_exports, {
42752
42789
  });
42753
42790
  import { readFile as readFile32, rename as rename4 } from "node:fs/promises";
42754
42791
  import { resolve as resolve42 } from "node:path";
42755
- import { existsSync as existsSync40 } from "node:fs";
42792
+ import { existsSync as existsSync41 } from "node:fs";
42756
42793
  function parseReviewFeedback(response) {
42757
42794
  let cleaned = response.trim();
42758
42795
  if (cleaned.startsWith("```json")) {
@@ -42784,7 +42821,7 @@ async function executeReview(options) {
42784
42821
  model = "flash"
42785
42822
  } = options;
42786
42823
  const absVideoPath = resolve42(process.cwd(), videoPath);
42787
- if (!existsSync40(absVideoPath)) {
42824
+ if (!existsSync41(absVideoPath)) {
42788
42825
  return { success: false, error: `Video not found: ${absVideoPath}` };
42789
42826
  }
42790
42827
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -42797,7 +42834,7 @@ async function executeReview(options) {
42797
42834
  let storyboardContext = "";
42798
42835
  if (storyboardPath) {
42799
42836
  const absStoryboardPath = resolve42(process.cwd(), storyboardPath);
42800
- if (existsSync40(absStoryboardPath)) {
42837
+ if (existsSync41(absStoryboardPath)) {
42801
42838
  const content = await readFile32(absStoryboardPath, "utf-8");
42802
42839
  storyboardContext = `
42803
42840
 
@@ -43100,10 +43137,10 @@ var init_tty = __esm({
43100
43137
  });
43101
43138
 
43102
43139
  // src/utils/audio-file.ts
43103
- import { mkdir as mkdir22, rm as rm8, writeFile as writeFile32 } from "node:fs/promises";
43140
+ import { mkdir as mkdir23, rm as rm8, writeFile as writeFile32 } from "node:fs/promises";
43104
43141
  import { dirname as dirname17, extname as extname11 } from "node:path";
43105
43142
  async function writeAudioFile(path, bytes, container) {
43106
- await mkdir22(dirname17(path), { recursive: true });
43143
+ await mkdir23(dirname17(path), { recursive: true });
43107
43144
  const wanted = extname11(path).toLowerCase();
43108
43145
  if (!CONVERTIBLE.has(wanted) || wanted === `.${container}`) {
43109
43146
  await writeFile32(path, bytes);
@@ -43218,9 +43255,9 @@ var init_speech2 = __esm({
43218
43255
  });
43219
43256
 
43220
43257
  // src/commands/generate/thumbnail.ts
43221
- import { resolve as resolve47, dirname as dirname18, basename as basename12, extname as extname12 } from "node:path";
43222
- import { existsSync as existsSync42 } from "node:fs";
43223
- import { writeFile as writeFile33, mkdir as mkdir23 } from "node:fs/promises";
43258
+ import { resolve as resolve47, dirname as dirname18, basename as basename13, extname as extname12 } from "node:path";
43259
+ import { existsSync as existsSync43 } from "node:fs";
43260
+ import { writeFile as writeFile33, mkdir as mkdir24 } from "node:fs/promises";
43224
43261
  function registerThumbnailCommand(parent) {
43225
43262
  parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("--style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", `Gemini model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").action(async (description, options) => {
43226
43263
  const startedAt = Date.now();
@@ -43231,7 +43268,7 @@ function registerThumbnailCommand(parent) {
43231
43268
  }
43232
43269
  if (options.bestFrame) {
43233
43270
  const absVideoPath = resolve47(process.cwd(), options.bestFrame);
43234
- if (!existsSync42(absVideoPath)) {
43271
+ if (!existsSync43(absVideoPath)) {
43235
43272
  exitWithError(notFoundError(absVideoPath));
43236
43273
  }
43237
43274
  if (!commandExists("ffmpeg")) {
@@ -43243,7 +43280,7 @@ function registerThumbnailCommand(parent) {
43243
43280
  );
43244
43281
  }
43245
43282
  const apiKey2 = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
43246
- const name = basename12(options.bestFrame, extname12(options.bestFrame));
43283
+ const name = basename13(options.bestFrame, extname12(options.bestFrame));
43247
43284
  const outputPath = options.output || `${name}-thumbnail.png`;
43248
43285
  const spinner3 = ora("Analyzing video for best frame...").start();
43249
43286
  const result2 = await executeThumbnailBestFrame({
@@ -43311,7 +43348,7 @@ function registerThumbnailCommand(parent) {
43311
43348
  throw new Error("No image data available");
43312
43349
  }
43313
43350
  outputPath = resolve47(process.cwd(), options.output);
43314
- await mkdir23(dirname18(outputPath), { recursive: true });
43351
+ await mkdir24(dirname18(outputPath), { recursive: true });
43315
43352
  await writeFile33(outputPath, buffer);
43316
43353
  }
43317
43354
  outputSuccess({
@@ -43342,7 +43379,7 @@ function registerThumbnailCommand(parent) {
43342
43379
  throw new Error("No image data available");
43343
43380
  }
43344
43381
  const outputPath = resolve47(process.cwd(), options.output);
43345
- await mkdir23(dirname18(outputPath), { recursive: true });
43382
+ await mkdir24(dirname18(outputPath), { recursive: true });
43346
43383
  await writeFile33(outputPath, buffer);
43347
43384
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
43348
43385
  } catch {
@@ -45958,7 +45995,7 @@ __export(detect_exports, {
45958
45995
  executeDetectSilence: () => executeDetectSilence
45959
45996
  });
45960
45997
  import { readFile as readFile39, writeFile as writeFile38 } from "node:fs/promises";
45961
- import { resolve as resolve54, basename as basename14 } from "node:path";
45998
+ import { resolve as resolve54, basename as basename15 } from "node:path";
45962
45999
  async function executeDetectScenes(options) {
45963
46000
  try {
45964
46001
  if (!commandExists("ffmpeg")) {
@@ -46208,7 +46245,7 @@ var init_detect = __esm({
46208
46245
  let source = project.getSources().find((s) => s.url === absPath);
46209
46246
  if (!source) {
46210
46247
  source = project.addSource({
46211
- name: basename14(absPath),
46248
+ name: basename15(absPath),
46212
46249
  type: "video",
46213
46250
  url: absPath,
46214
46251
  duration: totalDuration
@@ -50657,8 +50694,9 @@ async function runWithMcpPromotion(work, opts) {
50657
50694
  let lastRecordWriteAt = 0;
50658
50695
  let heartbeat = null;
50659
50696
  let writes = Promise.resolve();
50697
+ let finished = false;
50660
50698
  const writeRecord = (patch) => {
50661
- if (!record) return writes;
50699
+ if (!record || finished) return writes;
50662
50700
  record = {
50663
50701
  ...record,
50664
50702
  ...patch,
@@ -50721,8 +50759,14 @@ async function runWithMcpPromotion(work, opts) {
50721
50759
  void writeRecord({ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() });
50722
50760
  }, opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS);
50723
50761
  heartbeat.unref?.();
50762
+ const finish = (patch) => {
50763
+ if (heartbeat) clearInterval(heartbeat);
50764
+ const terminal2 = writeRecord(patch);
50765
+ finished = true;
50766
+ return terminal2;
50767
+ };
50724
50768
  void workPromise.then(
50725
- (result) => writeRecord(
50769
+ (result) => finish(
50726
50770
  result.success ? {
50727
50771
  status: "completed",
50728
50772
  progress: 100,
@@ -50734,13 +50778,11 @@ async function runWithMcpPromotion(work, opts) {
50734
50778
  resultPayload: result.data
50735
50779
  }
50736
50780
  ),
50737
- (error) => writeRecord({
50781
+ (error) => finish({
50738
50782
  status: "failed",
50739
50783
  error: error instanceof Error ? error.message : String(error)
50740
50784
  })
50741
- ).finally(() => {
50742
- if (heartbeat) clearInterval(heartbeat);
50743
- });
50785
+ );
50744
50786
  return {
50745
50787
  success: true,
50746
50788
  data: {
@@ -51022,10 +51064,11 @@ init_scene_project();
51022
51064
  init_project_config();
51023
51065
  init_cost_tier();
51024
51066
  import { resolve as resolve17, relative as relative5, dirname as dirname6 } from "node:path";
51025
- import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile14, access as access4, copyFile as copyFile2 } from "node:fs/promises";
51026
- import { existsSync as existsSync13 } from "node:fs";
51067
+ import { mkdir as mkdir10, readFile as readFile16, writeFile as writeFile14, access as access4, copyFile as copyFile3 } from "node:fs/promises";
51068
+ import { existsSync as existsSync14 } from "node:fs";
51027
51069
 
51028
51070
  // src/commands/_shared/scene-html-emit.ts
51071
+ init_lottie();
51029
51072
  var SCENE_PRESETS = [
51030
51073
  "simple",
51031
51074
  "announcement",
@@ -51033,14 +51076,7 @@ var SCENE_PRESETS = [
51033
51076
  "kinetic-type",
51034
51077
  "product-shot"
51035
51078
  ];
51036
- var LOTTIE_POSITIONS = [
51037
- "full",
51038
- "center",
51039
- "top-left",
51040
- "top-right",
51041
- "bottom-left",
51042
- "bottom-right"
51043
- ];
51079
+ var LOTTIE_RUNTIME_DIR = "assets/vendor/dotlottie";
51044
51080
  var GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
51045
51081
  function esc(text) {
51046
51082
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -51339,55 +51375,45 @@ function buildPreset(input) {
51339
51375
  }
51340
51376
  }
51341
51377
  }
51342
- var DOTLOTTIE_WC_VERSION = "0.9.12";
51343
- var DOTLOTTIE_WEB_VERSION = "0.71.0";
51344
- var DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
51345
- var DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
51346
- function lottieOverlayStyle(input) {
51378
+ function jsString(value) {
51379
+ return JSON.stringify(value).replace(/</g, "\\u003c");
51380
+ }
51381
+ function buildLottieOverlay(input, canvas, sceneId, duration) {
51347
51382
  const pos = input.position ?? "full";
51348
- const rawScale = input.scale ?? (pos === "full" ? 1 : 0.25);
51349
- const scale = Math.max(0.01, Math.min(2, rawScale));
51383
+ const scale = Math.max(0.01, Math.min(2, input.scale ?? (pos === "full" ? 1 : 0.25)));
51350
51384
  const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
51351
- const base = [
51352
- "position:absolute",
51353
- "pointer-events:none",
51354
- `opacity:${opacity}`
51355
- ];
51356
- if (pos === "full") {
51357
- base.push("inset:0", "width:100%", "height:100%");
51358
- } else {
51359
- const pct = scale * 100;
51360
- base.push(`width:${pct}%`, `height:${pct}%`);
51361
- switch (pos) {
51362
- case "center":
51363
- base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
51364
- break;
51365
- case "top-left":
51366
- base.push("top:4%", "left:4%");
51367
- break;
51368
- case "top-right":
51369
- base.push("top:4%", "right:4%");
51370
- break;
51371
- case "bottom-left":
51372
- base.push("bottom:4%", "left:4%");
51373
- break;
51374
- case "bottom-right":
51375
- base.push("bottom:4%", "right:4%");
51376
- break;
51377
- }
51378
- }
51379
- return base.join(";");
51380
- }
51381
- function buildLottieOverlay(input, sceneId) {
51382
- const loop = input.loop ?? true ? " loop" : "";
51383
- const style = lottieOverlayStyle(input);
51385
+ const box = overlayBox(canvas, pos, scale);
51384
51386
  const overlayId = `lottie-overlay-${sceneId}`;
51385
- const markup = `<dotlottie-wc id="${overlayId}" src="${esc(input.src)}" autoplay${loop} style="${style}"></dotlottie-wc>`;
51386
- const script = `<script type="module">
51387
- import { setWasmUrl } from "${DOTLOTTIE_WC_CDN}";
51388
- setWasmUrl("${DOTLOTTIE_WASM_CDN}");
51389
- </script>`;
51390
- return { markup, script };
51387
+ const markup = `<canvas id="${overlayId}" class="lottie-overlay" width="${box.width}" height="${box.height}" style="position:absolute;left:${box.left}px;top:${box.top}px;width:${box.width}px;height:${box.height}px;pointer-events:none;opacity:${opacity}"></canvas>`;
51388
+ const register = `${DOTLOTTIE_BROWSER_HELPERS}
51389
+ (async () => {
51390
+ const DotLottie = await loadDotLottie(${jsString(LOTTIE_RUNTIME_DIR)});
51391
+ const player = new DotLottie({
51392
+ canvas: document.getElementById(${jsString(overlayId)}),
51393
+ src: ${jsString(input.src)},
51394
+ autoplay: false,
51395
+ loop: false,
51396
+ renderConfig: { devicePixelRatio: 1, autoResize: false, freezeOnOffscreen: false },
51397
+ });
51398
+ await new Promise((resolve, reject) => {
51399
+ player.addEventListener("load", resolve);
51400
+ player.addEventListener("loadError", (event) => reject(event && event.error));
51401
+ });
51402
+ let sceneTime = 0;
51403
+ const lottieClock = {
51404
+ time(t) {
51405
+ if (t === undefined) return sceneTime;
51406
+ sceneTime = t;
51407
+ setLottieTime(player, t, ${input.loop ?? true});
51408
+ },
51409
+ };
51410
+ tl.fromTo(lottieClock, { time: 0 }, { time: ${duration}, duration: ${duration}, ease: "none" }, 0);
51411
+ })()
51412
+ .catch((error) => console.error("[vibe] Lottie overlay failed to load:", ${jsString(input.src)}, error))
51413
+ .finally(() => {
51414
+ window.__timelines["${sceneId}"] = tl;
51415
+ });`;
51416
+ return { markup, register };
51391
51417
  }
51392
51418
  function emitSceneHtml(input) {
51393
51419
  if (input.duration <= 0) {
@@ -51410,11 +51436,10 @@ function emitSceneHtml(input) {
51410
51436
  data-volume="1"
51411
51437
  ></audio>
51412
51438
  ` : "";
51413
- const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
51439
+ const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, input, id, dur) : null;
51414
51440
  const lottieMarkup = lottieLayer ? `
51415
51441
  ${lottieLayer.markup}` : "";
51416
- const lottieScript = lottieLayer ? `
51417
- ${lottieLayer.script}` : "";
51442
+ const register = lottieLayer?.register ?? `window.__timelines["${id}"] = tl;`;
51418
51443
  return `<template id="${id}-template">
51419
51444
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
51420
51445
  <style>
@@ -51423,14 +51448,14 @@ function emitSceneHtml(input) {
51423
51448
 
51424
51449
  ${parts.body}${lottieMarkup}
51425
51450
  ${audioBlock}
51426
- <script src="${GSAP_CDN}"></script>${lottieScript}
51451
+ <script src="${GSAP_CDN}"></script>
51427
51452
  <script>
51428
51453
  window.__timelines = window.__timelines || {};
51429
51454
  const tl = gsap.timeline({ paused: true });
51430
51455
 
51431
51456
  ${parts.timeline}
51432
51457
 
51433
- window.__timelines["${id}"] = tl;
51458
+ ${register}
51434
51459
  </script>
51435
51460
  </div>
51436
51461
  </template>
@@ -51488,6 +51513,7 @@ function readRootDims(rootHtml) {
51488
51513
  }
51489
51514
 
51490
51515
  // src/commands/scene.ts
51516
+ init_lottie();
51491
51517
  init_scene_lint();
51492
51518
  init_output();
51493
51519
  init_api_key();
@@ -51503,9 +51529,9 @@ init_scene_lint();
51503
51529
  init_storyboard_parse();
51504
51530
  init_storyboard_source();
51505
51531
  init_root_sync();
51506
- import { existsSync as existsSync11 } from "node:fs";
51507
- import { mkdir as mkdir8, writeFile as writeFile12 } from "node:fs/promises";
51508
- import { join as join10, resolve as resolve14 } from "node:path";
51532
+ import { existsSync as existsSync12 } from "node:fs";
51533
+ import { mkdir as mkdir9, writeFile as writeFile12 } from "node:fs/promises";
51534
+ import { join as join11, resolve as resolve14 } from "node:path";
51509
51535
  async function executeSceneSubmit(opts) {
51510
51536
  const projectDir = resolve14(opts.projectDir);
51511
51537
  const beatId = opts.beatId.trim();
@@ -51519,8 +51545,8 @@ async function executeSceneSubmit(opts) {
51519
51545
  warnings: [],
51520
51546
  retryWith: []
51521
51547
  };
51522
- const storyboardPath = join10(projectDir, "STORYBOARD.md");
51523
- if (!existsSync11(storyboardPath)) {
51548
+ const storyboardPath = join11(projectDir, "STORYBOARD.md");
51549
+ if (!existsSync12(storyboardPath)) {
51524
51550
  return {
51525
51551
  ...base,
51526
51552
  success: false,
@@ -51597,8 +51623,8 @@ async function executeSceneSubmit(opts) {
51597
51623
  if (opts.validateOnly) {
51598
51624
  return { ...base, success: true };
51599
51625
  }
51600
- const absPath = join10(projectDir, scenePath);
51601
- await mkdir8(join10(projectDir, "compositions"), { recursive: true });
51626
+ const absPath = join11(projectDir, scenePath);
51627
+ await mkdir9(join11(projectDir, "compositions"), { recursive: true });
51602
51628
  await writeFile12(absPath, html.endsWith("\n") ? html : `${html}
51603
51629
  `, "utf-8");
51604
51630
  return {
@@ -51793,7 +51819,7 @@ sceneCommand.command("submit").description(
51793
51819
  }
51794
51820
  html = Buffer.concat(chunks).toString("utf-8");
51795
51821
  } else if (options.file) {
51796
- html = await readFile15(resolve17(String(options.file)), "utf-8");
51822
+ html = await readFile16(resolve17(String(options.file)), "utf-8");
51797
51823
  } else {
51798
51824
  exitWithError(
51799
51825
  usageError(
@@ -52123,8 +52149,8 @@ function readClipStart(rootHtml, beatId) {
52123
52149
  async function resolveNarrationText(value) {
52124
52150
  if (!value) return void 0;
52125
52151
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
52126
- if (looksLikePath && existsSync13(value)) {
52127
- return (await readFile15(value, "utf-8")).trim();
52152
+ if (looksLikePath && existsSync14(value)) {
52153
+ return (await readFile16(value, "utf-8")).trim();
52128
52154
  }
52129
52155
  return value.trim();
52130
52156
  }
@@ -52161,7 +52187,7 @@ async function executeSceneAdd(opts) {
52161
52187
  if (!opts.force && await pathExists2(scenePath)) {
52162
52188
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
52163
52189
  }
52164
- const rootHtmlBefore = await readFile15(rootPath, "utf-8");
52190
+ const rootHtmlBefore = await readFile16(rootPath, "utf-8");
52165
52191
  let dims = readRootDims(rootHtmlBefore);
52166
52192
  if (!dims) {
52167
52193
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -52185,8 +52211,8 @@ async function executeSceneAdd(opts) {
52185
52211
  }
52186
52212
  audioRelPath = `assets/narration-${id}.${ext}`;
52187
52213
  audioAbsPath = resolve17(projectDir, audioRelPath);
52188
- await mkdir9(dirname6(audioAbsPath), { recursive: true });
52189
- await copyFile2(sourceAbs, audioAbsPath);
52214
+ await mkdir10(dirname6(audioAbsPath), { recursive: true });
52215
+ await copyFile3(sourceAbs, audioAbsPath);
52190
52216
  try {
52191
52217
  narrationDuration = await getAudioDuration(audioAbsPath);
52192
52218
  } catch {
@@ -52218,7 +52244,7 @@ async function executeSceneAdd(opts) {
52218
52244
  }
52219
52245
  audioRelPath = `assets/narration-${id}.${resolution.audioExtension}`;
52220
52246
  audioAbsPath = resolve17(projectDir, audioRelPath);
52221
- await mkdir9(dirname6(audioAbsPath), { recursive: true });
52247
+ await mkdir10(dirname6(audioAbsPath), { recursive: true });
52222
52248
  await writeFile14(audioAbsPath, tts.audioBuffer);
52223
52249
  try {
52224
52250
  narrationDuration = await getAudioDuration(audioAbsPath);
@@ -52276,7 +52302,7 @@ async function executeSceneAdd(opts) {
52276
52302
  });
52277
52303
  imageRelPath = `assets/scene-${id}.png`;
52278
52304
  imageAbsPath = resolve17(projectDir, imageRelPath);
52279
- await mkdir9(dirname6(imageAbsPath), { recursive: true });
52305
+ await mkdir10(dirname6(imageAbsPath), { recursive: true });
52280
52306
  await writeImageFile(imageAbsPath, Buffer.from(result.images[0].bytes));
52281
52307
  } catch (error) {
52282
52308
  return errResult(`${label} image generation failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -52294,10 +52320,13 @@ async function executeSceneAdd(opts) {
52294
52320
  if (ext !== "json" && ext !== "lottie") {
52295
52321
  return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
52296
52322
  }
52323
+ const assetProblem = await checkLottieAsset(sourceAbs);
52324
+ if (assetProblem) return errResult(assetProblem);
52297
52325
  lottieRelPath = `assets/lottie-${id}.${ext}`;
52298
52326
  lottieAbsPath = resolve17(projectDir, lottieRelPath);
52299
- await mkdir9(dirname6(lottieAbsPath), { recursive: true });
52300
- await copyFile2(sourceAbs, lottieAbsPath);
52327
+ await mkdir10(dirname6(lottieAbsPath), { recursive: true });
52328
+ await copyFile3(sourceAbs, lottieAbsPath);
52329
+ await copyDotLottieRuntime(resolve17(projectDir, LOTTIE_RUNTIME_DIR));
52301
52330
  lottieInput = {
52302
52331
  src: lottieRelPath,
52303
52332
  position: opts.lottie.position,
@@ -52341,7 +52370,7 @@ async function executeSceneAdd(opts) {
52341
52370
  storyboardAction = synced.action;
52342
52371
  } else {
52343
52372
  opts.onProgress?.("Syncing STORYBOARD.md...");
52344
- const before = await readFile15(storyboardAbsPath, "utf-8");
52373
+ const before = await readFile16(storyboardAbsPath, "utf-8");
52345
52374
  const synced = upsertStoryboardBeat(before, upsert);
52346
52375
  if (synced.markdown !== before) {
52347
52376
  await writeFile14(storyboardAbsPath, synced.markdown, "utf-8");
@@ -52366,7 +52395,7 @@ async function executeSceneAdd(opts) {
52366
52395
  transcript: transcriptWords,
52367
52396
  lottie: lottieInput
52368
52397
  });
52369
- await mkdir9(dirname6(scenePath), { recursive: true });
52398
+ await mkdir10(dirname6(scenePath), { recursive: true });
52370
52399
  await writeFile14(scenePath, sceneHtml, "utf-8");
52371
52400
  opts.onProgress?.("Updating root composition...");
52372
52401
  let start = 0;
@@ -52378,7 +52407,7 @@ async function executeSceneAdd(opts) {
52378
52407
  });
52379
52408
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
52380
52409
  if (blockingIssue) return errResult(blockingIssue.message);
52381
- const rootHtmlAfterSync = await readFile15(rootPath, "utf-8");
52410
+ const rootHtmlAfterSync = await readFile16(rootPath, "utf-8");
52382
52411
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
52383
52412
  } else {
52384
52413
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -52557,7 +52586,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
52557
52586
  const root = rootArg || "index.html";
52558
52587
  const project = projectArg || ".";
52559
52588
  const defaultProject = project === ".";
52560
- if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync13(resolve17(root, "index.html"))) {
52589
+ if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync14(resolve17(root, "index.html"))) {
52561
52590
  return { projectDir: resolve17(root), rootRel: "index.html" };
52562
52591
  }
52563
52592
  return { projectDir: resolve17(project), rootRel: root };
@@ -53419,9 +53448,9 @@ init_ai_edit();
53419
53448
  init_api_key();
53420
53449
  init_exec_safe();
53421
53450
  init_remotion();
53422
- import { resolve as resolve38, dirname as dirname16, basename as basename10 } from "node:path";
53423
- import { writeFile as writeFile29, mkdir as mkdir21, rm as rm7 } from "node:fs/promises";
53424
- import { existsSync as existsSync37 } from "node:fs";
53451
+ import { resolve as resolve38, dirname as dirname16, basename as basename11 } from "node:path";
53452
+ import { writeFile as writeFile29, mkdir as mkdir22, rm as rm7 } from "node:fs/promises";
53453
+ import { existsSync as existsSync38 } from "node:fs";
53425
53454
  import { tmpdir as tmpdir4 } from "node:os";
53426
53455
  var ASS_STYLES = ["karaoke-sweep", "typewriter"];
53427
53456
  var SENTENCE_BREAKS = /[.!?]/;
@@ -53572,7 +53601,7 @@ async function executeAnimatedCaption(options) {
53572
53601
  }
53573
53602
  const effectiveFontSize = fontSize ?? Math.round(height * 0.04);
53574
53603
  const tmpAudioDir = resolve38(tmpdir4(), `vf-ac-${Date.now()}`);
53575
- await mkdir21(tmpAudioDir, { recursive: true });
53604
+ await mkdir22(tmpAudioDir, { recursive: true });
53576
53605
  const audioPath = resolve38(tmpAudioDir, "audio.wav");
53577
53606
  await execSafe("ffmpeg", [
53578
53607
  "-y",
@@ -53602,8 +53631,8 @@ async function executeAnimatedCaption(options) {
53602
53631
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
53603
53632
  const absOutputPath = resolve38(process.cwd(), outputPath);
53604
53633
  const outDir = dirname16(absOutputPath);
53605
- if (!existsSync37(outDir)) {
53606
- await mkdir21(outDir, { recursive: true });
53634
+ if (!existsSync38(outDir)) {
53635
+ await mkdir22(outDir, { recursive: true });
53607
53636
  }
53608
53637
  if (tier === "ass") {
53609
53638
  const assContent = generateASS(
@@ -53634,7 +53663,7 @@ async function executeAnimatedCaption(options) {
53634
53663
  width,
53635
53664
  height,
53636
53665
  fps: videoFps,
53637
- videoFileName: basename10(videoPath)
53666
+ videoFileName: basename11(videoPath)
53638
53667
  });
53639
53668
  const durationInFrames = Math.ceil(duration * videoFps);
53640
53669
  const renderResult = await renderWithEmbeddedVideo({
@@ -53645,7 +53674,7 @@ async function executeAnimatedCaption(options) {
53645
53674
  fps: videoFps,
53646
53675
  durationInFrames,
53647
53676
  videoPath,
53648
- videoFileName: basename10(videoPath),
53677
+ videoFileName: basename11(videoPath),
53649
53678
  outputPath: absOutputPath
53650
53679
  });
53651
53680
  if (!renderResult.success) {
@@ -54196,9 +54225,9 @@ init_project_config();
54196
54225
  init_scene_lint();
54197
54226
  init_root_sync();
54198
54227
  init_review_report();
54199
- import { existsSync as existsSync39 } from "node:fs";
54228
+ import { existsSync as existsSync40 } from "node:fs";
54200
54229
  import { readdir as readdir4, readFile as readFile31, stat as stat5 } from "node:fs/promises";
54201
- import { isAbsolute as isAbsolute6, join as join23, resolve as resolve41 } from "node:path";
54230
+ import { isAbsolute as isAbsolute6, join as join24, resolve as resolve41 } from "node:path";
54202
54231
  async function inspectProject(opts) {
54203
54232
  const projectDir = resolve41(opts.projectDir);
54204
54233
  const issues = [];
@@ -54212,7 +54241,7 @@ async function inspectProject(opts) {
54212
54241
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
54213
54242
  rootSync: { ok: null, issueCount: 0, issues: [] }
54214
54243
  };
54215
- if (!existsSync39(projectDir) || !await isDirectory(projectDir)) {
54244
+ if (!existsSync40(projectDir) || !await isDirectory(projectDir)) {
54216
54245
  issues.push({
54217
54246
  severity: "error",
54218
54247
  code: "PROJECT_NOT_FOUND",
@@ -54227,15 +54256,15 @@ async function inspectProject(opts) {
54227
54256
  );
54228
54257
  }
54229
54258
  const coreFiles = {
54230
- storyboard: join23(projectDir, "STORYBOARD.md"),
54231
- design: join23(projectDir, "DESIGN.md"),
54232
- config: join23(projectDir, "vibe.config.json"),
54233
- legacyConfig: join23(projectDir, "vibe.project.yaml"),
54234
- buildReport: join23(projectDir, "build-report.json"),
54235
- root: join23(projectDir, "index.html")
54259
+ storyboard: join24(projectDir, "STORYBOARD.md"),
54260
+ design: join24(projectDir, "DESIGN.md"),
54261
+ config: join24(projectDir, "vibe.config.json"),
54262
+ legacyConfig: join24(projectDir, "vibe.project.yaml"),
54263
+ buildReport: join24(projectDir, "build-report.json"),
54264
+ root: join24(projectDir, "index.html")
54236
54265
  };
54237
54266
  for (const [name, path] of Object.entries(coreFiles)) {
54238
- checks.files[name] = existsSync39(path);
54267
+ checks.files[name] = existsSync40(path);
54239
54268
  }
54240
54269
  let beatIds = [];
54241
54270
  let inspectedBeatIds = [];
@@ -54316,20 +54345,20 @@ async function inspectProject(opts) {
54316
54345
  });
54317
54346
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
54318
54347
  }
54319
- const compositionsDir = join23(projectDir, "compositions");
54320
- const existingComps = existsSync39(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
54348
+ const compositionsDir = join24(projectDir, "compositions");
54349
+ const existingComps = existsSync40(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
54321
54350
  checks.compositions.expected = inspectedBeatIds.length;
54322
54351
  for (const beatId of inspectedBeatIds) {
54323
54352
  const file = `scene-${beatId}.html`;
54324
54353
  if (existingComps.has(file)) {
54325
54354
  checks.compositions.found++;
54326
54355
  } else {
54327
- checks.compositions.missing.push(join23("compositions", file));
54356
+ checks.compositions.missing.push(join24("compositions", file));
54328
54357
  issues.push({
54329
54358
  severity: "error",
54330
54359
  code: "MISSING_COMPOSITION",
54331
54360
  message: `Composition for beat "${beatId}" is missing.`,
54332
- file: join23("compositions", file),
54361
+ file: join24("compositions", file),
54333
54362
  scene: beatId,
54334
54363
  beatId,
54335
54364
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -54391,7 +54420,7 @@ async function inspectProject(opts) {
54391
54420
  try {
54392
54421
  const lint = await runProjectLint({ projectDir });
54393
54422
  const lintFiles = opts.beatId ? lint.files.filter(
54394
- (file) => file.file === "index.html" || file.file === join23("compositions", `scene-${opts.beatId}.html`)
54423
+ (file) => file.file === "index.html" || file.file === join24("compositions", `scene-${opts.beatId}.html`)
54395
54424
  ) : lint.files;
54396
54425
  const lintCounts = countLintFindings(lintFiles);
54397
54426
  checks.lint = {
@@ -54674,7 +54703,7 @@ function inspectReportedAsset(opts) {
54674
54703
  opts.checks.assets.checked++;
54675
54704
  if (isExternalRef2(opts.value)) return;
54676
54705
  const abs = isAbsolute6(opts.value) ? opts.value : resolve41(opts.projectDir, opts.value);
54677
- if (existsSync39(abs)) return;
54706
+ if (existsSync40(abs)) return;
54678
54707
  opts.checks.assets.missing.push(opts.value);
54679
54708
  opts.issues.push({
54680
54709
  severity: "warning",
@@ -54768,9 +54797,9 @@ init_exec_safe();
54768
54797
  init_project_config();
54769
54798
  init_storyboard_parse();
54770
54799
  init_review_report();
54771
- import { existsSync as existsSync41 } from "node:fs";
54800
+ import { existsSync as existsSync42 } from "node:fs";
54772
54801
  import { readdir as readdir5, readFile as readFile33, stat as stat6 } from "node:fs/promises";
54773
- import { basename as basename11, isAbsolute as isAbsolute7, join as join24, relative as relative10, resolve as resolve43 } from "node:path";
54802
+ import { basename as basename12, isAbsolute as isAbsolute7, join as join25, relative as relative10, resolve as resolve43 } from "node:path";
54774
54803
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
54775
54804
  var DEFAULT_AI_MODEL = "flash";
54776
54805
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -55271,11 +55300,11 @@ async function inspectRender(opts) {
55271
55300
  }
55272
55301
  async function renderExpectsAudio(projectDir, beatId) {
55273
55302
  const root = resolve43(projectDir);
55274
- const rootHtml = await readTextIfExists(join24(root, "index.html"));
55303
+ const rootHtml = await readTextIfExists(join25(root, "index.html"));
55275
55304
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
55276
- const buildReport = await readJsonIfExists(join24(root, "build-report.json"));
55305
+ const buildReport = await readJsonIfExists(join25(root, "build-report.json"));
55277
55306
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
55278
- const storyboard = await readTextIfExists(join24(root, "STORYBOARD.md"));
55307
+ const storyboard = await readTextIfExists(join25(root, "STORYBOARD.md"));
55279
55308
  if (!storyboard) return false;
55280
55309
  try {
55281
55310
  const parsed = parseStoryboard(storyboard);
@@ -55286,7 +55315,7 @@ async function renderExpectsAudio(projectDir, beatId) {
55286
55315
  `assets/narration-${beat.id}.wav`,
55287
55316
  `assets/music-${beat.id}.mp3`,
55288
55317
  `assets/music-${beat.id}.wav`
55289
- ].some((asset) => existsSync41(join24(root, asset)))
55318
+ ].some((asset) => existsSync42(join25(root, asset)))
55290
55319
  );
55291
55320
  } catch {
55292
55321
  return false;
@@ -55394,8 +55423,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
55394
55423
  }
55395
55424
  function renderSourceReports(projectDir, videoPath, checks) {
55396
55425
  const reports = [];
55397
- if (existsSync41(join24(projectDir, "build-report.json"))) reports.push("build-report.json");
55398
- if (existsSync41(join24(projectDir, "render-report.json"))) reports.push("render-report.json");
55426
+ if (existsSync42(join25(projectDir, "build-report.json"))) reports.push("build-report.json");
55427
+ if (existsSync42(join25(projectDir, "render-report.json"))) reports.push("render-report.json");
55399
55428
  if (videoPath) reports.push(displayPath(videoPath));
55400
55429
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
55401
55430
  reports.push("ffprobe");
@@ -55408,31 +55437,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
55408
55437
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
55409
55438
  if (explicit) {
55410
55439
  const candidate = resolve43(process.cwd(), explicit);
55411
- return existsSync41(candidate) ? candidate : null;
55440
+ return existsSync42(candidate) ? candidate : null;
55412
55441
  }
55413
- const renderReportPath = join24(projectDir, "render-report.json");
55414
- if (existsSync41(renderReportPath)) {
55442
+ const renderReportPath = join25(projectDir, "render-report.json");
55443
+ if (existsSync42(renderReportPath)) {
55415
55444
  try {
55416
55445
  const report = JSON.parse(await readFile33(renderReportPath, "utf-8"));
55417
55446
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
55418
55447
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
55419
55448
  const candidate = resolveReportedPath(projectDir, report.outputPath);
55420
- if (candidate && existsSync41(candidate)) return candidate;
55449
+ if (candidate && existsSync42(candidate)) return candidate;
55421
55450
  }
55422
55451
  } catch {
55423
55452
  }
55424
55453
  }
55425
- const reportPath = join24(projectDir, "build-report.json");
55426
- if (!beatId && existsSync41(reportPath)) {
55454
+ const reportPath = join25(projectDir, "build-report.json");
55455
+ if (!beatId && existsSync42(reportPath)) {
55427
55456
  try {
55428
55457
  const report = JSON.parse(await readFile33(reportPath, "utf-8"));
55429
55458
  const candidate = resolveReportedPath(projectDir, report.outputPath);
55430
- if (candidate && existsSync41(candidate)) return candidate;
55459
+ if (candidate && existsSync42(candidate)) return candidate;
55431
55460
  } catch {
55432
55461
  }
55433
55462
  }
55434
- const rendersDir = join24(projectDir, "renders");
55435
- if (!existsSync41(rendersDir)) return null;
55463
+ const rendersDir = join25(projectDir, "renders");
55464
+ if (!existsSync42(rendersDir)) return null;
55436
55465
  const entries = await readdir5(rendersDir, { withFileTypes: true });
55437
55466
  const candidates = [];
55438
55467
  for (const entry of entries) {
@@ -55440,7 +55469,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
55440
55469
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
55441
55470
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
55442
55471
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
55443
- const full = join24(rendersDir, entry.name);
55472
+ const full = join25(rendersDir, entry.name);
55444
55473
  const info = await stat6(full);
55445
55474
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
55446
55475
  }
@@ -55458,12 +55487,12 @@ async function runAiRenderReview(projectDir, videoPath, model) {
55458
55487
  });
55459
55488
  }
55460
55489
  function resolveStoryboardPath(projectDir) {
55461
- const storyboardPath = join24(projectDir, "STORYBOARD.md");
55462
- return existsSync41(storyboardPath) ? storyboardPath : null;
55490
+ const storyboardPath = join25(projectDir, "STORYBOARD.md");
55491
+ return existsSync42(storyboardPath) ? storyboardPath : null;
55463
55492
  }
55464
55493
  async function expectedDurationFromBuildReport(projectDir, beatId) {
55465
- const reportPath = join24(projectDir, "build-report.json");
55466
- if (!existsSync41(reportPath)) return void 0;
55494
+ const reportPath = join25(projectDir, "build-report.json");
55495
+ if (!existsSync42(reportPath)) return void 0;
55467
55496
  try {
55468
55497
  const report = JSON.parse(await readFile33(reportPath, "utf-8"));
55469
55498
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
@@ -55477,8 +55506,8 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
55477
55506
  }
55478
55507
  }
55479
55508
  async function beatTimingsFromBuildReport(projectDir, beatId) {
55480
- const reportPath = join24(projectDir, "build-report.json");
55481
- if (!existsSync41(reportPath)) return [];
55509
+ const reportPath = join25(projectDir, "build-report.json");
55510
+ if (!existsSync42(reportPath)) return [];
55482
55511
  try {
55483
55512
  const report = JSON.parse(await readFile33(reportPath, "utf-8"));
55484
55513
  const source = report.beats ?? [];
@@ -55506,17 +55535,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
55506
55535
  }
55507
55536
  async function buildRenderReviewProjectContext(projectDir) {
55508
55537
  const parts = [];
55509
- const storyboard = await readTextIfExists(join24(projectDir, "STORYBOARD.md"));
55538
+ const storyboard = await readTextIfExists(join25(projectDir, "STORYBOARD.md"));
55510
55539
  if (storyboard) {
55511
55540
  parts.push(`STORYBOARD.md:
55512
55541
  ${truncateText(storyboard, 4e3)}`);
55513
55542
  parts.push(`Storyboard beat cue summary:
55514
55543
  ${storyboardBeatCueSummary(storyboard)}`);
55515
55544
  }
55516
- const design = await readTextIfExists(join24(projectDir, "DESIGN.md"));
55545
+ const design = await readTextIfExists(join25(projectDir, "DESIGN.md"));
55517
55546
  if (design) parts.push(`DESIGN.md:
55518
55547
  ${truncateText(design, 4e3)}`);
55519
- const buildReport = await readTextIfExists(join24(projectDir, "build-report.json"));
55548
+ const buildReport = await readTextIfExists(join25(projectDir, "build-report.json"));
55520
55549
  if (buildReport) parts.push(`build-report.json:
55521
55550
  ${truncateText(buildReport, 6e3)}`);
55522
55551
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -55544,7 +55573,7 @@ function storyboardBeatCueSummary(storyboard) {
55544
55573
  }
55545
55574
  }
55546
55575
  async function readTextIfExists(path) {
55547
- if (!existsSync41(path)) return null;
55576
+ if (!existsSync42(path)) return null;
55548
55577
  try {
55549
55578
  return await readFile33(path, "utf-8");
55550
55579
  } catch {
@@ -55644,7 +55673,7 @@ function aspectRatio(aspect) {
55644
55673
  return w / h;
55645
55674
  }
55646
55675
  function displayPath(path) {
55647
- return relative10(process.cwd(), path) || basename11(path);
55676
+ return relative10(process.cwd(), path) || basename12(path);
55648
55677
  }
55649
55678
  function toSnakeCase(value) {
55650
55679
  return value.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
@@ -56172,16 +56201,16 @@ var generateTools = [
56172
56201
  ];
56173
56202
 
56174
56203
  // src/tools/manifest/storyboard.ts
56175
- import { existsSync as existsSync44 } from "node:fs";
56204
+ import { existsSync as existsSync45 } from "node:fs";
56176
56205
  import { readFile as readFile36, writeFile as writeFile36 } from "node:fs/promises";
56177
56206
  import { resolve as resolve51 } from "node:path";
56178
56207
  init_storyboard_parse();
56179
56208
  init_storyboard_edit();
56180
56209
 
56181
56210
  // src/commands/_shared/storyboard-revise.ts
56182
- import { existsSync as existsSync43 } from "node:fs";
56211
+ import { existsSync as existsSync44 } from "node:fs";
56183
56212
  import { readFile as readFile35, writeFile as writeFile35 } from "node:fs/promises";
56184
- import { join as join25, resolve as resolve50 } from "node:path";
56213
+ import { join as join26, resolve as resolve50 } from "node:path";
56185
56214
 
56186
56215
  // src/agent/adapters/index.ts
56187
56216
  init_openai2();
@@ -56234,9 +56263,9 @@ init_storyboard_source();
56234
56263
  init_storyboard_edit();
56235
56264
  async function executeStoryboardRevision(opts) {
56236
56265
  const projectDir = resolve50(opts.projectDir);
56237
- const storyboardPath = join25(projectDir, "STORYBOARD.md");
56266
+ const storyboardPath = join26(projectDir, "STORYBOARD.md");
56238
56267
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
56239
- if (!existsSync43(storyboardPath)) {
56268
+ if (!existsSync44(storyboardPath)) {
56240
56269
  return failureResult({
56241
56270
  projectDir,
56242
56271
  storyboardPath,
@@ -56477,16 +56506,16 @@ function diffChangedBeats(before, after) {
56477
56506
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
56478
56507
  }
56479
56508
  async function readRevisionContext(projectDir) {
56480
- const designMd = await readOptionalText(join25(projectDir, "DESIGN.md"));
56481
- const buildReportJson = await readOptionalJsonText(join25(projectDir, "build-report.json"));
56509
+ const designMd = await readOptionalText(join26(projectDir, "DESIGN.md"));
56510
+ const buildReportJson = await readOptionalJsonText(join26(projectDir, "build-report.json"));
56482
56511
  return { designMd, buildReportJson };
56483
56512
  }
56484
56513
  async function readOptionalText(path) {
56485
- if (!existsSync43(path)) return null;
56514
+ if (!existsSync44(path)) return null;
56486
56515
  return readFile35(path, "utf-8");
56487
56516
  }
56488
56517
  async function readOptionalJsonText(path) {
56489
- if (!existsSync43(path)) return null;
56518
+ if (!existsSync44(path)) return null;
56490
56519
  try {
56491
56520
  const parsed = JSON.parse(await readFile35(path, "utf-8"));
56492
56521
  return JSON.stringify(parsed, null, 2);
@@ -56534,7 +56563,7 @@ var projectDirSchema = external_exports.object({
56534
56563
  });
56535
56564
  async function readStoryboard(projectDir) {
56536
56565
  const path = resolve51(projectDir, "STORYBOARD.md");
56537
- if (!existsSync44(path)) return null;
56566
+ if (!existsSync45(path)) return null;
56538
56567
  return { path, markdown: await readFile36(path, "utf-8") };
56539
56568
  }
56540
56569
  var storyboardListTool = defineTool({
@@ -56792,9 +56821,9 @@ var storyboardTools = [
56792
56821
  ];
56793
56822
 
56794
56823
  // src/tools/manifest/design.ts
56795
- import { existsSync as existsSync45 } from "node:fs";
56824
+ import { existsSync as existsSync46 } from "node:fs";
56796
56825
  import { readFile as readFile37 } from "node:fs/promises";
56797
- import { resolve as resolve52, join as join26 } from "node:path";
56826
+ import { resolve as resolve52, join as join27 } from "node:path";
56798
56827
  init_design_parse();
56799
56828
  var PROJECT_DIR_DESCRIPTION4 = "Project directory. Defaults to the surface's cwd; in MCP hosts, relative paths resolve under the configured server workspace.";
56800
56829
  var projectDirSchema2 = external_exports.object({
@@ -56810,8 +56839,8 @@ var designValidateTool = defineTool({
56810
56839
  schema: projectDirSchema2,
56811
56840
  async execute(args, ctx) {
56812
56841
  const projectDir = args.projectDir ? resolve52(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
56813
- const path = join26(projectDir, "DESIGN.md");
56814
- if (!existsSync45(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
56842
+ const path = join27(projectDir, "DESIGN.md");
56843
+ if (!existsSync46(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
56815
56844
  const md = await readFile37(path, "utf-8");
56816
56845
  const issues = validateDesignMarkdown(md);
56817
56846
  const design = parseDesign(md);
@@ -56835,12 +56864,12 @@ var designTools = [designValidateTool];
56835
56864
  // src/tools/manifest/remix.ts
56836
56865
  import { writeFile as writeFile40 } from "node:fs/promises";
56837
56866
  import { tmpdir as tmpdir5 } from "node:os";
56838
- import { join as join27 } from "node:path";
56867
+ import { join as join28 } from "node:path";
56839
56868
 
56840
56869
  // src/commands/ai-highlights.ts
56841
- import { readFile as readFile38, writeFile as writeFile37, mkdir as mkdir24 } from "node:fs/promises";
56842
- import { resolve as resolve53, dirname as dirname19, basename as basename13, extname as extname13 } from "node:path";
56843
- import { existsSync as existsSync46 } from "node:fs";
56870
+ import { readFile as readFile38, writeFile as writeFile37, mkdir as mkdir25 } from "node:fs/promises";
56871
+ import { resolve as resolve53, dirname as dirname19, basename as basename14, extname as extname13 } from "node:path";
56872
+ import { existsSync as existsSync47 } from "node:fs";
56844
56873
  init_dist2();
56845
56874
  init_engine();
56846
56875
  init_api_key();
@@ -56872,7 +56901,7 @@ function filterHighlights(highlights, options) {
56872
56901
  async function executeHighlights(options) {
56873
56902
  try {
56874
56903
  const absPath = resolve53(process.cwd(), options.media);
56875
- if (!existsSync46(absPath)) {
56904
+ if (!existsSync47(absPath)) {
56876
56905
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
56877
56906
  }
56878
56907
  const ext = extname13(absPath).toLowerCase();
@@ -56984,7 +57013,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
56984
57013
  sourceDuration = await ffprobeDuration(absPath);
56985
57014
  }
56986
57015
  const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
56987
- if (tempAudioPath && existsSync46(tempAudioPath)) {
57016
+ if (tempAudioPath && existsSync47(tempAudioPath)) {
56988
57017
  const { unlink: unlinkFile } = await import("node:fs/promises");
56989
57018
  await unlinkFile(tempAudioPath).catch(() => {
56990
57019
  });
@@ -57030,7 +57059,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
57030
57059
  if (options.project) {
57031
57060
  const project = new Project("Highlight Reel");
57032
57061
  const source = project.addSource({
57033
- name: basename13(absPath),
57062
+ name: basename14(absPath),
57034
57063
  url: absPath,
57035
57064
  type: isVideo ? "video" : "audio",
57036
57065
  duration: sourceDuration
@@ -57071,7 +57100,7 @@ async function executeAutoShorts(options) {
57071
57100
  return { success: false, shorts: [], error: "FFmpeg not found" };
57072
57101
  }
57073
57102
  const absPath = resolve53(process.cwd(), options.video);
57074
- if (!existsSync46(absPath)) {
57103
+ if (!existsSync47(absPath)) {
57075
57104
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
57076
57105
  }
57077
57106
  const targetDuration = options.duration ?? 60;
@@ -57195,8 +57224,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
57195
57224
  };
57196
57225
  }
57197
57226
  const outputDir = options.outputDir ? resolve53(process.cwd(), options.outputDir) : dirname19(absPath);
57198
- if (options.outputDir && !existsSync46(outputDir)) {
57199
- await mkdir24(outputDir, { recursive: true });
57227
+ if (options.outputDir && !existsSync47(outputDir)) {
57228
+ await mkdir25(outputDir, { recursive: true });
57200
57229
  }
57201
57230
  const result = {
57202
57231
  success: true,
@@ -57204,7 +57233,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
57204
57233
  };
57205
57234
  for (let i = 0; i < selectedHighlights.length; i++) {
57206
57235
  const h = selectedHighlights[i];
57207
- const baseName = basename13(absPath, extname13(absPath));
57236
+ const baseName = basename14(absPath, extname13(absPath));
57208
57237
  const outputPath = resolve53(outputDir, `${baseName}-short-${i + 1}.mp4`);
57209
57238
  const { stdout: probeOut } = await execSafe("ffprobe", [
57210
57239
  "-v",
@@ -57274,8 +57303,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
57274
57303
  // src/pipeline/executor.ts
57275
57304
  var import_yaml8 = __toESM(require_dist2(), 1);
57276
57305
  import { resolve as resolve55 } from "node:path";
57277
- import { readFile as readFile40, writeFile as writeFile39, mkdir as mkdir25 } from "node:fs/promises";
57278
- import { existsSync as existsSync47 } from "node:fs";
57306
+ import { readFile as readFile40, writeFile as writeFile39, mkdir as mkdir26 } from "node:fs/promises";
57307
+ import { existsSync as existsSync48 } from "node:fs";
57279
57308
 
57280
57309
  // src/pipeline/resolver.ts
57281
57310
  function resolveStepParams(params, completedSteps) {
@@ -57673,7 +57702,7 @@ async function ensureActionsRegistered() {
57673
57702
  }
57674
57703
  async function loadPipeline(filePath) {
57675
57704
  const absPath = resolve55(process.cwd(), filePath);
57676
- if (!existsSync47(absPath)) {
57705
+ if (!existsSync48(absPath)) {
57677
57706
  throw new Error(`Pipeline file not found: ${absPath}`);
57678
57707
  }
57679
57708
  const content = await readFile40(absPath, "utf-8");
@@ -57693,13 +57722,13 @@ var CHECKPOINT_FILE = ".pipeline-state.yaml";
57693
57722
  async function executePipeline(manifest2, options = {}) {
57694
57723
  await ensureActionsRegistered();
57695
57724
  const outputDir = resolve55(process.cwd(), options.outputDir || `${manifest2.name}-output`);
57696
- await mkdir25(outputDir, { recursive: true });
57725
+ await mkdir26(outputDir, { recursive: true });
57697
57726
  const completedSteps = /* @__PURE__ */ new Map();
57698
57727
  const results = [];
57699
57728
  const startTime = Date.now();
57700
57729
  if (options.resume) {
57701
57730
  const checkpointPath = resolve55(outputDir, CHECKPOINT_FILE);
57702
- if (existsSync47(checkpointPath)) {
57731
+ if (existsSync48(checkpointPath)) {
57703
57732
  const checkpointContent = await readFile40(checkpointPath, "utf-8");
57704
57733
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
57705
57734
  for (const cs of checkpoint.completedSteps) {
@@ -57972,7 +58001,7 @@ var pipelineRunTool = defineTool({
57972
58001
  let resolvedPath = args.pipelinePath;
57973
58002
  let tempPath;
57974
58003
  if (args.pipelineYaml) {
57975
- tempPath = join27(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
58004
+ tempPath = join28(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
57976
58005
  await writeFile40(tempPath, args.pipelineYaml, "utf-8");
57977
58006
  resolvedPath = tempPath;
57978
58007
  }
@@ -58108,8 +58137,8 @@ var detectTools = [
58108
58137
  ];
58109
58138
 
58110
58139
  // src/tools/manifest/timeline.ts
58111
- import { mkdir as mkdir26, writeFile as writeFile42 } from "node:fs/promises";
58112
- import { basename as basename15, resolve as resolve56 } from "node:path";
58140
+ import { mkdir as mkdir27, writeFile as writeFile42 } from "node:fs/promises";
58141
+ import { basename as basename16, resolve as resolve56 } from "node:path";
58113
58142
  init_engine();
58114
58143
  init_project_resolver();
58115
58144
 
@@ -58156,7 +58185,7 @@ var timelineCreateTool = defineTool({
58156
58185
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
58157
58186
  }),
58158
58187
  async execute(args, ctx) {
58159
- const projectName = args.name === "." ? basename15(ctx.workingDirectory) : basename15(resolve56(ctx.workingDirectory, args.name));
58188
+ const projectName = args.name === "." ? basename16(ctx.workingDirectory) : basename16(resolve56(ctx.workingDirectory, args.name));
58160
58189
  const project = new Project(projectName);
58161
58190
  if (args.fps) project.setFrameRate(args.fps);
58162
58191
  let outputPath;
@@ -58164,7 +58193,7 @@ var timelineCreateTool = defineTool({
58164
58193
  outputPath = resolve56(ctx.workingDirectory, args.outputPath);
58165
58194
  } else {
58166
58195
  const dirPath = resolve56(ctx.workingDirectory, args.name);
58167
- await mkdir26(dirPath, { recursive: true });
58196
+ await mkdir27(dirPath, { recursive: true });
58168
58197
  outputPath = resolve56(dirPath, TIMELINE_FILENAME);
58169
58198
  }
58170
58199
  await writeFile42(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
@@ -58471,11 +58500,11 @@ init_engine();
58471
58500
  init_storyboard_parse();
58472
58501
  init_storyboard_source();
58473
58502
  import { readdir as readdir6, readFile as readFile42, stat as stat7 } from "node:fs/promises";
58474
- import { existsSync as existsSync48 } from "node:fs";
58475
- import { join as join28, resolve as resolve57 } from "node:path";
58503
+ import { existsSync as existsSync49 } from "node:fs";
58504
+ import { join as join29, resolve as resolve57 } from "node:path";
58476
58505
  async function executeProjectList(opts = {}) {
58477
58506
  const workspaceDir = resolve57(opts.workspaceDir ?? ".");
58478
- if (!existsSync48(workspaceDir)) {
58507
+ if (!existsSync49(workspaceDir)) {
58479
58508
  return {
58480
58509
  success: false,
58481
58510
  workspaceDir,
@@ -58492,11 +58521,11 @@ async function executeProjectList(opts = {}) {
58492
58521
  if (entry.name === "node_modules") continue;
58493
58522
  if (entry.name.startsWith(".")) continue;
58494
58523
  if (entry.name.startsWith("_")) {
58495
- archivedCount += await countProjects(join28(workspaceDir, entry.name));
58524
+ archivedCount += await countProjects(join29(workspaceDir, entry.name));
58496
58525
  continue;
58497
58526
  }
58498
- const projectDir = join28(workspaceDir, entry.name);
58499
- if (!existsSync48(join28(projectDir, "STORYBOARD.md"))) continue;
58527
+ const projectDir = join29(workspaceDir, entry.name);
58528
+ if (!existsSync49(join29(projectDir, "STORYBOARD.md"))) continue;
58500
58529
  projects.push(await describeProject(entry.name, projectDir));
58501
58530
  }
58502
58531
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -58506,7 +58535,7 @@ async function countProjects(dir) {
58506
58535
  try {
58507
58536
  const entries = await readdir6(dir, { withFileTypes: true });
58508
58537
  return entries.filter(
58509
- (e) => e.isDirectory() && existsSync48(join28(dir, e.name, "STORYBOARD.md"))
58538
+ (e) => e.isDirectory() && existsSync49(join29(dir, e.name, "STORYBOARD.md"))
58510
58539
  ).length;
58511
58540
  } catch {
58512
58541
  return 0;
@@ -58517,7 +58546,7 @@ async function describeProject(name, projectDir) {
58517
58546
  let storyboardDurationSec = 0;
58518
58547
  const timestamps = [];
58519
58548
  try {
58520
- const storyboardPath = join28(projectDir, "STORYBOARD.md");
58549
+ const storyboardPath = join29(projectDir, "STORYBOARD.md");
58521
58550
  const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
58522
58551
  beats = parsed.beats.length;
58523
58552
  storyboardDurationSec = Number(
@@ -58529,7 +58558,7 @@ async function describeProject(name, projectDir) {
58529
58558
  let buildStatus;
58530
58559
  let buildUpdatedAt;
58531
58560
  try {
58532
- const reportPath = join28(projectDir, "build-report.json");
58561
+ const reportPath = join29(projectDir, "build-report.json");
58533
58562
  const report = JSON.parse(await readFile42(reportPath, "utf-8"));
58534
58563
  buildStatus = report.status ?? report.phase;
58535
58564
  buildUpdatedAt = report.updatedAt;
@@ -58538,12 +58567,12 @@ async function describeProject(name, projectDir) {
58538
58567
  }
58539
58568
  let latestRender;
58540
58569
  try {
58541
- const rendersDir = join28(projectDir, "renders");
58570
+ const rendersDir = join29(projectDir, "renders");
58542
58571
  const files = await readdir6(rendersDir);
58543
58572
  let newest;
58544
58573
  for (const file of files) {
58545
58574
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
58546
- const { mtimeMs } = await stat7(join28(rendersDir, file));
58575
+ const { mtimeMs } = await stat7(join29(rendersDir, file));
58547
58576
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
58548
58577
  }
58549
58578
  if (newest) {
@@ -59343,7 +59372,7 @@ var exportTools = [exportVideoTool];
59343
59372
 
59344
59373
  // src/tools/manifest/agent-only.ts
59345
59374
  import { readFile as readFile44, writeFile as writeFile44, readdir as readdir7, stat as stat8, access as access7, unlink as unlink2 } from "node:fs/promises";
59346
- import { resolve as resolve61, join as join29, basename as basename16, extname as extname14 } from "node:path";
59375
+ import { resolve as resolve61, join as join30, basename as basename17, extname as extname14 } from "node:path";
59347
59376
  init_engine();
59348
59377
  init_exec_safe();
59349
59378
  init_project_resolver();
@@ -59421,7 +59450,7 @@ var fsListTool = defineTool({
59421
59450
  const results = [];
59422
59451
  for (const entry of entries) {
59423
59452
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
59424
- const fullPath = join29(absPath, entry.name);
59453
+ const fullPath = join30(absPath, entry.name);
59425
59454
  const stats = await stat8(fullPath);
59426
59455
  if (entry.isDirectory()) {
59427
59456
  results.push(`[DIR] ${entry.name}/`);
@@ -59534,7 +59563,7 @@ var batchImportTool = defineTool({
59534
59563
  const scanDir = async (dir) => {
59535
59564
  const entries = await readdir7(dir, { withFileTypes: true });
59536
59565
  for (const entry of entries) {
59537
- const entryPath = join29(dir, entry.name);
59566
+ const entryPath = join30(dir, entry.name);
59538
59567
  if (entry.isDirectory() && recursive) {
59539
59568
  await scanDir(entryPath);
59540
59569
  } else if (entry.isFile()) {
@@ -59551,7 +59580,7 @@ var batchImportTool = defineTool({
59551
59580
  mediaFiles.sort();
59552
59581
  const addedSources = [];
59553
59582
  for (const mediaFile of mediaFiles) {
59554
- const mediaName = basename16(mediaFile);
59583
+ const mediaName = basename17(mediaFile);
59555
59584
  const mediaType = detectMediaType(mediaFile);
59556
59585
  let duration = imageDuration;
59557
59586
  if (mediaType !== "image") {