@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.
package/dist/index.js CHANGED
@@ -10198,14 +10198,14 @@ async function loadKokoroRuntime() {
10198
10198
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
10199
10199
  try {
10200
10200
  const { readFile: readFile55 } = await import("node:fs/promises");
10201
- const { join: join35, resolve: resolve85 } = await import("node:path");
10201
+ const { join: join36, resolve: resolve85 } = await import("node:path");
10202
10202
  const { pathToFileURL } = await import("node:url");
10203
10203
  const pkgDir = resolve85(baseDir, "node_modules", "kokoro-js");
10204
- const pkg = JSON.parse(await readFile55(join35(pkgDir, "package.json"), "utf-8"));
10204
+ const pkg = JSON.parse(await readFile55(join36(pkgDir, "package.json"), "utf-8"));
10205
10205
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
10206
10206
  if (!entry)
10207
10207
  return null;
10208
- const mod = await import(pathToFileURL(join35(pkgDir, entry)).href);
10208
+ const mod = await import(pathToFileURL(join36(pkgDir, entry)).href);
10209
10209
  return mod.KokoroTTS ?? null;
10210
10210
  } catch {
10211
10211
  return null;
@@ -10216,20 +10216,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
10216
10216
  if (!base)
10217
10217
  return null;
10218
10218
  try {
10219
- const { readFile: readFile55, writeFile: writeFile53, mkdir: mkdir32 } = await import("node:fs/promises");
10220
- const { join: join35, resolve: resolve85 } = await import("node:path");
10219
+ const { readFile: readFile55, writeFile: writeFile53, mkdir: mkdir33 } = await import("node:fs/promises");
10220
+ const { join: join36, resolve: resolve85 } = await import("node:path");
10221
10221
  const { pathToFileURL } = await import("node:url");
10222
10222
  const { homedir: homedir8 } = await import("node:os");
10223
10223
  const transformersDir = resolve85(base, "node_modules", "@huggingface", "transformers");
10224
10224
  const ortDistDir = resolve85(base, "node_modules", "onnxruntime-web", "dist");
10225
- const transformers = await import(pathToFileURL(join35(transformersDir, "dist", "transformers.web.js")).href);
10226
- const cacheRoot = join35(homedir8(), ".cache", "vibeframe", "models");
10225
+ const transformers = await import(pathToFileURL(join36(transformersDir, "dist", "transformers.web.js")).href);
10226
+ const cacheRoot = join36(homedir8(), ".cache", "vibeframe", "models");
10227
10227
  transformers.env.allowLocalModels = false;
10228
10228
  transformers.env.useCustomCache = true;
10229
10229
  transformers.env.customCache = {
10230
10230
  async match(key) {
10231
10231
  try {
10232
- return new Response(await readFile55(join35(cacheRoot, encodeURIComponent(String(key)))));
10232
+ return new Response(await readFile55(join36(cacheRoot, encodeURIComponent(String(key)))));
10233
10233
  } catch {
10234
10234
  return void 0;
10235
10235
  }
@@ -10237,8 +10237,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
10237
10237
  async put(key, response) {
10238
10238
  try {
10239
10239
  const buf = Buffer.from(await response.arrayBuffer());
10240
- await mkdir32(cacheRoot, { recursive: true });
10241
- await writeFile53(join35(cacheRoot, encodeURIComponent(String(key))), buf);
10240
+ await mkdir33(cacheRoot, { recursive: true });
10241
+ await writeFile53(join36(cacheRoot, encodeURIComponent(String(key))), buf);
10242
10242
  } catch {
10243
10243
  }
10244
10244
  }
@@ -28909,6 +28909,81 @@ var init_visual_styles = __esm({
28909
28909
  }
28910
28910
  });
28911
28911
 
28912
+ // src/commands/_shared/lottie.ts
28913
+ import { existsSync as existsSync3 } from "node:fs";
28914
+ import { copyFile, mkdir as mkdir5, readFile as readFile7 } from "node:fs/promises";
28915
+ import { createRequire } from "node:module";
28916
+ import { basename as basename4, join as join2 } from "node:path";
28917
+ import { fileURLToPath } from "node:url";
28918
+ async function checkLottieAsset(path) {
28919
+ const bytes = await readFile7(path);
28920
+ const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
28921
+ if (isZip) return void 0;
28922
+ try {
28923
+ const json = JSON.parse(bytes.toString("utf-8"));
28924
+ if (Array.isArray(json.layers)) return void 0;
28925
+ } catch {
28926
+ }
28927
+ return `${basename4(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
28928
+ }
28929
+ function overlayBox(layer, position, scale) {
28930
+ if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
28931
+ const width = Math.max(1, Math.round(layer.width * scale));
28932
+ const height = Math.max(1, Math.round(layer.height * scale));
28933
+ const marginX = Math.round(layer.width * 0.04);
28934
+ const marginY = Math.round(layer.height * 0.04);
28935
+ const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
28936
+ const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
28937
+ return { left, top, width, height };
28938
+ }
28939
+ function dotLottieDist() {
28940
+ try {
28941
+ return join2(createRequire(import.meta.url).resolve("@lottiefiles/dotlottie-web"), "..");
28942
+ } catch {
28943
+ const bundled = fileURLToPath(new URL(`./${BUNDLED_DOTLOTTIE_DIR}/`, import.meta.url));
28944
+ if (existsSync3(join2(bundled, DOTLOTTIE_WASM))) return bundled;
28945
+ throw new Error("The dotLottie runtime (@lottiefiles/dotlottie-web) is not installed. Reinstall VibeFrame.");
28946
+ }
28947
+ }
28948
+ async function copyDotLottieRuntime(dir) {
28949
+ const dist = dotLottieDist();
28950
+ await mkdir5(dir, { recursive: true });
28951
+ await copyFile(join2(dist, "index.js"), join2(dir, DOTLOTTIE_SCRIPT));
28952
+ await copyFile(join2(dist, DOTLOTTIE_WASM), join2(dir, DOTLOTTIE_WASM));
28953
+ }
28954
+ var LOTTIE_POSITIONS, DOTLOTTIE_SCRIPT, DOTLOTTIE_WASM, BUNDLED_DOTLOTTIE_DIR, DOTLOTTIE_BROWSER_HELPERS;
28955
+ var init_lottie = __esm({
28956
+ "src/commands/_shared/lottie.ts"() {
28957
+ "use strict";
28958
+ LOTTIE_POSITIONS = [
28959
+ "full",
28960
+ "center",
28961
+ "top-left",
28962
+ "top-right",
28963
+ "bottom-left",
28964
+ "bottom-right"
28965
+ ];
28966
+ DOTLOTTIE_SCRIPT = "dotlottie-web.js";
28967
+ DOTLOTTIE_WASM = "dotlottie-player.wasm";
28968
+ BUNDLED_DOTLOTTIE_DIR = "dotlottie";
28969
+ DOTLOTTIE_BROWSER_HELPERS = `async function loadDotLottie(vendorDir) {
28970
+ const url = (file) => new URL(vendorDir + "/" + file, document.baseURI).href;
28971
+ const { DotLottie } = await import(url("${DOTLOTTIE_SCRIPT}"));
28972
+ const wasm = await (await fetch(url("${DOTLOTTIE_WASM}"))).arrayBuffer();
28973
+ DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
28974
+ return DotLottie;
28975
+ }
28976
+ function setLottieTime(player, t, loop) {
28977
+ const length = player.duration;
28978
+ const frames = player.totalFrames;
28979
+ if (!(length > 0) || !(frames > 0)) return;
28980
+ const local = Math.max(0, Number(t) || 0);
28981
+ const at = loop ? local % length : Math.min(local, length);
28982
+ player.setFrame(Math.min(frames - 1, (at / length) * frames));
28983
+ }`;
28984
+ }
28985
+ });
28986
+
28912
28987
  // src/commands/_shared/scene-html-emit.ts
28913
28988
  function esc(text) {
28914
28989
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -29206,51 +29281,45 @@ function buildPreset(input) {
29206
29281
  }
29207
29282
  }
29208
29283
  }
29209
- function lottieOverlayStyle(input) {
29284
+ function jsString(value) {
29285
+ return JSON.stringify(value).replace(/</g, "\\u003c");
29286
+ }
29287
+ function buildLottieOverlay(input, canvas, sceneId, duration) {
29210
29288
  const pos = input.position ?? "full";
29211
- const rawScale = input.scale ?? (pos === "full" ? 1 : 0.25);
29212
- const scale = Math.max(0.01, Math.min(2, rawScale));
29289
+ const scale = Math.max(0.01, Math.min(2, input.scale ?? (pos === "full" ? 1 : 0.25)));
29213
29290
  const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
29214
- const base = [
29215
- "position:absolute",
29216
- "pointer-events:none",
29217
- `opacity:${opacity}`
29218
- ];
29219
- if (pos === "full") {
29220
- base.push("inset:0", "width:100%", "height:100%");
29221
- } else {
29222
- const pct = scale * 100;
29223
- base.push(`width:${pct}%`, `height:${pct}%`);
29224
- switch (pos) {
29225
- case "center":
29226
- base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
29227
- break;
29228
- case "top-left":
29229
- base.push("top:4%", "left:4%");
29230
- break;
29231
- case "top-right":
29232
- base.push("top:4%", "right:4%");
29233
- break;
29234
- case "bottom-left":
29235
- base.push("bottom:4%", "left:4%");
29236
- break;
29237
- case "bottom-right":
29238
- base.push("bottom:4%", "right:4%");
29239
- break;
29240
- }
29241
- }
29242
- return base.join(";");
29243
- }
29244
- function buildLottieOverlay(input, sceneId) {
29245
- const loop = input.loop ?? true ? " loop" : "";
29246
- const style = lottieOverlayStyle(input);
29291
+ const box = overlayBox(canvas, pos, scale);
29247
29292
  const overlayId = `lottie-overlay-${sceneId}`;
29248
- const markup = `<dotlottie-wc id="${overlayId}" src="${esc(input.src)}" autoplay${loop} style="${style}"></dotlottie-wc>`;
29249
- const script = `<script type="module">
29250
- import { setWasmUrl } from "${DOTLOTTIE_WC_CDN}";
29251
- setWasmUrl("${DOTLOTTIE_WASM_CDN}");
29252
- </script>`;
29253
- return { markup, script };
29293
+ 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>`;
29294
+ const register = `${DOTLOTTIE_BROWSER_HELPERS}
29295
+ (async () => {
29296
+ const DotLottie = await loadDotLottie(${jsString(LOTTIE_RUNTIME_DIR)});
29297
+ const player = new DotLottie({
29298
+ canvas: document.getElementById(${jsString(overlayId)}),
29299
+ src: ${jsString(input.src)},
29300
+ autoplay: false,
29301
+ loop: false,
29302
+ renderConfig: { devicePixelRatio: 1, autoResize: false, freezeOnOffscreen: false },
29303
+ });
29304
+ await new Promise((resolve, reject) => {
29305
+ player.addEventListener("load", resolve);
29306
+ player.addEventListener("loadError", (event) => reject(event && event.error));
29307
+ });
29308
+ let sceneTime = 0;
29309
+ const lottieClock = {
29310
+ time(t) {
29311
+ if (t === undefined) return sceneTime;
29312
+ sceneTime = t;
29313
+ setLottieTime(player, t, ${input.loop ?? true});
29314
+ },
29315
+ };
29316
+ tl.fromTo(lottieClock, { time: 0 }, { time: ${duration}, duration: ${duration}, ease: "none" }, 0);
29317
+ })()
29318
+ .catch((error) => console.error("[vibe] Lottie overlay failed to load:", ${jsString(input.src)}, error))
29319
+ .finally(() => {
29320
+ window.__timelines["${sceneId}"] = tl;
29321
+ });`;
29322
+ return { markup, register };
29254
29323
  }
29255
29324
  function emitSceneHtml(input) {
29256
29325
  if (input.duration <= 0) {
@@ -29273,11 +29342,10 @@ function emitSceneHtml(input) {
29273
29342
  data-volume="1"
29274
29343
  ></audio>
29275
29344
  ` : "";
29276
- const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
29345
+ const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, input, id, dur) : null;
29277
29346
  const lottieMarkup = lottieLayer ? `
29278
29347
  ${lottieLayer.markup}` : "";
29279
- const lottieScript = lottieLayer ? `
29280
- ${lottieLayer.script}` : "";
29348
+ const register = lottieLayer?.register ?? `window.__timelines["${id}"] = tl;`;
29281
29349
  return `<template id="${id}-template">
29282
29350
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
29283
29351
  <style>
@@ -29286,14 +29354,14 @@ function emitSceneHtml(input) {
29286
29354
 
29287
29355
  ${parts.body}${lottieMarkup}
29288
29356
  ${audioBlock}
29289
- <script src="${GSAP_CDN}"></script>${lottieScript}
29357
+ <script src="${GSAP_CDN}"></script>
29290
29358
  <script>
29291
29359
  window.__timelines = window.__timelines || {};
29292
29360
  const tl = gsap.timeline({ paused: true });
29293
29361
 
29294
29362
  ${parts.timeline}
29295
29363
 
29296
- window.__timelines["${id}"] = tl;
29364
+ ${register}
29297
29365
  </script>
29298
29366
  </div>
29299
29367
  </template>
@@ -29349,10 +29417,11 @@ function readRootDims(rootHtml) {
29349
29417
  if (!widthMatch || !heightMatch) return null;
29350
29418
  return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
29351
29419
  }
29352
- var SCENE_PRESETS, LOTTIE_POSITIONS, GSAP_CDN, SCENE_OVERLAP_SECONDS, DOTLOTTIE_WC_VERSION, DOTLOTTIE_WEB_VERSION, DOTLOTTIE_WC_CDN, DOTLOTTIE_WASM_CDN;
29420
+ var SCENE_PRESETS, LOTTIE_RUNTIME_DIR, GSAP_CDN, SCENE_OVERLAP_SECONDS;
29353
29421
  var init_scene_html_emit = __esm({
29354
29422
  "src/commands/_shared/scene-html-emit.ts"() {
29355
29423
  "use strict";
29424
+ init_lottie();
29356
29425
  SCENE_PRESETS = [
29357
29426
  "simple",
29358
29427
  "announcement",
@@ -29360,37 +29429,26 @@ var init_scene_html_emit = __esm({
29360
29429
  "kinetic-type",
29361
29430
  "product-shot"
29362
29431
  ];
29363
- LOTTIE_POSITIONS = [
29364
- "full",
29365
- "center",
29366
- "top-left",
29367
- "top-right",
29368
- "bottom-left",
29369
- "bottom-right"
29370
- ];
29432
+ LOTTIE_RUNTIME_DIR = "assets/vendor/dotlottie";
29371
29433
  GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
29372
29434
  SCENE_OVERLAP_SECONDS = 0.4;
29373
- DOTLOTTIE_WC_VERSION = "0.9.12";
29374
- DOTLOTTIE_WEB_VERSION = "0.71.0";
29375
- DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
29376
- DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
29377
29435
  }
29378
29436
  });
29379
29437
 
29380
29438
  // src/commands/_shared/scene-lint.ts
29381
- import { readdir as readdir2, readFile as readFile7, writeFile as writeFile6, stat as stat3 } from "node:fs/promises";
29382
- import { existsSync as existsSync3 } from "node:fs";
29383
- import { resolve as resolve8, relative as relative2, join as join2 } from "node:path";
29439
+ import { readdir as readdir2, readFile as readFile8, writeFile as writeFile6, stat as stat3 } from "node:fs/promises";
29440
+ import { existsSync as existsSync4 } from "node:fs";
29441
+ import { resolve as resolve8, relative as relative2, join as join3 } from "node:path";
29384
29442
  import {
29385
29443
  runHyperframeLint
29386
29444
  } from "@hyperframes/producer";
29387
29445
  async function discoverSceneFiles(opts) {
29388
29446
  const projectDir = resolve8(opts.projectDir);
29389
29447
  const rootAbs = resolve8(projectDir, opts.rootRel ?? "index.html");
29390
- const root = existsSync3(rootAbs) ? rootAbs : null;
29448
+ const root = existsSync4(rootAbs) ? rootAbs : null;
29391
29449
  const compsDir = resolve8(projectDir, "compositions");
29392
29450
  const subs = [];
29393
- if (existsSync3(compsDir)) {
29451
+ if (existsSync4(compsDir)) {
29394
29452
  await collectHtmlRecursive(compsDir, subs);
29395
29453
  subs.sort();
29396
29454
  }
@@ -29399,7 +29457,7 @@ async function discoverSceneFiles(opts) {
29399
29457
  async function collectHtmlRecursive(dir, into) {
29400
29458
  const entries = await readdir2(dir, { withFileTypes: true });
29401
29459
  for (const entry of entries) {
29402
- const full = join2(dir, entry.name);
29460
+ const full = join3(dir, entry.name);
29403
29461
  if (entry.isDirectory()) {
29404
29462
  await collectHtmlRecursive(full, into);
29405
29463
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
@@ -29483,7 +29541,7 @@ async function runProjectLint(opts) {
29483
29541
  for (const sub of subs) targets.push({ abs: sub, isSub: true });
29484
29542
  for (const { abs, isSub } of targets) {
29485
29543
  const rel = relative2(projectDir, abs) || abs;
29486
- const html = await readFile7(abs, "utf-8");
29544
+ const html = await readFile8(abs, "utf-8");
29487
29545
  const prepared = {
29488
29546
  html,
29489
29547
  entryFile: rel,
@@ -29908,7 +29966,7 @@ var init_upload_host = __esm({
29908
29966
  });
29909
29967
 
29910
29968
  // src/commands/_shared/video-jobs.ts
29911
- import { readFile as readFile8 } from "node:fs/promises";
29969
+ import { readFile as readFile9 } from "node:fs/promises";
29912
29970
  import { resolve as resolve9 } from "node:path";
29913
29971
  function canonicalVideoProvider(provider) {
29914
29972
  const id = provider.toLowerCase();
@@ -29941,7 +29999,7 @@ function mimeTypeForPath(path, fallback2) {
29941
29999
  }
29942
30000
  async function fileToUrlOrDataUri(input, fallbackMimeType) {
29943
30001
  if (/^(https?:|data:)/.test(input)) return input;
29944
- const buffer = await readFile8(resolve9(process.cwd(), input));
30002
+ const buffer = await readFile9(resolve9(process.cwd(), input));
29945
30003
  return `data:${mimeTypeForPath(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
29946
30004
  }
29947
30005
  async function videoImageInput(generator, input) {
@@ -29956,7 +30014,7 @@ async function videoImageInput(generator, input) {
29956
30014
  if (generator.imageInput !== "url" || isUrl) {
29957
30015
  return fileToUrlOrDataUri(input, "image/png");
29958
30016
  }
29959
- const buffer = input.startsWith("data:") ? Buffer.from(input.slice(input.indexOf(",") + 1), "base64") : await readFile8(resolve9(process.cwd(), input));
30017
+ const buffer = input.startsWith("data:") ? Buffer.from(input.slice(input.indexOf(",") + 1), "base64") : await readFile9(resolve9(process.cwd(), input));
29960
30018
  const host = await resolveUploadHost();
29961
30019
  const upload = await host.uploadImage(buffer, {
29962
30020
  filename: input.startsWith("data:") ? "frame.png" : input,
@@ -29995,8 +30053,8 @@ var init_video_jobs = __esm({
29995
30053
  });
29996
30054
 
29997
30055
  // src/commands/_shared/image-jobs.ts
29998
- import { existsSync as existsSync4 } from "node:fs";
29999
- import { mkdir as mkdir5, readFile as readFile9 } from "node:fs/promises";
30056
+ import { existsSync as existsSync5 } from "node:fs";
30057
+ import { mkdir as mkdir6, readFile as readFile10 } from "node:fs/promises";
30000
30058
  import { dirname as dirname2, resolve as resolve10 } from "node:path";
30001
30059
  async function openImageGenerator(provider, apiKey) {
30002
30060
  const envVar = IMAGE_PROVIDER_ENV[provider];
@@ -30013,14 +30071,14 @@ async function openImageGenerator(provider, apiKey) {
30013
30071
  }
30014
30072
  async function readImageInput(path) {
30015
30073
  const absPath = resolve10(process.cwd(), path);
30016
- if (!existsSync4(absPath)) {
30074
+ if (!existsSync5(absPath)) {
30017
30075
  throw new ProviderError({ kind: "invalid-request", provider: "local", message: `Image not found: ${absPath}` });
30018
30076
  }
30019
- return { bytes: new Uint8Array(await readFile9(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
30077
+ return { bytes: new Uint8Array(await readFile10(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
30020
30078
  }
30021
30079
  async function saveImage(output, bytes) {
30022
30080
  const outputPath = resolve10(process.cwd(), output);
30023
- await mkdir5(dirname2(outputPath), { recursive: true });
30081
+ await mkdir6(dirname2(outputPath), { recursive: true });
30024
30082
  await writeImageFile(outputPath, Buffer.from(bytes));
30025
30083
  return outputPath;
30026
30084
  }
@@ -30078,19 +30136,19 @@ var init_audio = __esm({
30078
30136
  });
30079
30137
 
30080
30138
  // src/commands/_shared/install-skill.ts
30081
- import { existsSync as existsSync5 } from "node:fs";
30139
+ import { existsSync as existsSync6 } from "node:fs";
30082
30140
  import { homedir as homedir3 } from "node:os";
30083
- import { join as join3, resolve as resolve11 } from "node:path";
30141
+ import { join as join4, resolve as resolve11 } from "node:path";
30084
30142
  function hasGlobalHyperframesSkill() {
30085
- return existsSync5(join3(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
30143
+ return existsSync6(join4(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
30086
30144
  }
30087
30145
  function hasProjectHyperframesSkill(projectDir) {
30088
- return existsSync5(join3(resolve11(projectDir), PROJECT_SKILL_PATH));
30146
+ return existsSync6(join4(resolve11(projectDir), PROJECT_SKILL_PATH));
30089
30147
  }
30090
30148
  function resolveSkillReference(projectDir) {
30091
30149
  const dir = resolve11(projectDir);
30092
- if (existsSync5(join3(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
30093
- if (existsSync5(join3(dir, "SKILL.md"))) return "SKILL.md";
30150
+ if (existsSync6(join4(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
30151
+ if (existsSync6(join4(dir, "SKILL.md"))) return "SKILL.md";
30094
30152
  return null;
30095
30153
  }
30096
30154
  function hyperframesSkillAvailable(projectDir) {
@@ -30151,7 +30209,7 @@ var init_install_skill = __esm({
30151
30209
  init_exec_safe();
30152
30210
  HYPERFRAMES_SKILL_PACKAGE = "heygen-com/hyperframes";
30153
30211
  HYPERFRAMES_SKILL_INSTALL_COMMAND = `npx skills add ${HYPERFRAMES_SKILL_PACKAGE}`;
30154
- PROJECT_SKILL_PATH = join3(".agents", "skills", "hyperframes", "SKILL.md");
30212
+ PROJECT_SKILL_PATH = join4(".agents", "skills", "hyperframes", "SKILL.md");
30155
30213
  }
30156
30214
  });
30157
30215
 
@@ -30404,17 +30462,17 @@ var init_compose_beat_prompt = __esm({
30404
30462
  });
30405
30463
 
30406
30464
  // src/commands/_shared/root-sync.ts
30407
- import { existsSync as existsSync6 } from "node:fs";
30408
- import { readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
30409
- import { join as join4, resolve as resolve12 } from "node:path";
30465
+ import { existsSync as existsSync7 } from "node:fs";
30466
+ import { readFile as readFile11, writeFile as writeFile8 } from "node:fs/promises";
30467
+ import { join as join5, resolve as resolve12 } from "node:path";
30410
30468
  async function createRootSyncPlan(opts) {
30411
30469
  const projectDir = resolve12(opts.projectDir);
30412
30470
  const rootRel = opts.rootRel ?? "index.html";
30413
30471
  const rootPath = resolve12(projectDir, rootRel);
30414
- if (!existsSync6(rootPath)) {
30472
+ if (!existsSync7(rootPath)) {
30415
30473
  return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
30416
30474
  }
30417
- const html = await readFile10(rootPath, "utf-8");
30475
+ const html = await readFile11(rootPath, "utf-8");
30418
30476
  if (opts.beats.length === 0) {
30419
30477
  return { rootPath, rootRel, exists: true, changed: false, fixCodes: [], issues: [] };
30420
30478
  }
@@ -30473,8 +30531,8 @@ async function syncRootComposition(opts) {
30473
30531
  }
30474
30532
  async function loadProjectRootSyncBeats(projectDir) {
30475
30533
  const root = resolve12(projectDir);
30476
- const storyboardPath2 = join4(root, "STORYBOARD.md");
30477
- if (!existsSync6(storyboardPath2)) return [];
30534
+ const storyboardPath2 = join5(root, "STORYBOARD.md");
30535
+ if (!existsSync7(storyboardPath2)) return [];
30478
30536
  const parsed = parseStoryboard(await loadStoryboardMarkdown(root));
30479
30537
  const reportBeats = await readBuildReportBeats(root);
30480
30538
  return parsed.beats.map((beat) => {
@@ -30542,7 +30600,7 @@ async function resolveSyncedBeatDuration(opts) {
30542
30600
  if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
30543
30601
  const probe = opts.probeAudioDuration ?? getAudioDuration;
30544
30602
  try {
30545
- const audioDuration = await probe(join4(opts.projectDir, opts.narrationPath));
30603
+ const audioDuration = await probe(join5(opts.projectDir, opts.narrationPath));
30546
30604
  return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
30547
30605
  } catch {
30548
30606
  return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
@@ -30700,17 +30758,17 @@ function hasExpectedAudioRef(html, ref) {
30700
30758
  return ref.volume === void 0 || tag.includes(`data-volume="${ref.volume}"`);
30701
30759
  }
30702
30760
  async function readBuildReportBeats(projectDir) {
30703
- const reportPath = join4(projectDir, "build-report.json");
30704
- if (!existsSync6(reportPath)) return [];
30761
+ const reportPath = join5(projectDir, "build-report.json");
30762
+ if (!existsSync7(reportPath)) return [];
30705
30763
  try {
30706
- const report = JSON.parse(await readFile10(reportPath, "utf-8"));
30764
+ const report = JSON.parse(await readFile11(reportPath, "utf-8"));
30707
30765
  return Array.isArray(report.beats) ? report.beats : [];
30708
30766
  } catch {
30709
30767
  return [];
30710
30768
  }
30711
30769
  }
30712
30770
  function firstExisting(projectDir, rels) {
30713
- return rels.find((rel) => existsSync6(join4(projectDir, rel)));
30771
+ return rels.find((rel) => existsSync7(join5(projectDir, rel)));
30714
30772
  }
30715
30773
  function stringOrUndefined(value) {
30716
30774
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -30745,13 +30803,13 @@ var init_root_sync = __esm({
30745
30803
  });
30746
30804
 
30747
30805
  // src/commands/_shared/transcription.ts
30748
- import { readFile as readFile11 } from "node:fs/promises";
30749
- import { basename as basename4 } from "node:path";
30806
+ import { readFile as readFile12 } from "node:fs/promises";
30807
+ import { basename as basename5 } from "node:path";
30750
30808
  async function transcribeAudioFile(path, options) {
30751
30809
  try {
30752
30810
  const transcriber = await createTranscriber("openai", options.apiKey);
30753
- const audio = new Uint8Array(await readFile11(path));
30754
- const name = basename4(path);
30811
+ const audio = new Uint8Array(await readFile12(path));
30812
+ const name = basename5(path);
30755
30813
  const transcript = await transcriber.transcribeAudio({
30756
30814
  audio,
30757
30815
  // A real extension helps Whisper; temp files without one are sniffed from the bytes.
@@ -30784,9 +30842,9 @@ var init_transcription2 = __esm({
30784
30842
  });
30785
30843
 
30786
30844
  // src/commands/_shared/transcribe-narration.ts
30787
- import { existsSync as existsSync7 } from "node:fs";
30788
- import { readFile as readFile12 } from "node:fs/promises";
30789
- import { join as join5 } from "node:path";
30845
+ import { existsSync as existsSync8 } from "node:fs";
30846
+ import { readFile as readFile13 } from "node:fs/promises";
30847
+ import { join as join6 } from "node:path";
30790
30848
  async function transcribeNarrationWords(audioAbsPath, opts) {
30791
30849
  try {
30792
30850
  const transcript = await transcribeAudioFile(audioAbsPath, { apiKey: opts.apiKey, granularity: "word", language: opts.language });
@@ -30802,10 +30860,10 @@ function beatTranscriptRelPath(beatId) {
30802
30860
  return `assets/transcript-${beatId}.json`;
30803
30861
  }
30804
30862
  async function readBeatTranscript(projectDir, beatId) {
30805
- const abs = join5(projectDir, beatTranscriptRelPath(beatId));
30806
- if (!existsSync7(abs)) return void 0;
30863
+ const abs = join6(projectDir, beatTranscriptRelPath(beatId));
30864
+ if (!existsSync8(abs)) return void 0;
30807
30865
  try {
30808
- const parsed = JSON.parse(await readFile12(abs, "utf-8"));
30866
+ const parsed = JSON.parse(await readFile13(abs, "utf-8"));
30809
30867
  if (!Array.isArray(parsed)) return void 0;
30810
30868
  const words = parsed.filter(
30811
30869
  (w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
@@ -30823,15 +30881,15 @@ var init_transcribe_narration = __esm({
30823
30881
  });
30824
30882
 
30825
30883
  // src/commands/_shared/compose-prompts.ts
30826
- import { existsSync as existsSync8 } from "node:fs";
30827
- import { join as join6, relative as relative3, resolve as resolve13 } from "node:path";
30884
+ import { existsSync as existsSync9 } from "node:fs";
30885
+ import { join as join7, relative as relative3, resolve as resolve13 } from "node:path";
30828
30886
  async function getComposePrompts(opts) {
30829
30887
  const projectDir = resolve13(opts.projectDir);
30830
- const designPath = join6(projectDir, "DESIGN.md");
30831
- const compositionPath = join6(projectDir, "COMPOSITION.md");
30832
- const storyboardPath2 = join6(projectDir, "STORYBOARD.md");
30833
- const compositionsDir = join6(projectDir, "compositions");
30834
- const compositionReference = existsSync8(compositionPath) ? "COMPOSITION.md" : null;
30888
+ const designPath = join7(projectDir, "DESIGN.md");
30889
+ const compositionPath = join7(projectDir, "COMPOSITION.md");
30890
+ const storyboardPath2 = join7(projectDir, "STORYBOARD.md");
30891
+ const compositionsDir = join7(projectDir, "compositions");
30892
+ const compositionReference = existsSync9(compositionPath) ? "COMPOSITION.md" : null;
30835
30893
  const warnings = [];
30836
30894
  const baseError = (msg) => ({
30837
30895
  success: false,
@@ -30846,10 +30904,10 @@ async function getComposePrompts(opts) {
30846
30904
  warnings,
30847
30905
  error: msg
30848
30906
  });
30849
- if (!existsSync8(designPath)) {
30907
+ if (!existsSync9(designPath)) {
30850
30908
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
30851
30909
  }
30852
- if (!existsSync8(storyboardPath2)) {
30910
+ if (!existsSync9(storyboardPath2)) {
30853
30911
  return baseError(`STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
30854
30912
  }
30855
30913
  if (!hyperframesSkillAvailable(projectDir)) {
@@ -30881,7 +30939,7 @@ async function getComposePrompts(opts) {
30881
30939
  }
30882
30940
  const result = await Promise.all(
30883
30941
  beats.map(async (beat) => {
30884
- const outputPathAbs = join6(compositionsDir, `scene-${beat.id}.html`);
30942
+ const outputPathAbs = join7(compositionsDir, `scene-${beat.id}.html`);
30885
30943
  const outputPathRel = relative3(projectDir, outputPathAbs);
30886
30944
  const finalDurationSec = finalDurations.get(beat.id);
30887
30945
  const transcript = await readBeatTranscript(projectDir, beat.id);
@@ -30901,7 +30959,7 @@ async function getComposePrompts(opts) {
30901
30959
  body: beat.body,
30902
30960
  userPrompt,
30903
30961
  transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
30904
- exists: existsSync8(outputPathAbs)
30962
+ exists: existsSync9(outputPathAbs)
30905
30963
  };
30906
30964
  })
30907
30965
  );
@@ -30968,9 +31026,9 @@ var init_compose_prompts = __esm({
30968
31026
  });
30969
31027
 
30970
31028
  // src/commands/_shared/scene-submit.ts
30971
- import { existsSync as existsSync9 } from "node:fs";
30972
- import { mkdir as mkdir6, writeFile as writeFile9 } from "node:fs/promises";
30973
- import { join as join7, resolve as resolve14 } from "node:path";
31029
+ import { existsSync as existsSync10 } from "node:fs";
31030
+ import { mkdir as mkdir7, writeFile as writeFile9 } from "node:fs/promises";
31031
+ import { join as join8, resolve as resolve14 } from "node:path";
30974
31032
  async function executeSceneSubmit(opts) {
30975
31033
  const projectDir = resolve14(opts.projectDir);
30976
31034
  const beatId = opts.beatId.trim();
@@ -30984,8 +31042,8 @@ async function executeSceneSubmit(opts) {
30984
31042
  warnings: [],
30985
31043
  retryWith: []
30986
31044
  };
30987
- const storyboardPath2 = join7(projectDir, "STORYBOARD.md");
30988
- if (!existsSync9(storyboardPath2)) {
31045
+ const storyboardPath2 = join8(projectDir, "STORYBOARD.md");
31046
+ if (!existsSync10(storyboardPath2)) {
30989
31047
  return {
30990
31048
  ...base,
30991
31049
  success: false,
@@ -31062,8 +31120,8 @@ async function executeSceneSubmit(opts) {
31062
31120
  if (opts.validateOnly) {
31063
31121
  return { ...base, success: true };
31064
31122
  }
31065
- const absPath = join7(projectDir, scenePath);
31066
- await mkdir6(join7(projectDir, "compositions"), { recursive: true });
31123
+ const absPath = join8(projectDir, scenePath);
31124
+ await mkdir7(join8(projectDir, "compositions"), { recursive: true });
31067
31125
  await writeFile9(absPath, html.endsWith("\n") ? html : `${html}
31068
31126
  `, "utf-8");
31069
31127
  return {
@@ -31087,9 +31145,9 @@ var init_scene_submit = __esm({
31087
31145
  });
31088
31146
 
31089
31147
  // src/commands/_shared/sub-comp-duration-sync.ts
31090
- import { existsSync as existsSync10 } from "node:fs";
31091
- import { readFile as readFile13 } from "node:fs/promises";
31092
- import { join as join8, resolve as resolve15 } from "node:path";
31148
+ import { existsSync as existsSync11 } from "node:fs";
31149
+ import { readFile as readFile14 } from "node:fs/promises";
31150
+ import { join as join9, resolve as resolve15 } from "node:path";
31093
31151
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
31094
31152
  const compositionId = `scene-${beatId}`;
31095
31153
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -31181,15 +31239,15 @@ async function createSubCompDurationSyncPlans(opts) {
31181
31239
  const results = [];
31182
31240
  for (const beat of opts.beats) {
31183
31241
  const fileRel = `compositions/scene-${beat.id}.html`;
31184
- const filePath = join8(projectDir, fileRel);
31185
- if (!existsSync10(filePath)) continue;
31242
+ const filePath = join9(projectDir, fileRel);
31243
+ if (!existsSync11(filePath)) continue;
31186
31244
  const newDurationSec = await resolveSyncedBeatDuration({
31187
31245
  projectDir,
31188
31246
  beatDuration: beat.duration,
31189
31247
  narrationPath: beat.narrationPath,
31190
31248
  sceneDurationSec: beat.sceneDurationSec
31191
31249
  });
31192
- const html = await readFile13(filePath, "utf-8");
31250
+ const html = await readFile14(filePath, "utf-8");
31193
31251
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
31194
31252
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
31195
31253
  }
@@ -31214,7 +31272,7 @@ var init_sub_comp_duration_sync = __esm({
31214
31272
 
31215
31273
  // src/commands/_shared/review-report.ts
31216
31274
  import { writeFile as writeFile10 } from "node:fs/promises";
31217
- import { join as join9, resolve as resolve16 } from "node:path";
31275
+ import { join as join10, resolve as resolve16 } from "node:path";
31218
31276
  function statusFromIssues(issues) {
31219
31277
  if (issues.some((issue) => issue.severity === "error")) return "fail";
31220
31278
  if (issues.some((issue) => issue.severity === "warning")) return "warn";
@@ -31392,7 +31450,7 @@ function buildReviewReport(opts) {
31392
31450
  });
31393
31451
  }
31394
31452
  function defaultReviewReportPath(projectDir) {
31395
- return join9(resolve16(projectDir), "review-report.json");
31453
+ return join10(resolve16(projectDir), "review-report.json");
31396
31454
  }
31397
31455
  async function writeReviewReport(path, report) {
31398
31456
  await writeFile10(path, JSON.stringify(report, null, 2) + "\n", "utf-8");
@@ -31707,7 +31765,7 @@ var init_review_report = __esm({
31707
31765
  });
31708
31766
 
31709
31767
  // src/commands/_shared/scene-repair.ts
31710
- import { readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
31768
+ import { readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
31711
31769
  import { relative as relative4, resolve as resolve17 } from "node:path";
31712
31770
  import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
31713
31771
  async function executeSceneRepair(opts) {
@@ -31753,7 +31811,7 @@ async function executeSceneRepair(opts) {
31753
31811
  }
31754
31812
  for (const target of targets) {
31755
31813
  const rel = relative4(projectDir, target.abs) || target.abs;
31756
- const html = await readFile14(target.abs, "utf-8");
31814
+ const html = await readFile15(target.abs, "utf-8");
31757
31815
  const findings = await lintHtml(html, rel, target.isSub);
31758
31816
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
31759
31817
  if (fixedCodes.length > 0) {
@@ -31834,8 +31892,8 @@ var init_scene_repair = __esm({
31834
31892
 
31835
31893
  // src/commands/scene.ts
31836
31894
  import { resolve as resolve18, relative as relative5, dirname as dirname3 } from "node:path";
31837
- import { mkdir as mkdir7, readFile as readFile15, writeFile as writeFile12, access as access5, copyFile } from "node:fs/promises";
31838
- import { existsSync as existsSync11 } from "node:fs";
31895
+ import { mkdir as mkdir8, readFile as readFile16, writeFile as writeFile12, access as access5, copyFile as copyFile2 } from "node:fs/promises";
31896
+ import { existsSync as existsSync12 } from "node:fs";
31839
31897
  function validateDuration(value) {
31840
31898
  const n = parseFloat(value);
31841
31899
  if (!Number.isFinite(n) || n <= 0 || n > 3600) {
@@ -31899,8 +31957,8 @@ function readClipStart(rootHtml, beatId) {
31899
31957
  async function resolveNarrationText(value) {
31900
31958
  if (!value) return void 0;
31901
31959
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
31902
- if (looksLikePath && existsSync11(value)) {
31903
- return (await readFile15(value, "utf-8")).trim();
31960
+ if (looksLikePath && existsSync12(value)) {
31961
+ return (await readFile16(value, "utf-8")).trim();
31904
31962
  }
31905
31963
  return value.trim();
31906
31964
  }
@@ -31937,7 +31995,7 @@ async function executeSceneAdd(opts) {
31937
31995
  if (!opts.force && await pathExists2(scenePath)) {
31938
31996
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
31939
31997
  }
31940
- const rootHtmlBefore = await readFile15(rootPath, "utf-8");
31998
+ const rootHtmlBefore = await readFile16(rootPath, "utf-8");
31941
31999
  let dims = readRootDims(rootHtmlBefore);
31942
32000
  if (!dims) {
31943
32001
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -31961,8 +32019,8 @@ async function executeSceneAdd(opts) {
31961
32019
  }
31962
32020
  audioRelPath = `assets/narration-${id}.${ext}`;
31963
32021
  audioAbsPath = resolve18(projectDir, audioRelPath);
31964
- await mkdir7(dirname3(audioAbsPath), { recursive: true });
31965
- await copyFile(sourceAbs, audioAbsPath);
32022
+ await mkdir8(dirname3(audioAbsPath), { recursive: true });
32023
+ await copyFile2(sourceAbs, audioAbsPath);
31966
32024
  try {
31967
32025
  narrationDuration = await getAudioDuration(audioAbsPath);
31968
32026
  } catch {
@@ -31994,7 +32052,7 @@ async function executeSceneAdd(opts) {
31994
32052
  }
31995
32053
  audioRelPath = `assets/narration-${id}.${resolution.audioExtension}`;
31996
32054
  audioAbsPath = resolve18(projectDir, audioRelPath);
31997
- await mkdir7(dirname3(audioAbsPath), { recursive: true });
32055
+ await mkdir8(dirname3(audioAbsPath), { recursive: true });
31998
32056
  await writeFile12(audioAbsPath, tts.audioBuffer);
31999
32057
  try {
32000
32058
  narrationDuration = await getAudioDuration(audioAbsPath);
@@ -32052,7 +32110,7 @@ async function executeSceneAdd(opts) {
32052
32110
  });
32053
32111
  imageRelPath = `assets/scene-${id}.png`;
32054
32112
  imageAbsPath = resolve18(projectDir, imageRelPath);
32055
- await mkdir7(dirname3(imageAbsPath), { recursive: true });
32113
+ await mkdir8(dirname3(imageAbsPath), { recursive: true });
32056
32114
  await writeImageFile(imageAbsPath, Buffer.from(result.images[0].bytes));
32057
32115
  } catch (error) {
32058
32116
  return errResult(`${label} image generation failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -32070,10 +32128,13 @@ async function executeSceneAdd(opts) {
32070
32128
  if (ext !== "json" && ext !== "lottie") {
32071
32129
  return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
32072
32130
  }
32131
+ const assetProblem = await checkLottieAsset(sourceAbs);
32132
+ if (assetProblem) return errResult(assetProblem);
32073
32133
  lottieRelPath = `assets/lottie-${id}.${ext}`;
32074
32134
  lottieAbsPath = resolve18(projectDir, lottieRelPath);
32075
- await mkdir7(dirname3(lottieAbsPath), { recursive: true });
32076
- await copyFile(sourceAbs, lottieAbsPath);
32135
+ await mkdir8(dirname3(lottieAbsPath), { recursive: true });
32136
+ await copyFile2(sourceAbs, lottieAbsPath);
32137
+ await copyDotLottieRuntime(resolve18(projectDir, LOTTIE_RUNTIME_DIR));
32077
32138
  lottieInput = {
32078
32139
  src: lottieRelPath,
32079
32140
  position: opts.lottie.position,
@@ -32117,7 +32178,7 @@ async function executeSceneAdd(opts) {
32117
32178
  storyboardAction = synced.action;
32118
32179
  } else {
32119
32180
  opts.onProgress?.("Syncing STORYBOARD.md...");
32120
- const before = await readFile15(storyboardAbsPath, "utf-8");
32181
+ const before = await readFile16(storyboardAbsPath, "utf-8");
32121
32182
  const synced = upsertStoryboardBeat(before, upsert);
32122
32183
  if (synced.markdown !== before) {
32123
32184
  await writeFile12(storyboardAbsPath, synced.markdown, "utf-8");
@@ -32142,7 +32203,7 @@ async function executeSceneAdd(opts) {
32142
32203
  transcript: transcriptWords,
32143
32204
  lottie: lottieInput
32144
32205
  });
32145
- await mkdir7(dirname3(scenePath), { recursive: true });
32206
+ await mkdir8(dirname3(scenePath), { recursive: true });
32146
32207
  await writeFile12(scenePath, sceneHtml, "utf-8");
32147
32208
  opts.onProgress?.("Updating root composition...");
32148
32209
  let start = 0;
@@ -32154,7 +32215,7 @@ async function executeSceneAdd(opts) {
32154
32215
  });
32155
32216
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
32156
32217
  if (blockingIssue) return errResult(blockingIssue.message);
32157
- const rootHtmlAfterSync = await readFile15(rootPath, "utf-8");
32218
+ const rootHtmlAfterSync = await readFile16(rootPath, "utf-8");
32158
32219
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
32159
32220
  } else {
32160
32221
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -32186,7 +32247,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
32186
32247
  const root = rootArg || "index.html";
32187
32248
  const project = projectArg || ".";
32188
32249
  const defaultProject = project === ".";
32189
- if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync11(resolve18(root, "index.html"))) {
32250
+ if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync12(resolve18(root, "index.html"))) {
32190
32251
  return { projectDir: resolve18(root), rootRel: "index.html" };
32191
32252
  }
32192
32253
  return { projectDir: resolve18(project), rootRel: root };
@@ -32210,6 +32271,7 @@ var init_scene = __esm({
32210
32271
  init_cost_tier();
32211
32272
  init_visual_styles();
32212
32273
  init_scene_html_emit();
32274
+ init_lottie();
32213
32275
  init_scene_lint();
32214
32276
  init_output();
32215
32277
  init_api_key();
@@ -32382,7 +32444,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
32382
32444
  }
32383
32445
  html = Buffer.concat(chunks).toString("utf-8");
32384
32446
  } else if (options.file) {
32385
- html = await readFile15(resolve18(String(options.file)), "utf-8");
32447
+ html = await readFile16(resolve18(String(options.file)), "utf-8");
32386
32448
  } else {
32387
32449
  exitWithError(
32388
32450
  usageError(
@@ -32839,9 +32901,9 @@ __export(remotion_exports, {
32839
32901
  wrapComponentWithImage: () => wrapComponentWithImage,
32840
32902
  wrapComponentWithVideo: () => wrapComponentWithVideo
32841
32903
  });
32842
- import { writeFile as writeFile14, mkdir as mkdir8, rm as rm3, copyFile as copyFile2 } from "node:fs/promises";
32843
- import { existsSync as existsSync13 } from "node:fs";
32844
- import { join as join10 } from "node:path";
32904
+ import { writeFile as writeFile14, mkdir as mkdir9, rm as rm3, copyFile as copyFile3 } from "node:fs/promises";
32905
+ import { existsSync as existsSync14 } from "node:fs";
32906
+ import { join as join11 } from "node:path";
32845
32907
  import { tmpdir } from "node:os";
32846
32908
  import { createHash as createHash2 } from "node:crypto";
32847
32909
  async function ensureRemotionInstalled() {
@@ -32858,8 +32920,8 @@ async function ensureRemotionInstalled() {
32858
32920
  }
32859
32921
  }
32860
32922
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
32861
- const dir = join10(tmpdir(), `vibe_motion_${Date.now()}`);
32862
- await mkdir8(dir, { recursive: true });
32923
+ const dir = join11(tmpdir(), `vibe_motion_${Date.now()}`);
32924
+ await mkdir9(dir, { recursive: true });
32863
32925
  const deps = {
32864
32926
  remotion: REMOTION_VERSION,
32865
32927
  "@remotion/cli": REMOTION_VERSION,
@@ -32877,7 +32939,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
32877
32939
  dependencies: deps
32878
32940
  };
32879
32941
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
32880
- await writeFile14(join10(dir, "package.json"), packageJsonStr);
32942
+ await writeFile14(join11(dir, "package.json"), packageJsonStr);
32881
32943
  const tsconfig = {
32882
32944
  compilerOptions: {
32883
32945
  target: "ES2020",
@@ -32889,8 +32951,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
32889
32951
  skipLibCheck: true
32890
32952
  }
32891
32953
  };
32892
- await writeFile14(join10(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
32893
- await writeFile14(join10(dir, "Component.tsx"), componentCode);
32954
+ await writeFile14(join11(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
32955
+ await writeFile14(join11(dir, "Component.tsx"), componentCode);
32894
32956
  const rootCode = `import { registerRoot, Composition } from "remotion";
32895
32957
  import { ${componentName} } from "./Component";
32896
32958
 
@@ -32909,27 +32971,27 @@ const Root = () => {
32909
32971
 
32910
32972
  registerRoot(Root);
32911
32973
  `;
32912
- await writeFile14(join10(dir, "Root.tsx"), rootCode);
32974
+ await writeFile14(join11(dir, "Root.tsx"), rootCode);
32913
32975
  const depsHash = createHash2("md5").update(packageJsonStr).digest("hex").slice(0, 12);
32914
- const cacheMarker = join10(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
32915
- const cachedModules = join10(REMOTION_CACHE_DIR, "node_modules");
32916
- if (existsSync13(cacheMarker) && existsSync13(cachedModules)) {
32976
+ const cacheMarker = join11(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
32977
+ const cachedModules = join11(REMOTION_CACHE_DIR, "node_modules");
32978
+ if (existsSync14(cacheMarker) && existsSync14(cachedModules)) {
32917
32979
  const { symlink } = await import("node:fs/promises");
32918
32980
  try {
32919
- await symlink(cachedModules, join10(dir, "node_modules"), "dir");
32981
+ await symlink(cachedModules, join11(dir, "node_modules"), "dir");
32920
32982
  } catch {
32921
32983
  await this_npmInstall(dir);
32922
32984
  }
32923
32985
  } else {
32924
32986
  await this_npmInstall(dir);
32925
32987
  try {
32926
- await mkdir8(REMOTION_CACHE_DIR, { recursive: true });
32927
- await writeFile14(join10(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
32928
- if (existsSync13(join10(dir, "node_modules"))) {
32988
+ await mkdir9(REMOTION_CACHE_DIR, { recursive: true });
32989
+ await writeFile14(join11(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
32990
+ if (existsSync14(join11(dir, "node_modules"))) {
32929
32991
  const { rename: rename5, symlink } = await import("node:fs/promises");
32930
- await rename5(join10(dir, "node_modules"), cachedModules).catch(() => {
32992
+ await rename5(join11(dir, "node_modules"), cachedModules).catch(() => {
32931
32993
  });
32932
- await symlink(cachedModules, join10(dir, "node_modules"), "dir").catch(() => {
32994
+ await symlink(cachedModules, join11(dir, "node_modules"), "dir").catch(() => {
32933
32995
  });
32934
32996
  await writeFile14(cacheMarker, depsHash);
32935
32997
  }
@@ -32966,7 +33028,7 @@ async function renderMotion(options) {
32966
33028
  }
32967
33029
  );
32968
33030
  try {
32969
- const entryPoint = join10(dir, "Root.tsx");
33031
+ const entryPoint = join11(dir, "Root.tsx");
32970
33032
  const { execFile: execFile3 } = await import("node:child_process");
32971
33033
  const { promisify: promisify3 } = await import("node:util");
32972
33034
  const execFileAsync3 = promisify3(execFile3);
@@ -33080,10 +33142,10 @@ async function renderWithEmbeddedImage(options) {
33080
33142
  }
33081
33143
  );
33082
33144
  try {
33083
- const publicDir = join10(dir, "public");
33084
- await mkdir8(publicDir, { recursive: true });
33085
- await copyFile2(options.imagePath, join10(publicDir, options.imageFileName));
33086
- const entryPoint = join10(dir, "Root.tsx");
33145
+ const publicDir = join11(dir, "public");
33146
+ await mkdir9(publicDir, { recursive: true });
33147
+ await copyFile3(options.imagePath, join11(publicDir, options.imageFileName));
33148
+ const entryPoint = join11(dir, "Root.tsx");
33087
33149
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
33088
33150
  const { execFile: execFileImg } = await import("node:child_process");
33089
33151
  const { promisify: promisifyImg } = await import("node:util");
@@ -33144,10 +33206,10 @@ async function renderWithEmbeddedVideo(options) {
33144
33206
  }
33145
33207
  );
33146
33208
  try {
33147
- const publicDir = join10(dir, "public");
33148
- await mkdir8(publicDir, { recursive: true });
33149
- await copyFile2(options.videoPath, join10(publicDir, options.videoFileName));
33150
- const entryPoint = join10(dir, "Root.tsx");
33209
+ const publicDir = join11(dir, "public");
33210
+ await mkdir9(publicDir, { recursive: true });
33211
+ await copyFile3(options.videoPath, join11(publicDir, options.videoFileName));
33212
+ const entryPoint = join11(dir, "Root.tsx");
33151
33213
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
33152
33214
  const { execFile: execFileVid } = await import("node:child_process");
33153
33215
  const { promisify: promisifyVid } = await import("node:util");
@@ -33563,7 +33625,7 @@ var init_remotion = __esm({
33563
33625
  "use strict";
33564
33626
  init_exec_safe();
33565
33627
  REMOTION_VERSION = "4.0.447";
33566
- REMOTION_CACHE_DIR = join10(tmpdir(), "vibe_remotion_cache");
33628
+ REMOTION_CACHE_DIR = join11(tmpdir(), "vibe_remotion_cache");
33567
33629
  }
33568
33630
  });
33569
33631
 
@@ -33574,8 +33636,8 @@ __export(ai_motion_exports, {
33574
33636
  registerMotionCommand: () => registerMotionCommand
33575
33637
  });
33576
33638
  import { resolve as resolve20 } from "node:path";
33577
- import { existsSync as existsSync14 } from "node:fs";
33578
- import { readFile as readFile17, writeFile as writeFile15 } from "node:fs/promises";
33639
+ import { existsSync as existsSync15 } from "node:fs";
33640
+ import { readFile as readFile18, writeFile as writeFile15 } from "node:fs/promises";
33579
33641
  function resolveMotionModel(alias) {
33580
33642
  const claude = findModel("claude", "llm", alias);
33581
33643
  if (claude) return { provider: "claude", modelId: claude.id };
@@ -33629,7 +33691,7 @@ async function executeMotion(options) {
33629
33691
  };
33630
33692
  }
33631
33693
  const imagePath = resolve20(process.cwd(), options.image);
33632
- const imageBuffer = await readFile17(imagePath);
33694
+ const imageBuffer = await readFile18(imagePath);
33633
33695
  const gemini = new GeminiProvider();
33634
33696
  await gemini.initialize({ apiKey: geminiApiKey });
33635
33697
  const analysisResult = await gemini.analyzeImage(
@@ -33666,7 +33728,7 @@ Use this image analysis to inform the color palette, typography placement, and o
33666
33728
  }
33667
33729
  if (geminiApiKey) {
33668
33730
  const videoPath = resolve20(process.cwd(), options.video);
33669
- const videoBuffer = await readFile17(videoPath);
33731
+ const videoBuffer = await readFile18(videoPath);
33670
33732
  const gemini = new GeminiProvider();
33671
33733
  await gemini.initialize({ apiKey: geminiApiKey });
33672
33734
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -33697,10 +33759,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
33697
33759
  let result;
33698
33760
  if (options.fromTsx) {
33699
33761
  const tsxPath = resolve20(process.cwd(), options.fromTsx);
33700
- if (!existsSync14(tsxPath)) {
33762
+ if (!existsSync15(tsxPath)) {
33701
33763
  return { success: false, error: `TSX file not found: ${tsxPath}` };
33702
33764
  }
33703
- const existingCode = await readFile17(tsxPath, "utf-8");
33765
+ const existingCode = await readFile18(tsxPath, "utf-8");
33704
33766
  if (useGemini) {
33705
33767
  const gemini = new GeminiProvider();
33706
33768
  await gemini.initialize({ apiKey });
@@ -34158,11 +34220,11 @@ __export(ai_video_exports, {
34158
34220
  executeVideoGenerate: () => executeVideoGenerate,
34159
34221
  executeVideoStatus: () => executeVideoStatus
34160
34222
  });
34161
- import { mkdir as mkdir9, writeFile as writeFile17 } from "node:fs/promises";
34223
+ import { mkdir as mkdir10, writeFile as writeFile17 } from "node:fs/promises";
34162
34224
  import { dirname as dirname4, resolve as resolve22 } from "node:path";
34163
34225
  async function saveOutput(output, bytes) {
34164
34226
  const outputPath = resolve22(process.cwd(), output);
34165
- await mkdir9(dirname4(outputPath), { recursive: true });
34227
+ await mkdir10(dirname4(outputPath), { recursive: true });
34166
34228
  await writeFile17(outputPath, bytes);
34167
34229
  return outputPath;
34168
34230
  }
@@ -34789,10 +34851,10 @@ var init_tty = __esm({
34789
34851
  });
34790
34852
 
34791
34853
  // src/utils/audio-file.ts
34792
- import { mkdir as mkdir10, rm as rm4, writeFile as writeFile18 } from "node:fs/promises";
34854
+ import { mkdir as mkdir11, rm as rm4, writeFile as writeFile18 } from "node:fs/promises";
34793
34855
  import { dirname as dirname5, extname as extname4 } from "node:path";
34794
34856
  async function writeAudioFile(path, bytes, container) {
34795
- await mkdir10(dirname5(path), { recursive: true });
34857
+ await mkdir11(dirname5(path), { recursive: true });
34796
34858
  const wanted = extname4(path).toLowerCase();
34797
34859
  if (!CONVERTIBLE.has(wanted) || wanted === `.${container}`) {
34798
34860
  await writeFile18(path, bytes);
@@ -34939,15 +35001,15 @@ var init_music_status = __esm({
34939
35001
  });
34940
35002
 
34941
35003
  // src/commands/_shared/build-asset-metadata.ts
34942
- import { existsSync as existsSync15, readFileSync } from "node:fs";
34943
- import { mkdir as mkdir11, writeFile as writeFile19 } from "node:fs/promises";
34944
- import { dirname as dirname6, join as join11 } from "node:path";
35004
+ import { existsSync as existsSync16, readFileSync } from "node:fs";
35005
+ import { mkdir as mkdir12, writeFile as writeFile19 } from "node:fs/promises";
35006
+ import { dirname as dirname6, join as join12 } from "node:path";
34945
35007
  function assetMetadataPath(kind, beatId) {
34946
35008
  return `.vibeframe/assets/${kind}-${beatId}.json`;
34947
35009
  }
34948
35010
  function readAssetMetadata(projectDir, kind, beatId) {
34949
- const path = join11(projectDir, assetMetadataPath(kind, beatId));
34950
- if (!existsSync15(path)) return null;
35011
+ const path = join12(projectDir, assetMetadataPath(kind, beatId));
35012
+ if (!existsSync16(path)) return null;
34951
35013
  try {
34952
35014
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
34953
35015
  if (parsed.schemaVersion !== "1" || parsed.kind !== kind || parsed.beatId !== beatId || typeof parsed.cacheKey !== "string") {
@@ -34976,7 +35038,7 @@ function isFreshCanonicalAsset(opts) {
34976
35038
  }
34977
35039
  async function writeAssetMetadata(opts) {
34978
35040
  const rel = assetMetadataPath(opts.kind, opts.beatId);
34979
- const abs = join11(opts.projectDir, rel);
35041
+ const abs = join12(opts.projectDir, rel);
34980
35042
  const metadata = {
34981
35043
  schemaVersion: "1",
34982
35044
  kind: opts.kind,
@@ -34989,7 +35051,7 @@ async function writeAssetMetadata(opts) {
34989
35051
  cachePath: opts.cachePath,
34990
35052
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
34991
35053
  };
34992
- await mkdir11(dirname6(abs), { recursive: true });
35054
+ await mkdir12(dirname6(abs), { recursive: true });
34993
35055
  await writeFile19(abs, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
34994
35056
  }
34995
35057
  var init_build_asset_metadata = __esm({
@@ -35000,20 +35062,20 @@ var init_build_asset_metadata = __esm({
35000
35062
 
35001
35063
  // src/commands/_shared/status-jobs.ts
35002
35064
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
35003
- import { existsSync as existsSync16, statSync } from "node:fs";
35004
- import { copyFile as copyFile3, mkdir as mkdir12, readFile as readFile18, readdir as readdir3, rename as rename2, stat as stat4, writeFile as writeFile20 } from "node:fs/promises";
35005
- import { dirname as dirname7, join as join12, parse as parse2, relative as relative6, resolve as resolve24 } from "node:path";
35065
+ import { existsSync as existsSync17, statSync } from "node:fs";
35066
+ import { copyFile as copyFile4, mkdir as mkdir13, readFile as readFile19, readdir as readdir3, rename as rename2, stat as stat4, writeFile as writeFile20 } from "node:fs/promises";
35067
+ import { dirname as dirname7, join as join13, parse as parse2, relative as relative6, resolve as resolve24 } from "node:path";
35006
35068
  function isLocalJob(record) {
35007
35069
  return record.jobType === "build" || record.jobType === "render";
35008
35070
  }
35009
35071
  function findProjectRoot(start = process.cwd()) {
35010
35072
  let dir = resolve24(start);
35011
35073
  try {
35012
- if (existsSync16(dir) && statSync(dir).isFile()) dir = dirname7(dir);
35074
+ if (existsSync17(dir) && statSync(dir).isFile()) dir = dirname7(dir);
35013
35075
  } catch {
35014
35076
  }
35015
35077
  for (; ; ) {
35016
- if (existsSync16(join12(dir, "vibe.config.json")) || existsSync16(join12(dir, "STORYBOARD.md"))) {
35078
+ if (existsSync17(join13(dir, "vibe.config.json")) || existsSync17(join13(dir, "STORYBOARD.md"))) {
35017
35079
  return dir;
35018
35080
  }
35019
35081
  const parent = dirname7(dir);
@@ -35022,7 +35084,7 @@ function findProjectRoot(start = process.cwd()) {
35022
35084
  }
35023
35085
  }
35024
35086
  function jobsDir(projectDir) {
35025
- return join12(resolve24(projectDir), ".vibeframe", "jobs");
35087
+ return join13(resolve24(projectDir), ".vibeframe", "jobs");
35026
35088
  }
35027
35089
  function normalizeJobStatus(status) {
35028
35090
  const value = String(status ?? "").toLowerCase();
@@ -35084,7 +35146,7 @@ async function createAndWriteJobRecord(opts) {
35084
35146
  }
35085
35147
  async function writeJobRecord(record) {
35086
35148
  const dir = jobsDir(record.projectDir);
35087
- await mkdir12(dir, { recursive: true });
35149
+ await mkdir13(dir, { recursive: true });
35088
35150
  const path = jobRecordPath(record.id, record.projectDir);
35089
35151
  const tmp = `${path}.${randomUUID2()}.tmp`;
35090
35152
  await writeFile20(tmp, JSON.stringify(stripUndefined2(record), null, 2) + "\n", "utf-8");
@@ -35094,18 +35156,18 @@ async function readJobRecord(jobId, projectDir) {
35094
35156
  const id = normalizeJobId(jobId);
35095
35157
  const project = resolve24(projectDir ?? findProjectRoot());
35096
35158
  const path = jobRecordPath(id, project);
35097
- if (!existsSync16(path)) return null;
35098
- return parseJobRecord(await readFile18(path, "utf-8"));
35159
+ if (!existsSync17(path)) return null;
35160
+ return parseJobRecord(await readFile19(path, "utf-8"));
35099
35161
  }
35100
35162
  async function listJobRecords(projectDir) {
35101
35163
  const dir = jobsDir(projectDir);
35102
- if (!existsSync16(dir)) return [];
35164
+ if (!existsSync17(dir)) return [];
35103
35165
  const entries = await readdir3(dir, { withFileTypes: true });
35104
35166
  const records = [];
35105
35167
  for (const entry of entries) {
35106
35168
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
35107
35169
  try {
35108
- const parsed = parseJobRecord(await readFile18(join12(dir, entry.name), "utf-8"));
35170
+ const parsed = parseJobRecord(await readFile19(join13(dir, entry.name), "utf-8"));
35109
35171
  if (parsed) records.push(parsed);
35110
35172
  } catch {
35111
35173
  }
@@ -35132,7 +35194,7 @@ async function refreshJobRecord(record, opts = {}) {
35132
35194
  try {
35133
35195
  if (record.jobType === "generate-video") {
35134
35196
  const output = resolveRefreshOutput(record, opts);
35135
- if (output) await mkdir12(dirname7(output), { recursive: true });
35197
+ if (output) await mkdir13(dirname7(output), { recursive: true });
35136
35198
  const result = await executeVideoStatus({
35137
35199
  taskId: record.providerTaskId,
35138
35200
  provider: record.provider,
@@ -35200,7 +35262,7 @@ async function refreshLocalJobRecord(record, opts) {
35200
35262
  const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
35201
35263
  const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
35202
35264
  if (orphaned) {
35203
- const hint = `The process that ran this ${record.jobType} job is gone. Check ${join12(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
35265
+ const hint = `The process that ran this ${record.jobType} job is gone. Check ${join13(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
35204
35266
  warnings.push(hint);
35205
35267
  updated = stripUndefined2({
35206
35268
  ...record,
@@ -35360,7 +35422,7 @@ function normalizeJobId(jobId) {
35360
35422
  return id;
35361
35423
  }
35362
35424
  function jobRecordPath(jobId, projectDir) {
35363
- return join12(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
35425
+ return join13(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
35364
35426
  }
35365
35427
  function parseJobRecord(raw) {
35366
35428
  const parsed = JSON.parse(raw);
@@ -35413,21 +35475,21 @@ async function downloadUrl(url, outputPath) {
35413
35475
  const response = await fetch(url);
35414
35476
  if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
35415
35477
  const buffer = Buffer.from(await response.arrayBuffer());
35416
- await mkdir12(dirname7(outputPath), { recursive: true });
35478
+ await mkdir13(dirname7(outputPath), { recursive: true });
35417
35479
  await writeFile20(outputPath, buffer);
35418
35480
  return outputPath;
35419
35481
  }
35420
35482
  async function maybeCacheOutput(record) {
35421
35483
  if (record.status !== "completed" || !record.outputPath || !record.cachePath) return;
35422
35484
  try {
35423
- await mkdir12(dirname7(record.cachePath), { recursive: true });
35424
- await copyFile3(record.outputPath, record.cachePath);
35485
+ await mkdir13(dirname7(record.cachePath), { recursive: true });
35486
+ await copyFile4(record.outputPath, record.cachePath);
35425
35487
  } catch {
35426
35488
  }
35427
35489
  }
35428
35490
  async function maybeWriteCompletedAssetMetadata(record) {
35429
35491
  const kind = assetKindForJob(record);
35430
- if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !existsSync16(record.outputPath)) {
35492
+ if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !existsSync17(record.outputPath)) {
35431
35493
  return;
35432
35494
  }
35433
35495
  try {
@@ -35446,7 +35508,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
35446
35508
  }
35447
35509
  }
35448
35510
  async function refreshBuildReportFromJobs(projectDir, records) {
35449
- const reportPath = join12(projectDir, "build-report.json");
35511
+ const reportPath = join13(projectDir, "build-report.json");
35450
35512
  const report = await readJson(reportPath);
35451
35513
  if (!report || !Array.isArray(report.beats)) return;
35452
35514
  let changed = false;
@@ -35499,7 +35561,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
35499
35561
  }
35500
35562
  }
35501
35563
  async function readBuildSummary(projectDir) {
35502
- const reportPath = join12(projectDir, "build-report.json");
35564
+ const reportPath = join13(projectDir, "build-report.json");
35503
35565
  const report = await readJson(reportPath);
35504
35566
  if (!report) return null;
35505
35567
  const info = await statOrNull(reportPath);
@@ -35522,7 +35584,7 @@ async function readBuildSummary(projectDir) {
35522
35584
  });
35523
35585
  }
35524
35586
  async function readReviewSummary(projectDir) {
35525
- const reportPath = join12(projectDir, "review-report.json");
35587
+ const reportPath = join13(projectDir, "review-report.json");
35526
35588
  const report = await readJson(reportPath);
35527
35589
  if (!report) return null;
35528
35590
  const info = await statOrNull(reportPath);
@@ -35611,8 +35673,8 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
35611
35673
  if (fromBeats) return fromBeats;
35612
35674
  if (build2.beats) return augmentBeatReadinessFromJobs(build2.beats, records, report?.jobs);
35613
35675
  }
35614
- const storyboardPath2 = join12(projectDir, "STORYBOARD.md");
35615
- if (!existsSync16(storyboardPath2)) {
35676
+ const storyboardPath2 = join13(projectDir, "STORYBOARD.md");
35677
+ if (!existsSync17(storyboardPath2)) {
35616
35678
  return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
35617
35679
  }
35618
35680
  try {
@@ -35620,7 +35682,7 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
35620
35682
  const needsAuthor = [];
35621
35683
  let compositionsReady = 0;
35622
35684
  for (const beat of parsed.beats) {
35623
- if (existsSync16(join12(projectDir, "compositions", `scene-${beat.id}.html`))) {
35685
+ if (existsSync17(join13(projectDir, "compositions", `scene-${beat.id}.html`))) {
35624
35686
  compositionsReady += 1;
35625
35687
  } else {
35626
35688
  needsAuthor.push(beat.id);
@@ -35638,7 +35700,7 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
35638
35700
  }
35639
35701
  function deriveProjectWorkflow(opts) {
35640
35702
  const retryWith = [];
35641
- const hasStoryboard = existsSync16(join12(opts.project, "STORYBOARD.md"));
35703
+ const hasStoryboard = existsSync17(join13(opts.project, "STORYBOARD.md"));
35642
35704
  if (!hasStoryboard && !opts.build) {
35643
35705
  retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
35644
35706
  return { status: "empty", currentStage: "init", retryWith };
@@ -35734,7 +35796,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
35734
35796
  if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
35735
35797
  assetsReady += 1;
35736
35798
  const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
35737
- if (compositionPath && existsSync16(resolve24(projectDir, compositionPath))) {
35799
+ if (compositionPath && existsSync17(resolve24(projectDir, compositionPath))) {
35738
35800
  compositionsReady += 1;
35739
35801
  } else if (id) {
35740
35802
  needsAuthor.push(id);
@@ -35829,7 +35891,7 @@ function projectRelativePath(projectDir, path) {
35829
35891
  }
35830
35892
  async function readJson(path) {
35831
35893
  try {
35832
- return JSON.parse(await readFile18(path, "utf-8"));
35894
+ return JSON.parse(await readFile19(path, "utf-8"));
35833
35895
  } catch {
35834
35896
  return null;
35835
35897
  }
@@ -35870,7 +35932,7 @@ var init_status_jobs = __esm({
35870
35932
  // src/commands/generate/music.ts
35871
35933
  import { resolve as resolve25 } from "node:path";
35872
35934
  import { writeFile as writeFile21 } from "node:fs/promises";
35873
- import { existsSync as existsSync17 } from "node:fs";
35935
+ import { existsSync as existsSync18 } from "node:fs";
35874
35936
  async function executeMusic(options) {
35875
35937
  try {
35876
35938
  const provider = options.provider || "elevenlabs";
@@ -35952,7 +36014,7 @@ function registerMusicCommand(parent) {
35952
36014
  exitWithError(usageError(`Invalid provider: ${provider}`, "Available providers: elevenlabs, replicate"));
35953
36015
  }
35954
36016
  if (options.melody) {
35955
- if (!existsSync17(resolve25(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
36017
+ if (!existsSync18(resolve25(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
35956
36018
  exitWithError(usageError("Melody conditioning requires a publicly accessible URL", "Please upload your melody file and provide the URL."));
35957
36019
  }
35958
36020
  if (options.dryRun) {
@@ -36047,8 +36109,8 @@ __export(ai_image_exports, {
36047
36109
  executeImageGenerate: () => executeImageGenerate,
36048
36110
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
36049
36111
  });
36050
- import { readFile as readFile19 } from "node:fs/promises";
36051
- import { existsSync as existsSync18 } from "node:fs";
36112
+ import { readFile as readFile20 } from "node:fs/promises";
36113
+ import { existsSync as existsSync19 } from "node:fs";
36052
36114
  function numberedPath(path, n) {
36053
36115
  const dot = path.lastIndexOf(".");
36054
36116
  return dot > path.lastIndexOf("/") ? `${path.slice(0, dot)}-${n}${path.slice(dot)}` : `${path}-${n}`;
@@ -36120,7 +36182,7 @@ async function executeThumbnailBestFrame(options) {
36120
36182
  model = "flash",
36121
36183
  apiKey
36122
36184
  } = options;
36123
- if (!existsSync18(videoPath)) {
36185
+ if (!existsSync19(videoPath)) {
36124
36186
  return { success: false, error: `Video not found: ${videoPath}` };
36125
36187
  }
36126
36188
  if (!commandExists("ffmpeg")) {
@@ -36133,7 +36195,7 @@ async function executeThumbnailBestFrame(options) {
36133
36195
  try {
36134
36196
  const gemini = new GeminiProvider();
36135
36197
  await gemini.initialize({ apiKey: googleKey });
36136
- const videoData = await readFile19(videoPath);
36198
+ const videoData = await readFile20(videoPath);
36137
36199
  const analysisPrompt = prompt3 || 'Analyze this video and find the single best frame for a thumbnail. Look for frames that are visually striking, well-composed, emotionally engaging, and representative of the video content. Avoid blurry frames, transitions, or dark scenes. Return ONLY a JSON object: {"timestamp": <seconds as number>, "reason": "<brief explanation>"}';
36138
36200
  const modelId = resolveGeminiTextModel(model);
36139
36201
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
@@ -36154,7 +36216,7 @@ async function executeThumbnailBestFrame(options) {
36154
36216
  reason = reasonMatch[1];
36155
36217
  }
36156
36218
  await execSafe("ffmpeg", ["-ss", String(timestamp2), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
36157
- if (!existsSync18(outputPath)) {
36219
+ if (!existsSync19(outputPath)) {
36158
36220
  return { success: false, error: "FFmpeg failed to extract frame" };
36159
36221
  }
36160
36222
  return {
@@ -36181,9 +36243,9 @@ var init_ai_image = __esm({
36181
36243
  });
36182
36244
 
36183
36245
  // src/commands/generate/thumbnail.ts
36184
- import { resolve as resolve26, dirname as dirname8, basename as basename6, extname as extname5 } from "node:path";
36185
- import { existsSync as existsSync19 } from "node:fs";
36186
- import { writeFile as writeFile22, mkdir as mkdir13 } from "node:fs/promises";
36246
+ import { resolve as resolve26, dirname as dirname8, basename as basename7, extname as extname5 } from "node:path";
36247
+ import { existsSync as existsSync20 } from "node:fs";
36248
+ import { writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
36187
36249
  function registerThumbnailCommand(parent) {
36188
36250
  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) => {
36189
36251
  const startedAt = Date.now();
@@ -36194,7 +36256,7 @@ function registerThumbnailCommand(parent) {
36194
36256
  }
36195
36257
  if (options.bestFrame) {
36196
36258
  const absVideoPath = resolve26(process.cwd(), options.bestFrame);
36197
- if (!existsSync19(absVideoPath)) {
36259
+ if (!existsSync20(absVideoPath)) {
36198
36260
  exitWithError(notFoundError(absVideoPath));
36199
36261
  }
36200
36262
  if (!commandExists("ffmpeg")) {
@@ -36206,7 +36268,7 @@ function registerThumbnailCommand(parent) {
36206
36268
  );
36207
36269
  }
36208
36270
  const apiKey2 = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
36209
- const name = basename6(options.bestFrame, extname5(options.bestFrame));
36271
+ const name = basename7(options.bestFrame, extname5(options.bestFrame));
36210
36272
  const outputPath = options.output || `${name}-thumbnail.png`;
36211
36273
  const spinner3 = ora("Analyzing video for best frame...").start();
36212
36274
  const result2 = await executeThumbnailBestFrame({
@@ -36274,7 +36336,7 @@ function registerThumbnailCommand(parent) {
36274
36336
  throw new Error("No image data available");
36275
36337
  }
36276
36338
  outputPath = resolve26(process.cwd(), options.output);
36277
- await mkdir13(dirname8(outputPath), { recursive: true });
36339
+ await mkdir14(dirname8(outputPath), { recursive: true });
36278
36340
  await writeFile22(outputPath, buffer);
36279
36341
  }
36280
36342
  outputSuccess({
@@ -36305,7 +36367,7 @@ function registerThumbnailCommand(parent) {
36305
36367
  throw new Error("No image data available");
36306
36368
  }
36307
36369
  const outputPath = resolve26(process.cwd(), options.output);
36308
- await mkdir13(dirname8(outputPath), { recursive: true });
36370
+ await mkdir14(dirname8(outputPath), { recursive: true });
36309
36371
  await writeFile22(outputPath, buffer);
36310
36372
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
36311
36373
  } catch {
@@ -37567,7 +37629,7 @@ var init_dist3 = __esm({
37567
37629
 
37568
37630
  // src/commands/generate/video.ts
37569
37631
  import { resolve as resolve27 } from "node:path";
37570
- import { readFile as readFile20 } from "node:fs/promises";
37632
+ import { readFile as readFile21 } from "node:fs/promises";
37571
37633
  function registerVideoCommand(parent) {
37572
37634
  parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, Veo, or Gemini Omni)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
37573
37635
  "-p, --provider <provider>",
@@ -37674,7 +37736,7 @@ Examples:
37674
37736
  provider = resolved?.name ?? "grok";
37675
37737
  }
37676
37738
  if (options.image) {
37677
- const imageBuffer = await readFile20(resolve27(process.cwd(), options.image));
37739
+ const imageBuffer = await readFile21(resolve27(process.cwd(), options.image));
37678
37740
  if (!options.ratio) {
37679
37741
  const dimensions = imageSize(imageBuffer);
37680
37742
  if (dimensions.width && dimensions.height) {
@@ -38076,8 +38138,8 @@ var init_ai_helpers = __esm({
38076
38138
  });
38077
38139
 
38078
38140
  // src/commands/_shared/edit/silence-cut.ts
38079
- import { existsSync as existsSync20 } from "node:fs";
38080
- import { readFile as readFile21 } from "node:fs/promises";
38141
+ import { existsSync as existsSync21 } from "node:fs";
38142
+ import { readFile as readFile22 } from "node:fs/promises";
38081
38143
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
38082
38144
  const totalDuration = await getVideoDuration(videoPath);
38083
38145
  const { stdout, stderr } = await execSafe(
@@ -38127,7 +38189,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
38127
38189
  }
38128
38190
  const gemini = new GeminiProvider();
38129
38191
  await gemini.initialize({ apiKey: geminiApiKey });
38130
- const videoBuffer = await readFile21(videoPath);
38192
+ const videoBuffer = await readFile22(videoPath);
38131
38193
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
38132
38194
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
38133
38195
 
@@ -38203,7 +38265,7 @@ async function executeSilenceCut(options) {
38203
38265
  analyzeOnly = false,
38204
38266
  useGemini = false
38205
38267
  } = options;
38206
- if (!existsSync20(videoPath)) {
38268
+ if (!existsSync21(videoPath)) {
38207
38269
  return { success: false, error: `Video not found: ${videoPath}` };
38208
38270
  }
38209
38271
  if (!commandExists("ffmpeg")) {
@@ -38311,9 +38373,9 @@ var init_silence_cut = __esm({
38311
38373
  });
38312
38374
 
38313
38375
  // src/commands/_shared/edit/jump-cut.ts
38314
- import { existsSync as existsSync21 } from "node:fs";
38315
- import { writeFile as writeFile24, mkdir as mkdir14 } from "node:fs/promises";
38316
- import { join as join13 } from "node:path";
38376
+ import { existsSync as existsSync22 } from "node:fs";
38377
+ import { writeFile as writeFile24, mkdir as mkdir15 } from "node:fs/promises";
38378
+ import { join as join14 } from "node:path";
38317
38379
  async function transcribeWithWords(audioPath, apiKey, language) {
38318
38380
  const result = await transcribeAudioFile(audioPath, { apiKey, language, granularity: "word" });
38319
38381
  if (result.status !== "completed") {
@@ -38356,7 +38418,7 @@ async function executeJumpCut(options) {
38356
38418
  analyzeOnly = false,
38357
38419
  apiKey
38358
38420
  } = options;
38359
- if (!existsSync21(videoPath)) {
38421
+ if (!existsSync22(videoPath)) {
38360
38422
  return { success: false, error: `Video not found: ${videoPath}` };
38361
38423
  }
38362
38424
  if (!commandExists("ffmpeg")) {
@@ -38374,8 +38436,8 @@ async function executeJumpCut(options) {
38374
38436
  }
38375
38437
  try {
38376
38438
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
38377
- await mkdir14(tmpDir, { recursive: true });
38378
- const audioPath = join13(tmpDir, "audio.wav");
38439
+ await mkdir15(tmpDir, { recursive: true });
38440
+ const audioPath = join14(tmpDir, "audio.wav");
38379
38441
  try {
38380
38442
  await execSafe(
38381
38443
  "ffmpeg",
@@ -38429,7 +38491,7 @@ async function executeJumpCut(options) {
38429
38491
  const segmentPaths = [];
38430
38492
  for (let i = 0; i < segments.length; i++) {
38431
38493
  const seg = segments[i];
38432
- const segPath = join13(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
38494
+ const segPath = join14(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
38433
38495
  const duration = seg.end - seg.start;
38434
38496
  await execSafe(
38435
38497
  "ffmpeg",
@@ -38452,7 +38514,7 @@ async function executeJumpCut(options) {
38452
38514
  segmentPaths.push(segPath);
38453
38515
  }
38454
38516
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
38455
- const listPath = join13(tmpDir, "concat.txt");
38517
+ const listPath = join14(tmpDir, "concat.txt");
38456
38518
  await writeFile24(listPath, concatList);
38457
38519
  await execSafe(
38458
38520
  "ffmpeg",
@@ -38643,9 +38705,9 @@ var init_helpers = __esm({
38643
38705
  });
38644
38706
 
38645
38707
  // src/commands/_shared/edit/caption.ts
38646
- import { existsSync as existsSync22 } from "node:fs";
38647
- import { writeFile as writeFile25, mkdir as mkdir15 } from "node:fs/promises";
38648
- import { dirname as dirname9, basename as basename7, extname as extname6, join as join14 } from "node:path";
38708
+ import { existsSync as existsSync23 } from "node:fs";
38709
+ import { writeFile as writeFile25, mkdir as mkdir16 } from "node:fs/promises";
38710
+ import { dirname as dirname9, basename as basename8, extname as extname6, join as join15 } from "node:path";
38649
38711
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
38650
38712
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
38651
38713
  const marginV = position === "center" ? 0 : 30;
@@ -38673,7 +38735,7 @@ async function executeCaption(options) {
38673
38735
  position = "bottom",
38674
38736
  apiKey
38675
38737
  } = options;
38676
- if (!existsSync22(videoPath)) {
38738
+ if (!existsSync23(videoPath)) {
38677
38739
  return { success: false, error: `Video not found: ${videoPath}` };
38678
38740
  }
38679
38741
  if (!commandExists("ffmpeg")) {
@@ -38691,9 +38753,9 @@ async function executeCaption(options) {
38691
38753
  }
38692
38754
  try {
38693
38755
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
38694
- await mkdir15(tmpDir, { recursive: true });
38695
- const audioPath = join14(tmpDir, "audio.wav");
38696
- const srtPath = join14(tmpDir, "captions.srt");
38756
+ await mkdir16(tmpDir, { recursive: true });
38757
+ const audioPath = join15(tmpDir, "audio.wav");
38758
+ const srtPath = join15(tmpDir, "captions.srt");
38697
38759
  try {
38698
38760
  await execSafe(
38699
38761
  "ffmpeg",
@@ -38758,9 +38820,9 @@ async function executeCaption(options) {
38758
38820
  const remotionErr = await ensureRemotionInstalled2();
38759
38821
  if (remotionErr) {
38760
38822
  const outputDir2 = dirname9(outputPath);
38761
- const outputSrtPath2 = join14(
38823
+ const outputSrtPath2 = join15(
38762
38824
  outputDir2,
38763
- basename7(outputPath, extname6(outputPath)) + ".srt"
38825
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38764
38826
  );
38765
38827
  await writeFile25(outputSrtPath2, srtContent);
38766
38828
  return { success: false, error: `${remotionErr}
@@ -38797,9 +38859,9 @@ SRT saved to: ${outputSrtPath2}` };
38797
38859
  });
38798
38860
  if (!renderResult.success) {
38799
38861
  const outputDir2 = dirname9(outputPath);
38800
- const outputSrtPath2 = join14(
38862
+ const outputSrtPath2 = join15(
38801
38863
  outputDir2,
38802
- basename7(outputPath, extname6(outputPath)) + ".srt"
38864
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38803
38865
  );
38804
38866
  await writeFile25(outputSrtPath2, srtContent);
38805
38867
  return {
@@ -38810,9 +38872,9 @@ SRT saved to: ${outputSrtPath2}`
38810
38872
  }
38811
38873
  }
38812
38874
  const outputDir = dirname9(outputPath);
38813
- const outputSrtPath = join14(
38875
+ const outputSrtPath = join15(
38814
38876
  outputDir,
38815
- basename7(outputPath, extname6(outputPath)) + ".srt"
38877
+ basename8(outputPath, extname6(outputPath)) + ".srt"
38816
38878
  );
38817
38879
  await writeFile25(outputSrtPath, srtContent);
38818
38880
  return {
@@ -38847,10 +38909,10 @@ var init_caption = __esm({
38847
38909
  });
38848
38910
 
38849
38911
  // src/commands/_shared/edit/noise-reduce.ts
38850
- import { existsSync as existsSync23 } from "node:fs";
38912
+ import { existsSync as existsSync24 } from "node:fs";
38851
38913
  async function executeNoiseReduce(options) {
38852
38914
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
38853
- if (!existsSync23(inputPath)) {
38915
+ if (!existsSync24(inputPath)) {
38854
38916
  return { success: false, error: `File not found: ${inputPath}` };
38855
38917
  }
38856
38918
  if (!commandExists("ffmpeg")) {
@@ -38907,7 +38969,7 @@ var init_noise_reduce = __esm({
38907
38969
  });
38908
38970
 
38909
38971
  // src/commands/_shared/edit/fade.ts
38910
- import { existsSync as existsSync24 } from "node:fs";
38972
+ import { existsSync as existsSync25 } from "node:fs";
38911
38973
  async function executeFade(options) {
38912
38974
  const {
38913
38975
  videoPath,
@@ -38917,7 +38979,7 @@ async function executeFade(options) {
38917
38979
  audioOnly = false,
38918
38980
  videoOnly = false
38919
38981
  } = options;
38920
- if (!existsSync24(videoPath)) {
38982
+ if (!existsSync25(videoPath)) {
38921
38983
  return { success: false, error: `Video not found: ${videoPath}` };
38922
38984
  }
38923
38985
  if (!commandExists("ffmpeg")) {
@@ -39060,8 +39122,8 @@ var init_translate_texts = __esm({
39060
39122
  });
39061
39123
 
39062
39124
  // src/commands/_shared/edit/translate-srt.ts
39063
- import { existsSync as existsSync25 } from "node:fs";
39064
- import { readFile as readFile22, writeFile as writeFile26 } from "node:fs/promises";
39125
+ import { existsSync as existsSync26 } from "node:fs";
39126
+ import { readFile as readFile23, writeFile as writeFile26 } from "node:fs/promises";
39065
39127
  async function executeTranslateSrt(options) {
39066
39128
  const {
39067
39129
  srtPath,
@@ -39071,11 +39133,11 @@ async function executeTranslateSrt(options) {
39071
39133
  sourceLanguage,
39072
39134
  apiKey
39073
39135
  } = options;
39074
- if (!existsSync25(srtPath)) {
39136
+ if (!existsSync26(srtPath)) {
39075
39137
  return { success: false, error: `SRT file not found: ${srtPath}` };
39076
39138
  }
39077
39139
  try {
39078
- const srtContent = await readFile22(srtPath, "utf-8");
39140
+ const srtContent = await readFile23(srtPath, "utf-8");
39079
39141
  const segments = parseSRT(srtContent);
39080
39142
  if (segments.length === 0) {
39081
39143
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -39115,7 +39177,7 @@ var init_translate_srt = __esm({
39115
39177
  });
39116
39178
 
39117
39179
  // src/commands/_shared/edit/text-overlay.ts
39118
- import { existsSync as existsSync26 } from "node:fs";
39180
+ import { existsSync as existsSync27 } from "node:fs";
39119
39181
  import { resolve as resolve29 } from "node:path";
39120
39182
  function detectSystemFont() {
39121
39183
  const platform3 = process.platform;
@@ -39126,7 +39188,7 @@ function detectSystemFont() {
39126
39188
  "/Library/Fonts/Arial.ttf"
39127
39189
  ];
39128
39190
  for (const f of candidates) {
39129
- if (existsSync26(f)) return f;
39191
+ if (existsSync27(f)) return f;
39130
39192
  }
39131
39193
  } else if (platform3 === "linux") {
39132
39194
  const candidates = [
@@ -39135,7 +39197,7 @@ function detectSystemFont() {
39135
39197
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
39136
39198
  ];
39137
39199
  for (const f of candidates) {
39138
- if (existsSync26(f)) return f;
39200
+ if (existsSync27(f)) return f;
39139
39201
  }
39140
39202
  } else if (platform3 === "win32") {
39141
39203
  const candidates = [
@@ -39143,7 +39205,7 @@ function detectSystemFont() {
39143
39205
  "C:\\Windows\\Fonts\\segoeui.ttf"
39144
39206
  ];
39145
39207
  for (const f of candidates) {
39146
- if (existsSync26(f)) return f;
39208
+ if (existsSync27(f)) return f;
39147
39209
  }
39148
39210
  }
39149
39211
  return null;
@@ -39164,7 +39226,7 @@ async function applyTextOverlays(options) {
39164
39226
  }
39165
39227
  const absVideoPath = resolve29(process.cwd(), videoPath);
39166
39228
  const absOutputPath = resolve29(process.cwd(), outputPath);
39167
- if (!existsSync26(absVideoPath)) {
39229
+ if (!existsSync27(absVideoPath)) {
39168
39230
  return { success: false, error: `Video not found: ${absVideoPath}` };
39169
39231
  }
39170
39232
  if (!commandExists("ffmpeg")) {
@@ -39344,8 +39406,8 @@ var init_ai_edit = __esm({
39344
39406
  });
39345
39407
 
39346
39408
  // src/commands/ai-edit-cli.ts
39347
- import { resolve as resolve30, extname as extname7, basename as basename8 } from "node:path";
39348
- import { existsSync as existsSync27 } from "node:fs";
39409
+ import { resolve as resolve30, extname as extname7, basename as basename9 } from "node:path";
39410
+ import { existsSync as existsSync28 } from "node:fs";
39349
39411
  function registerEditCommands(aiCommand) {
39350
39412
  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", `
39351
39413
  Examples:
@@ -39362,14 +39424,14 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
39362
39424
  validateOutputPath(options.output);
39363
39425
  }
39364
39426
  const absVideoPath = resolve30(process.cwd(), videoPath);
39365
- if (!existsSync27(absVideoPath)) {
39427
+ if (!existsSync28(absVideoPath)) {
39366
39428
  exitWithError(notFoundError(absVideoPath));
39367
39429
  }
39368
39430
  if (!commandExists("ffmpeg")) {
39369
39431
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
39370
39432
  }
39371
39433
  const ext = extname7(videoPath);
39372
- const name = basename8(videoPath, ext);
39434
+ const name = basename9(videoPath, ext);
39373
39435
  const outputPath = options.output || `${name}-cut${ext}`;
39374
39436
  const useGemini = options.useGemini || false;
39375
39437
  if (options.analyzeOnly) {
@@ -39470,7 +39532,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39470
39532
  validateOutputPath(options.output);
39471
39533
  }
39472
39534
  const absVideoPath = resolve30(process.cwd(), videoPath);
39473
- if (!existsSync27(absVideoPath)) {
39535
+ if (!existsSync28(absVideoPath)) {
39474
39536
  exitWithError(notFoundError(absVideoPath));
39475
39537
  }
39476
39538
  if (!commandExists("ffmpeg")) {
@@ -39499,7 +39561,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39499
39561
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39500
39562
  }
39501
39563
  const ext = extname7(videoPath);
39502
- const name = basename8(videoPath, ext);
39564
+ const name = basename9(videoPath, ext);
39503
39565
  const outputPath = options.output || `${name}-captioned${ext}`;
39504
39566
  const spinner2 = ora("Starting caption process...").start();
39505
39567
  const result = await executeCaption({
@@ -39551,7 +39613,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39551
39613
  validateOutputPath(options.output);
39552
39614
  }
39553
39615
  const absInputPath = resolve30(process.cwd(), inputPath);
39554
- if (!existsSync27(absInputPath)) {
39616
+ if (!existsSync28(absInputPath)) {
39555
39617
  exitWithError(notFoundError(absInputPath));
39556
39618
  }
39557
39619
  if (!commandExists("ffmpeg")) {
@@ -39573,7 +39635,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39573
39635
  return;
39574
39636
  }
39575
39637
  const ext = extname7(inputPath);
39576
- const name = basename8(inputPath, ext);
39638
+ const name = basename9(inputPath, ext);
39577
39639
  const outputPath = options.output || `${name}-denoised${ext}`;
39578
39640
  const spinner2 = ora("Applying noise reduction...").start();
39579
39641
  const result = await executeNoiseReduce({
@@ -39617,7 +39679,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39617
39679
  validateOutputPath(options.output);
39618
39680
  }
39619
39681
  const absVideoPath = resolve30(process.cwd(), videoPath);
39620
- if (!existsSync27(absVideoPath)) {
39682
+ if (!existsSync28(absVideoPath)) {
39621
39683
  exitWithError(notFoundError(absVideoPath));
39622
39684
  }
39623
39685
  if (!commandExists("ffmpeg")) {
@@ -39641,7 +39703,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39641
39703
  return;
39642
39704
  }
39643
39705
  const ext = extname7(videoPath);
39644
- const name = basename8(videoPath, ext);
39706
+ const name = basename9(videoPath, ext);
39645
39707
  const outputPath = options.output || `${name}-faded${ext}`;
39646
39708
  const spinner2 = ora("Applying fade effects...").start();
39647
39709
  const result = await executeFade({
@@ -39692,7 +39754,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39692
39754
  exitWithError(usageError("Target language required. Use -t or --target"));
39693
39755
  }
39694
39756
  const absSrtPath = resolve30(process.cwd(), srtPath);
39695
- if (!existsSync27(absSrtPath)) {
39757
+ if (!existsSync28(absSrtPath)) {
39696
39758
  exitWithError(notFoundError(absSrtPath));
39697
39759
  }
39698
39760
  if (options.dryRun) {
@@ -39719,7 +39781,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39719
39781
  exitWithError(authError(envKey, providerName));
39720
39782
  }
39721
39783
  const ext = extname7(srtPath);
39722
- const name = basename8(srtPath, ext);
39784
+ const name = basename9(srtPath, ext);
39723
39785
  const outputPath = options.output || `${name}-${options.target}${ext}`;
39724
39786
  const spinner2 = ora(`Translating to ${options.target}...`).start();
39725
39787
  const result = await executeTranslateSrt({
@@ -39768,7 +39830,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39768
39830
  }
39769
39831
  if (options.fillers) rejectControlChars(options.fillers);
39770
39832
  const absVideoPath = resolve30(process.cwd(), videoPath);
39771
- if (!existsSync27(absVideoPath)) {
39833
+ if (!existsSync28(absVideoPath)) {
39772
39834
  exitWithError(notFoundError(absVideoPath));
39773
39835
  }
39774
39836
  if (!commandExists("ffmpeg")) {
@@ -39797,7 +39859,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39797
39859
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39798
39860
  }
39799
39861
  const ext = extname7(videoPath);
39800
- const name = basename8(videoPath, ext);
39862
+ const name = basename9(videoPath, ext);
39801
39863
  const outputPath = options.output || `${name}-jumpcut${ext}`;
39802
39864
  const fillers = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
39803
39865
  const spinner2 = ora("Transcribing with word-level timestamps...").start();
@@ -39868,8 +39930,8 @@ var init_ai_edit_cli = __esm({
39868
39930
  });
39869
39931
 
39870
39932
  // src/commands/_shared/execute-fill-gaps.ts
39871
- import { existsSync as existsSync28 } from "node:fs";
39872
- import { readFile as readFile23, writeFile as writeFile27, mkdir as mkdir16, rename as renameFs } from "node:fs/promises";
39933
+ import { existsSync as existsSync29 } from "node:fs";
39934
+ import { readFile as readFile24, writeFile as writeFile27, mkdir as mkdir17, rename as renameFs } from "node:fs/promises";
39873
39935
  import { resolve as resolve31, dirname as dirname10 } from "node:path";
39874
39936
  function detectVideoGaps(videoClips, totalDuration) {
39875
39937
  const gaps = [];
@@ -39926,7 +39988,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
39926
39988
  }
39927
39989
  async function uploadFrame(host, framePath) {
39928
39990
  try {
39929
- const upload = await host.uploadImage(await readFile23(framePath), {
39991
+ const upload = await host.uploadImage(await readFile24(framePath), {
39930
39992
  filename: framePath,
39931
39993
  mimeType: "image/png"
39932
39994
  });
@@ -39959,14 +40021,14 @@ async function executeFillGaps(options) {
39959
40021
  try {
39960
40022
  onProgress("Loading timeline...");
39961
40023
  const filePath = await resolveTimelineFile(options.projectPath);
39962
- if (!existsSync28(filePath)) {
40024
+ if (!existsSync29(filePath)) {
39963
40025
  return {
39964
40026
  success: false,
39965
40027
  error: `Timeline file not found: ${filePath}`,
39966
40028
  humanLines
39967
40029
  };
39968
40030
  }
39969
- const content = await readFile23(filePath, "utf-8");
40031
+ const content = await readFile24(filePath, "utf-8");
39970
40032
  const data = JSON.parse(content);
39971
40033
  const project = Project.fromJSON(data);
39972
40034
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -40086,8 +40148,8 @@ async function executeFillGaps(options) {
40086
40148
  const kling = await openVideoGenerator("kling", apiKey);
40087
40149
  const projectDir = dirname10(filePath);
40088
40150
  const footageDir = options.dir ? resolve31(process.cwd(), options.dir) : resolve31(projectDir, "footage");
40089
- if (!existsSync28(footageDir)) {
40090
- await mkdir16(footageDir, { recursive: true });
40151
+ if (!existsSync29(footageDir)) {
40152
+ await mkdir17(footageDir, { recursive: true });
40091
40153
  }
40092
40154
  humanLines.push("Generating AI Videos");
40093
40155
  let uploadHost;
@@ -40378,15 +40440,15 @@ var init_ai_fill_gaps = __esm({
40378
40440
  });
40379
40441
 
40380
40442
  // src/pipeline/renderers/chrome.ts
40381
- import { existsSync as existsSync29 } from "node:fs";
40443
+ import { existsSync as existsSync30 } from "node:fs";
40382
40444
  import { homedir as homedir4 } from "node:os";
40383
- import { join as join15 } from "node:path";
40445
+ import { join as join16 } from "node:path";
40384
40446
  function findChrome() {
40385
40447
  const candidates = [
40386
40448
  process.env.HYPERFRAMES_CHROME_PATH,
40387
40449
  process.env.CHROME_PATH,
40388
40450
  // puppeteer auto-downloaded headless shell
40389
- join15(
40451
+ join16(
40390
40452
  homedir4(),
40391
40453
  ".cache",
40392
40454
  "puppeteer",
@@ -40404,7 +40466,7 @@ function findChrome() {
40404
40466
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
40405
40467
  ];
40406
40468
  for (const c of candidates) {
40407
- if (c && existsSync29(c)) return c;
40469
+ if (c && existsSync30(c)) return c;
40408
40470
  }
40409
40471
  return void 0;
40410
40472
  }
@@ -40436,10 +40498,9 @@ var init_stdout_guard = __esm({
40436
40498
  });
40437
40499
 
40438
40500
  // src/commands/edit/lottie-overlay.ts
40439
- import { createRequire } from "node:module";
40440
- import { copyFile as copyFile4, mkdir as mkdir17, mkdtemp, readFile as readFile24, rm as rm5, writeFile as writeFile28 } from "node:fs/promises";
40501
+ import { copyFile as copyFile5, mkdir as mkdir18, mkdtemp, rm as rm5, writeFile as writeFile28 } from "node:fs/promises";
40441
40502
  import { tmpdir as tmpdir2 } from "node:os";
40442
- import { basename as basename9, extname as extname8, join as join16 } from "node:path";
40503
+ import { basename as basename10, extname as extname8, join as join17 } from "node:path";
40443
40504
  async function probeVideoStream(path) {
40444
40505
  const { stdout } = await execSafe("ffprobe", [
40445
40506
  "-v",
@@ -40466,27 +40527,6 @@ async function probeVideoStream(path) {
40466
40527
  duration: Number(info.format?.duration ?? 0)
40467
40528
  };
40468
40529
  }
40469
- async function checkLottieAsset(path) {
40470
- const bytes = await readFile24(path);
40471
- const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
40472
- if (isZip) return void 0;
40473
- try {
40474
- const json = JSON.parse(bytes.toString("utf-8"));
40475
- if (Array.isArray(json.layers)) return void 0;
40476
- } catch {
40477
- }
40478
- return `${basename9(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
40479
- }
40480
- function overlayBox(layer, position, scale) {
40481
- if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
40482
- const width = Math.max(1, Math.round(layer.width * scale));
40483
- const height = Math.max(1, Math.round(layer.height * scale));
40484
- const marginX = Math.round(layer.width * 0.04);
40485
- const marginY = Math.round(layer.height * 0.04);
40486
- const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
40487
- const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
40488
- return { left, top, width, height };
40489
- }
40490
40530
  function lottieLayerHtml(opts) {
40491
40531
  const { box } = opts;
40492
40532
  return `<!DOCTYPE html>
@@ -40499,11 +40539,8 @@ function lottieLayerHtml(opts) {
40499
40539
  </head><body>
40500
40540
  <canvas id="lottie" width="${box.width}" height="${box.height}"></canvas>
40501
40541
  <script type="module">
40502
- import { DotLottie } from "./vendor/dotlottie-web.js";
40503
- // The render server sends .wasm without application/wasm, which makes
40504
- // WebAssembly.instantiateStreaming reject it; a typed Blob URL avoids that.
40505
- const wasm = await (await fetch("./vendor/dotlottie-player.wasm")).arrayBuffer();
40506
- DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
40542
+ ${DOTLOTTIE_BROWSER_HELPERS}
40543
+ const DotLottie = await loadDotLottie("./vendor");
40507
40544
  const LOOP = ${opts.loop};
40508
40545
  const DURATION = ${opts.duration};
40509
40546
  const player = new DotLottie({
@@ -40520,12 +40557,7 @@ function lottieLayerHtml(opts) {
40520
40557
  window.__hf = {
40521
40558
  duration: DURATION,
40522
40559
  seek(t) {
40523
- const length = player.duration;
40524
- const frames = player.totalFrames;
40525
- if (!(length > 0) || !(frames > 0)) return;
40526
- const local = Math.max(0, Number(t) || 0);
40527
- const at = LOOP ? local % length : Math.min(local, length);
40528
- player.setFrame(Math.min(frames - 1, (at / length) * frames));
40560
+ setLottieTime(player, t, LOOP);
40529
40561
  },
40530
40562
  };
40531
40563
  window.__hf.seek(0);
@@ -40534,21 +40566,12 @@ function lottieLayerHtml(opts) {
40534
40566
  </body></html>
40535
40567
  `;
40536
40568
  }
40537
- function vendorPaths() {
40538
- const require2 = createRequire(import.meta.url);
40539
- const script = require2.resolve("@lottiefiles/dotlottie-web");
40540
- const dist = join16(script, "..");
40541
- return { script: join16(dist, "index.js"), wasm: join16(dist, "dotlottie-player.wasm") };
40542
- }
40543
40569
  async function renderLayer(opts) {
40544
40570
  const { dir, info, options } = opts;
40545
- const vendor = vendorPaths();
40546
- await mkdir17(join16(dir, "vendor"), { recursive: true });
40547
- await mkdir17(join16(dir, "assets"), { recursive: true });
40548
- await copyFile4(vendor.script, join16(dir, "vendor", "dotlottie-web.js"));
40549
- await copyFile4(vendor.wasm, join16(dir, "vendor", "dotlottie-player.wasm"));
40571
+ await copyDotLottieRuntime(join17(dir, "vendor"));
40572
+ await mkdir18(join17(dir, "assets"), { recursive: true });
40550
40573
  const assetName = `animation${extname8(options.assetPath).toLowerCase()}`;
40551
- await copyFile4(options.assetPath, join16(dir, "assets", assetName));
40574
+ await copyFile5(options.assetPath, join17(dir, "assets", assetName));
40552
40575
  const html = lottieLayerHtml({
40553
40576
  width: info.width,
40554
40577
  height: info.height,
@@ -40558,8 +40581,8 @@ async function renderLayer(opts) {
40558
40581
  opacity: options.opacity,
40559
40582
  loop: options.loop
40560
40583
  });
40561
- await writeFile28(join16(dir, "index.html"), html, "utf-8");
40562
- const layerPath = join16(dir, "layer.mov");
40584
+ await writeFile28(join17(dir, "index.html"), html, "utf-8");
40585
+ const layerPath = join17(dir, "layer.mov");
40563
40586
  const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await import("@hyperframes/producer");
40564
40587
  const job = createRenderJob2({
40565
40588
  fps: Math.min(60, Math.max(1, Math.round(info.fps))),
@@ -40612,7 +40635,7 @@ async function renderLottieOverlay(options) {
40612
40635
  const assetProblem = await checkLottieAsset(options.assetPath);
40613
40636
  if (assetProblem) return { success: false, error: assetProblem };
40614
40637
  const info = await probeVideoStream(options.videoPath);
40615
- const dir = await mkdtemp(join16(tmpdir2(), "vibeframe-lottie-"));
40638
+ const dir = await mkdtemp(join17(tmpdir2(), "vibeframe-lottie-"));
40616
40639
  try {
40617
40640
  let layerPath;
40618
40641
  try {
@@ -40622,7 +40645,7 @@ async function renderLottieOverlay(options) {
40622
40645
  if (/__hf not ready/.test(message)) {
40623
40646
  return {
40624
40647
  success: false,
40625
- error: `Could not load the Lottie animation ${basename9(options.assetPath)}.`,
40648
+ error: `Could not load the Lottie animation ${basename10(options.assetPath)}.`,
40626
40649
  suggestion: "Check that it opens in a Lottie player (https://lottiefiles.com/preview)."
40627
40650
  };
40628
40651
  }
@@ -40643,6 +40666,7 @@ var init_lottie_overlay = __esm({
40643
40666
  init_chrome();
40644
40667
  init_exec_safe();
40645
40668
  init_stdout_guard();
40669
+ init_lottie();
40646
40670
  FFMPEG_MISSING = "FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details.";
40647
40671
  }
40648
40672
  });
@@ -40653,8 +40677,8 @@ __export(motion_overlay_exports, {
40653
40677
  executeMotionOverlay: () => executeMotionOverlay,
40654
40678
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
40655
40679
  });
40656
- import { mkdir as mkdir18 } from "node:fs/promises";
40657
- import { existsSync as existsSync30 } from "node:fs";
40680
+ import { mkdir as mkdir19 } from "node:fs/promises";
40681
+ import { existsSync as existsSync31 } from "node:fs";
40658
40682
  import { dirname as dirname11, extname as extname9, resolve as resolve32 } from "node:path";
40659
40683
  function normalizeUnderstand2(value) {
40660
40684
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -40689,11 +40713,11 @@ async function executeMotionOverlay(options) {
40689
40713
  const validationError = assertValidRequest(options);
40690
40714
  if (validationError) return { success: false, error: validationError };
40691
40715
  const absVideo = resolve32(process.cwd(), options.videoPath);
40692
- if (!existsSync30(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
40716
+ if (!existsSync31(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
40693
40717
  const outputPath = options.output ? resolve32(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
40694
40718
  if (options.asset) {
40695
40719
  const absAsset = resolve32(process.cwd(), options.asset);
40696
- if (!existsSync30(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
40720
+ if (!existsSync31(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
40697
40721
  const videoDuration = await ffprobeDuration(absVideo);
40698
40722
  const start = Math.max(0, options.start ?? 0);
40699
40723
  if (start >= videoDuration) {
@@ -40701,7 +40725,7 @@ async function executeMotionOverlay(options) {
40701
40725
  }
40702
40726
  const remaining = videoDuration - start;
40703
40727
  const position = normalizePosition(options.position);
40704
- await mkdir18(dirname11(outputPath), { recursive: true });
40728
+ await mkdir19(dirname11(outputPath), { recursive: true });
40705
40729
  const result2 = await renderLottieOverlay({
40706
40730
  videoPath: absVideo,
40707
40731
  assetPath: absAsset,
@@ -40719,7 +40743,7 @@ async function executeMotionOverlay(options) {
40719
40743
  return { success: true, outputPath, provider: "lottie" };
40720
40744
  }
40721
40745
  const duration = options.duration ?? await ffprobeDuration(absVideo).catch(() => void 0);
40722
- await mkdir18(dirname11(outputPath), { recursive: true });
40746
+ await mkdir19(dirname11(outputPath), { recursive: true });
40723
40747
  const result = await executeMotion({
40724
40748
  description: options.description.trim(),
40725
40749
  video: absVideo,
@@ -41753,7 +41777,7 @@ __export(ai_analyze_exports, {
41753
41777
  });
41754
41778
  import { readFile as readFile26 } from "node:fs/promises";
41755
41779
  import { extname as extname10, resolve as resolve34 } from "node:path";
41756
- import { existsSync as existsSync31 } from "node:fs";
41780
+ import { existsSync as existsSync32 } from "node:fs";
41757
41781
  async function executeGeminiVideo(options) {
41758
41782
  try {
41759
41783
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -41767,7 +41791,7 @@ async function executeGeminiVideo(options) {
41767
41791
  videoData = options.source;
41768
41792
  } else {
41769
41793
  const absPath = resolve34(process.cwd(), options.source);
41770
- if (!existsSync31(absPath)) {
41794
+ if (!existsSync32(absPath)) {
41771
41795
  return { success: false, error: `File not found: ${absPath}` };
41772
41796
  }
41773
41797
  videoData = await readFile26(absPath);
@@ -41835,7 +41859,7 @@ async function executeAnalyze(options) {
41835
41859
  imageBuffer = Buffer.from(await response.arrayBuffer());
41836
41860
  } else {
41837
41861
  const absPath = resolve34(process.cwd(), source);
41838
- if (!existsSync31(absPath)) {
41862
+ if (!existsSync32(absPath)) {
41839
41863
  return { success: false, error: `File not found: ${absPath}` };
41840
41864
  }
41841
41865
  imageBuffer = await readFile26(absPath);
@@ -41870,7 +41894,7 @@ async function executeAnalyze(options) {
41870
41894
  videoData = Buffer.from(await response.arrayBuffer());
41871
41895
  } else {
41872
41896
  const absPath = resolve34(process.cwd(), source);
41873
- if (!existsSync31(absPath)) {
41897
+ if (!existsSync32(absPath)) {
41874
41898
  return { success: false, error: `File not found: ${absPath}` };
41875
41899
  }
41876
41900
  videoData = await readFile26(absPath);
@@ -41910,9 +41934,9 @@ var init_ai_analyze = __esm({
41910
41934
  });
41911
41935
 
41912
41936
  // src/commands/_shared/scene-inspect.ts
41913
- import { existsSync as existsSync32 } from "node:fs";
41937
+ import { existsSync as existsSync33 } from "node:fs";
41914
41938
  import { readdir as readdir4, readFile as readFile27, stat as stat5 } from "node:fs/promises";
41915
- import { isAbsolute as isAbsolute2, join as join17, resolve as resolve35 } from "node:path";
41939
+ import { isAbsolute as isAbsolute2, join as join18, resolve as resolve35 } from "node:path";
41916
41940
  async function inspectProject(opts) {
41917
41941
  const projectDir = resolve35(opts.projectDir);
41918
41942
  const issues = [];
@@ -41926,7 +41950,7 @@ async function inspectProject(opts) {
41926
41950
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
41927
41951
  rootSync: { ok: null, issueCount: 0, issues: [] }
41928
41952
  };
41929
- if (!existsSync32(projectDir) || !await isDirectory(projectDir)) {
41953
+ if (!existsSync33(projectDir) || !await isDirectory(projectDir)) {
41930
41954
  issues.push({
41931
41955
  severity: "error",
41932
41956
  code: "PROJECT_NOT_FOUND",
@@ -41941,15 +41965,15 @@ async function inspectProject(opts) {
41941
41965
  );
41942
41966
  }
41943
41967
  const coreFiles = {
41944
- storyboard: join17(projectDir, "STORYBOARD.md"),
41945
- design: join17(projectDir, "DESIGN.md"),
41946
- config: join17(projectDir, "vibe.config.json"),
41947
- legacyConfig: join17(projectDir, "vibe.project.yaml"),
41948
- buildReport: join17(projectDir, "build-report.json"),
41949
- root: join17(projectDir, "index.html")
41968
+ storyboard: join18(projectDir, "STORYBOARD.md"),
41969
+ design: join18(projectDir, "DESIGN.md"),
41970
+ config: join18(projectDir, "vibe.config.json"),
41971
+ legacyConfig: join18(projectDir, "vibe.project.yaml"),
41972
+ buildReport: join18(projectDir, "build-report.json"),
41973
+ root: join18(projectDir, "index.html")
41950
41974
  };
41951
41975
  for (const [name, path] of Object.entries(coreFiles)) {
41952
- checks.files[name] = existsSync32(path);
41976
+ checks.files[name] = existsSync33(path);
41953
41977
  }
41954
41978
  let beatIds = [];
41955
41979
  let inspectedBeatIds = [];
@@ -42030,20 +42054,20 @@ async function inspectProject(opts) {
42030
42054
  });
42031
42055
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
42032
42056
  }
42033
- const compositionsDir = join17(projectDir, "compositions");
42034
- const existingComps = existsSync32(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
42057
+ const compositionsDir = join18(projectDir, "compositions");
42058
+ const existingComps = existsSync33(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
42035
42059
  checks.compositions.expected = inspectedBeatIds.length;
42036
42060
  for (const beatId of inspectedBeatIds) {
42037
42061
  const file = `scene-${beatId}.html`;
42038
42062
  if (existingComps.has(file)) {
42039
42063
  checks.compositions.found++;
42040
42064
  } else {
42041
- checks.compositions.missing.push(join17("compositions", file));
42065
+ checks.compositions.missing.push(join18("compositions", file));
42042
42066
  issues.push({
42043
42067
  severity: "error",
42044
42068
  code: "MISSING_COMPOSITION",
42045
42069
  message: `Composition for beat "${beatId}" is missing.`,
42046
- file: join17("compositions", file),
42070
+ file: join18("compositions", file),
42047
42071
  scene: beatId,
42048
42072
  beatId,
42049
42073
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -42105,7 +42129,7 @@ async function inspectProject(opts) {
42105
42129
  try {
42106
42130
  const lint = await runProjectLint({ projectDir });
42107
42131
  const lintFiles = opts.beatId ? lint.files.filter(
42108
- (file) => file.file === "index.html" || file.file === join17("compositions", `scene-${opts.beatId}.html`)
42132
+ (file) => file.file === "index.html" || file.file === join18("compositions", `scene-${opts.beatId}.html`)
42109
42133
  ) : lint.files;
42110
42134
  const lintCounts = countLintFindings(lintFiles);
42111
42135
  checks.lint = {
@@ -42388,7 +42412,7 @@ function inspectReportedAsset(opts) {
42388
42412
  opts.checks.assets.checked++;
42389
42413
  if (isExternalRef(opts.value)) return;
42390
42414
  const abs = isAbsolute2(opts.value) ? opts.value : resolve35(opts.projectDir, opts.value);
42391
- if (existsSync32(abs)) return;
42415
+ if (existsSync33(abs)) return;
42392
42416
  opts.checks.assets.missing.push(opts.value);
42393
42417
  opts.issues.push({
42394
42418
  severity: "warning",
@@ -42493,7 +42517,7 @@ __export(ai_review_exports, {
42493
42517
  });
42494
42518
  import { readFile as readFile28, rename as rename3 } from "node:fs/promises";
42495
42519
  import { resolve as resolve36 } from "node:path";
42496
- import { existsSync as existsSync33 } from "node:fs";
42520
+ import { existsSync as existsSync34 } from "node:fs";
42497
42521
  function parseReviewFeedback(response) {
42498
42522
  let cleaned = response.trim();
42499
42523
  if (cleaned.startsWith("```json")) {
@@ -42525,7 +42549,7 @@ async function executeReview(options) {
42525
42549
  model = "flash"
42526
42550
  } = options;
42527
42551
  const absVideoPath = resolve36(process.cwd(), videoPath);
42528
- if (!existsSync33(absVideoPath)) {
42552
+ if (!existsSync34(absVideoPath)) {
42529
42553
  return { success: false, error: `Video not found: ${absVideoPath}` };
42530
42554
  }
42531
42555
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -42538,7 +42562,7 @@ async function executeReview(options) {
42538
42562
  let storyboardContext = "";
42539
42563
  if (storyboardPath2) {
42540
42564
  const absStoryboardPath = resolve36(process.cwd(), storyboardPath2);
42541
- if (existsSync33(absStoryboardPath)) {
42565
+ if (existsSync34(absStoryboardPath)) {
42542
42566
  const content = await readFile28(absStoryboardPath, "utf-8");
42543
42567
  storyboardContext = `
42544
42568
 
@@ -42666,9 +42690,9 @@ var init_ai_review = __esm({
42666
42690
  });
42667
42691
 
42668
42692
  // src/commands/_shared/render-inspect.ts
42669
- import { existsSync as existsSync34 } from "node:fs";
42693
+ import { existsSync as existsSync35 } from "node:fs";
42670
42694
  import { readdir as readdir5, readFile as readFile29, stat as stat6 } from "node:fs/promises";
42671
- import { basename as basename10, isAbsolute as isAbsolute3, join as join18, relative as relative7, resolve as resolve37 } from "node:path";
42695
+ import { basename as basename11, isAbsolute as isAbsolute3, join as join19, relative as relative7, resolve as resolve37 } from "node:path";
42672
42696
  function parseBlackdetectOutput(output) {
42673
42697
  const ranges = [];
42674
42698
  const regex2 = /black_start:(-?\d+(?:\.\d+)?)\s+black_end:(-?\d+(?:\.\d+)?)\s+black_duration:(-?\d+(?:\.\d+)?)/g;
@@ -43154,11 +43178,11 @@ async function inspectRender(opts) {
43154
43178
  }
43155
43179
  async function renderExpectsAudio(projectDir, beatId) {
43156
43180
  const root = resolve37(projectDir);
43157
- const rootHtml = await readTextIfExists(join18(root, "index.html"));
43181
+ const rootHtml = await readTextIfExists(join19(root, "index.html"));
43158
43182
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
43159
- const buildReport = await readJsonIfExists(join18(root, "build-report.json"));
43183
+ const buildReport = await readJsonIfExists(join19(root, "build-report.json"));
43160
43184
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
43161
- const storyboard = await readTextIfExists(join18(root, "STORYBOARD.md"));
43185
+ const storyboard = await readTextIfExists(join19(root, "STORYBOARD.md"));
43162
43186
  if (!storyboard) return false;
43163
43187
  try {
43164
43188
  const parsed = parseStoryboard(storyboard);
@@ -43169,7 +43193,7 @@ async function renderExpectsAudio(projectDir, beatId) {
43169
43193
  `assets/narration-${beat.id}.wav`,
43170
43194
  `assets/music-${beat.id}.mp3`,
43171
43195
  `assets/music-${beat.id}.wav`
43172
- ].some((asset) => existsSync34(join18(root, asset)))
43196
+ ].some((asset) => existsSync35(join19(root, asset)))
43173
43197
  );
43174
43198
  } catch {
43175
43199
  return false;
@@ -43277,8 +43301,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
43277
43301
  }
43278
43302
  function renderSourceReports(projectDir, videoPath, checks) {
43279
43303
  const reports = [];
43280
- if (existsSync34(join18(projectDir, "build-report.json"))) reports.push("build-report.json");
43281
- if (existsSync34(join18(projectDir, "render-report.json"))) reports.push("render-report.json");
43304
+ if (existsSync35(join19(projectDir, "build-report.json"))) reports.push("build-report.json");
43305
+ if (existsSync35(join19(projectDir, "render-report.json"))) reports.push("render-report.json");
43282
43306
  if (videoPath) reports.push(displayPath(videoPath));
43283
43307
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
43284
43308
  reports.push("ffprobe");
@@ -43291,31 +43315,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
43291
43315
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
43292
43316
  if (explicit) {
43293
43317
  const candidate = resolve37(process.cwd(), explicit);
43294
- return existsSync34(candidate) ? candidate : null;
43318
+ return existsSync35(candidate) ? candidate : null;
43295
43319
  }
43296
- const renderReportPath = join18(projectDir, "render-report.json");
43297
- if (existsSync34(renderReportPath)) {
43320
+ const renderReportPath = join19(projectDir, "render-report.json");
43321
+ if (existsSync35(renderReportPath)) {
43298
43322
  try {
43299
43323
  const report = JSON.parse(await readFile29(renderReportPath, "utf-8"));
43300
43324
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
43301
43325
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
43302
43326
  const candidate = resolveReportedPath(projectDir, report.outputPath);
43303
- if (candidate && existsSync34(candidate)) return candidate;
43327
+ if (candidate && existsSync35(candidate)) return candidate;
43304
43328
  }
43305
43329
  } catch {
43306
43330
  }
43307
43331
  }
43308
- const reportPath = join18(projectDir, "build-report.json");
43309
- if (!beatId && existsSync34(reportPath)) {
43332
+ const reportPath = join19(projectDir, "build-report.json");
43333
+ if (!beatId && existsSync35(reportPath)) {
43310
43334
  try {
43311
43335
  const report = JSON.parse(await readFile29(reportPath, "utf-8"));
43312
43336
  const candidate = resolveReportedPath(projectDir, report.outputPath);
43313
- if (candidate && existsSync34(candidate)) return candidate;
43337
+ if (candidate && existsSync35(candidate)) return candidate;
43314
43338
  } catch {
43315
43339
  }
43316
43340
  }
43317
- const rendersDir = join18(projectDir, "renders");
43318
- if (!existsSync34(rendersDir)) return null;
43341
+ const rendersDir = join19(projectDir, "renders");
43342
+ if (!existsSync35(rendersDir)) return null;
43319
43343
  const entries = await readdir5(rendersDir, { withFileTypes: true });
43320
43344
  const candidates = [];
43321
43345
  for (const entry of entries) {
@@ -43323,7 +43347,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
43323
43347
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
43324
43348
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
43325
43349
  if (beatId && !entry.name.includes(sanitizeFileSegment(beatId))) continue;
43326
- const full = join18(rendersDir, entry.name);
43350
+ const full = join19(rendersDir, entry.name);
43327
43351
  const info = await stat6(full);
43328
43352
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
43329
43353
  }
@@ -43341,12 +43365,12 @@ async function runAiRenderReview(projectDir, videoPath, model) {
43341
43365
  });
43342
43366
  }
43343
43367
  function resolveStoryboardPath(projectDir) {
43344
- const storyboardPath2 = join18(projectDir, "STORYBOARD.md");
43345
- return existsSync34(storyboardPath2) ? storyboardPath2 : null;
43368
+ const storyboardPath2 = join19(projectDir, "STORYBOARD.md");
43369
+ return existsSync35(storyboardPath2) ? storyboardPath2 : null;
43346
43370
  }
43347
43371
  async function expectedDurationFromBuildReport(projectDir, beatId) {
43348
- const reportPath = join18(projectDir, "build-report.json");
43349
- if (!existsSync34(reportPath)) return void 0;
43372
+ const reportPath = join19(projectDir, "build-report.json");
43373
+ if (!existsSync35(reportPath)) return void 0;
43350
43374
  try {
43351
43375
  const report = JSON.parse(await readFile29(reportPath, "utf-8"));
43352
43376
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
@@ -43360,8 +43384,8 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
43360
43384
  }
43361
43385
  }
43362
43386
  async function beatTimingsFromBuildReport(projectDir, beatId) {
43363
- const reportPath = join18(projectDir, "build-report.json");
43364
- if (!existsSync34(reportPath)) return [];
43387
+ const reportPath = join19(projectDir, "build-report.json");
43388
+ if (!existsSync35(reportPath)) return [];
43365
43389
  try {
43366
43390
  const report = JSON.parse(await readFile29(reportPath, "utf-8"));
43367
43391
  const source = report.beats ?? [];
@@ -43389,17 +43413,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
43389
43413
  }
43390
43414
  async function buildRenderReviewProjectContext(projectDir) {
43391
43415
  const parts = [];
43392
- const storyboard = await readTextIfExists(join18(projectDir, "STORYBOARD.md"));
43416
+ const storyboard = await readTextIfExists(join19(projectDir, "STORYBOARD.md"));
43393
43417
  if (storyboard) {
43394
43418
  parts.push(`STORYBOARD.md:
43395
43419
  ${truncateText(storyboard, 4e3)}`);
43396
43420
  parts.push(`Storyboard beat cue summary:
43397
43421
  ${storyboardBeatCueSummary(storyboard)}`);
43398
43422
  }
43399
- const design = await readTextIfExists(join18(projectDir, "DESIGN.md"));
43423
+ const design = await readTextIfExists(join19(projectDir, "DESIGN.md"));
43400
43424
  if (design) parts.push(`DESIGN.md:
43401
43425
  ${truncateText(design, 4e3)}`);
43402
- const buildReport = await readTextIfExists(join18(projectDir, "build-report.json"));
43426
+ const buildReport = await readTextIfExists(join19(projectDir, "build-report.json"));
43403
43427
  if (buildReport) parts.push(`build-report.json:
43404
43428
  ${truncateText(buildReport, 6e3)}`);
43405
43429
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -43427,7 +43451,7 @@ function storyboardBeatCueSummary(storyboard) {
43427
43451
  }
43428
43452
  }
43429
43453
  async function readTextIfExists(path) {
43430
- if (!existsSync34(path)) return null;
43454
+ if (!existsSync35(path)) return null;
43431
43455
  try {
43432
43456
  return await readFile29(path, "utf-8");
43433
43457
  } catch {
@@ -43527,7 +43551,7 @@ function aspectRatio(aspect) {
43527
43551
  return w / h;
43528
43552
  }
43529
43553
  function displayPath(path) {
43530
- return relative7(process.cwd(), path) || basename10(path);
43554
+ return relative7(process.cwd(), path) || basename11(path);
43531
43555
  }
43532
43556
  function toSnakeCase(value) {
43533
43557
  return value.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
@@ -43578,15 +43602,15 @@ __export(ai_audio_exports, {
43578
43602
  executeTranscribe: () => executeTranscribe,
43579
43603
  executeVoiceClone: () => executeVoiceClone
43580
43604
  });
43581
- import { resolve as resolve39, dirname as dirname12, basename as basename11, extname as extname11, join as join19 } from "node:path";
43582
- import { mkdir as mkdir19, mkdtemp as mkdtemp2, readFile as readFile31, rm as rm6, writeFile as writeFile31 } from "node:fs/promises";
43605
+ import { resolve as resolve39, dirname as dirname12, basename as basename12, extname as extname11, join as join20 } from "node:path";
43606
+ import { mkdir as mkdir20, mkdtemp as mkdtemp2, readFile as readFile31, rm as rm6, writeFile as writeFile31 } from "node:fs/promises";
43583
43607
  import { tmpdir as tmpdir3 } from "node:os";
43584
- import { existsSync as existsSync35 } from "node:fs";
43608
+ import { existsSync as existsSync36 } from "node:fs";
43585
43609
  async function executeTranscribe(options) {
43586
43610
  const { audioPath, language, output, format, apiKey } = options;
43587
43611
  try {
43588
43612
  const absPath = resolve39(process.cwd(), audioPath);
43589
- if (!existsSync35(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43613
+ if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43590
43614
  const key = await getConfiguredApiKey("OPENAI_API_KEY", apiKey);
43591
43615
  if (!key) return { success: false, error: "OPENAI_API_KEY required", errorKind: "auth" };
43592
43616
  const result = await transcribeAudioFile(absPath, { apiKey: key, language });
@@ -43616,7 +43640,7 @@ async function executeIsolate(options) {
43616
43640
  const { audioPath, output = "vocals.mp3", apiKey } = options;
43617
43641
  try {
43618
43642
  const absPath = resolve39(process.cwd(), audioPath);
43619
- if (!existsSync35(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43643
+ if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43620
43644
  const key = await getConfiguredApiKey("ELEVENLABS_API_KEY", apiKey);
43621
43645
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required", errorKind: "auth" };
43622
43646
  const audioBuffer = await readFile31(absPath);
@@ -43644,7 +43668,7 @@ async function executeVoiceClone(options) {
43644
43668
  const audioBuffers = [];
43645
43669
  for (const samplePath of samplePaths) {
43646
43670
  const absPath = resolve39(process.cwd(), samplePath);
43647
- if (!existsSync35(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
43671
+ if (!existsSync36(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
43648
43672
  const buffer = await readFile31(absPath);
43649
43673
  audioBuffers.push(buffer);
43650
43674
  }
@@ -43665,16 +43689,16 @@ async function executeVoiceClone(options) {
43665
43689
  async function executeDub(options) {
43666
43690
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
43667
43691
  const absPath = resolve39(process.cwd(), mediaPath);
43668
- if (!existsSync35(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43692
+ if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
43669
43693
  const openaiKey = await getConfiguredApiKey("OPENAI_API_KEY");
43670
43694
  if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription", errorKind: "auth" };
43671
- const workDir = await mkdtemp2(join19(tmpdir3(), "vibe-dub-"));
43695
+ const workDir = await mkdtemp2(join20(tmpdir3(), "vibe-dub-"));
43672
43696
  try {
43673
43697
  const ext = extname11(absPath).toLowerCase();
43674
43698
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
43675
43699
  let audioPath = absPath;
43676
43700
  if (isVideo) {
43677
- audioPath = join19(workDir, "source.mp3");
43701
+ audioPath = join20(workDir, "source.mp3");
43678
43702
  await execSafe("ffmpeg", ["-y", "-loglevel", "error", "-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "4", audioPath]);
43679
43703
  }
43680
43704
  const transcript = await transcribeAudioFile(audioPath, { apiKey: openaiKey, language: source });
@@ -43715,7 +43739,7 @@ async function executeDub(options) {
43715
43739
  if (!spoken.success || !spoken.audioBuffer) {
43716
43740
  return { success: false, error: `Speech failed on segment ${i + 1}: ${spoken.error}`, errorKind: spoken.errorKind };
43717
43741
  }
43718
- const clipPath = join19(workDir, `line-${i}.${tts.audioExtension}`);
43742
+ const clipPath = join20(workDir, `line-${i}.${tts.audioExtension}`);
43719
43743
  await writeFile31(clipPath, spoken.audioBuffer);
43720
43744
  const slot = Math.max(0.1, (report[i + 1]?.startTime ?? seg.endTime) - seg.startTime);
43721
43745
  const length = await getAudioDuration(clipPath);
@@ -43724,7 +43748,7 @@ async function executeDub(options) {
43724
43748
  const outputExt = isVideo ? ".mp3" : ext || ".mp3";
43725
43749
  const finalOutputPath = resolve39(
43726
43750
  process.cwd(),
43727
- output || resolve39(dirname12(absPath), `${basename11(absPath, extname11(absPath))}-${language}${outputExt}`)
43751
+ output || resolve39(dirname12(absPath), `${basename12(absPath, extname11(absPath))}-${language}${outputExt}`)
43728
43752
  );
43729
43753
  await mixAtOffsets(clips, finalOutputPath);
43730
43754
  return { ...base, outputPath: finalOutputPath };
@@ -43742,7 +43766,7 @@ async function mixAtOffsets(clips, outputPath) {
43742
43766
  return `[${i}:a]${tempo}adelay=${delay}|${delay}[a${i}]`;
43743
43767
  });
43744
43768
  const mix = `${clips.map((_, i) => `[a${i}]`).join("")}amix=inputs=${clips.length}:normalize=0:dropout_transition=0[out]`;
43745
- await mkdir19(dirname12(outputPath), { recursive: true });
43769
+ await mkdir20(dirname12(outputPath), { recursive: true });
43746
43770
  await execSafe("ffmpeg", ["-y", "-loglevel", "error", ...inputs, "-filter_complex", [...chains, mix].join(";"), "-map", "[out]", outputPath]);
43747
43771
  }
43748
43772
  async function executeDuck(options) {
@@ -43759,9 +43783,9 @@ async function executeDuck(options) {
43759
43783
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
43760
43784
  const absMusicPath = resolve39(process.cwd(), musicPath);
43761
43785
  const absVoicePath = resolve39(process.cwd(), voicePath);
43762
- if (!existsSync35(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
43763
- if (!existsSync35(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
43764
- const defaultOutput = resolve39(dirname12(absMusicPath), `${basename11(absMusicPath, extname11(absMusicPath))}-ducked${extname11(absMusicPath)}`);
43786
+ if (!existsSync36(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
43787
+ if (!existsSync36(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
43788
+ const defaultOutput = resolve39(dirname12(absMusicPath), `${basename12(absMusicPath, extname11(absMusicPath))}-ducked${extname11(absMusicPath)}`);
43765
43789
  const outputPath = resolve39(process.cwd(), output || defaultOutput);
43766
43790
  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`;
43767
43791
  await execSafe("ffmpeg", [
@@ -43774,7 +43798,7 @@ async function executeDuck(options) {
43774
43798
  "-y",
43775
43799
  outputPath
43776
43800
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
43777
- if (!existsSync35(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
43801
+ if (!existsSync36(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
43778
43802
  return { success: true, outputPath };
43779
43803
  } catch (error) {
43780
43804
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -43797,9 +43821,9 @@ var init_ai_audio = __esm({
43797
43821
  });
43798
43822
 
43799
43823
  // src/commands/ai-highlights.ts
43800
- import { readFile as readFile32, writeFile as writeFile32, mkdir as mkdir20 } from "node:fs/promises";
43801
- import { resolve as resolve41, dirname as dirname13, basename as basename12, extname as extname12 } from "node:path";
43802
- import { existsSync as existsSync37 } from "node:fs";
43824
+ import { readFile as readFile32, writeFile as writeFile32, mkdir as mkdir21 } from "node:fs/promises";
43825
+ import { resolve as resolve41, dirname as dirname13, basename as basename13, extname as extname12 } from "node:path";
43826
+ import { existsSync as existsSync38 } from "node:fs";
43803
43827
  function filterHighlights(highlights, options) {
43804
43828
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
43805
43829
  filtered.sort((a, b) => b.confidence - a.confidence);
@@ -43839,7 +43863,7 @@ function truncate(text, maxLength) {
43839
43863
  async function executeHighlights(options) {
43840
43864
  try {
43841
43865
  const absPath = resolve41(process.cwd(), options.media);
43842
- if (!existsSync37(absPath)) {
43866
+ if (!existsSync38(absPath)) {
43843
43867
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
43844
43868
  }
43845
43869
  const ext = extname12(absPath).toLowerCase();
@@ -43951,7 +43975,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
43951
43975
  sourceDuration = await ffprobeDuration(absPath);
43952
43976
  }
43953
43977
  const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
43954
- if (tempAudioPath && existsSync37(tempAudioPath)) {
43978
+ if (tempAudioPath && existsSync38(tempAudioPath)) {
43955
43979
  const { unlink: unlinkFile } = await import("node:fs/promises");
43956
43980
  await unlinkFile(tempAudioPath).catch(() => {
43957
43981
  });
@@ -43997,7 +44021,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
43997
44021
  if (options.project) {
43998
44022
  const project = new Project("Highlight Reel");
43999
44023
  const source = project.addSource({
44000
- name: basename12(absPath),
44024
+ name: basename13(absPath),
44001
44025
  url: absPath,
44002
44026
  type: isVideo ? "video" : "audio",
44003
44027
  duration: sourceDuration
@@ -44038,7 +44062,7 @@ async function executeAutoShorts(options) {
44038
44062
  return { success: false, shorts: [], error: "FFmpeg not found" };
44039
44063
  }
44040
44064
  const absPath = resolve41(process.cwd(), options.video);
44041
- if (!existsSync37(absPath)) {
44065
+ if (!existsSync38(absPath)) {
44042
44066
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
44043
44067
  }
44044
44068
  const targetDuration = options.duration ?? 60;
@@ -44162,8 +44186,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
44162
44186
  };
44163
44187
  }
44164
44188
  const outputDir = options.outputDir ? resolve41(process.cwd(), options.outputDir) : dirname13(absPath);
44165
- if (options.outputDir && !existsSync37(outputDir)) {
44166
- await mkdir20(outputDir, { recursive: true });
44189
+ if (options.outputDir && !existsSync38(outputDir)) {
44190
+ await mkdir21(outputDir, { recursive: true });
44167
44191
  }
44168
44192
  const result = {
44169
44193
  success: true,
@@ -44171,7 +44195,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
44171
44195
  };
44172
44196
  for (let i = 0; i < selectedHighlights.length; i++) {
44173
44197
  const h = selectedHighlights[i];
44174
- const baseName = basename12(absPath, extname12(absPath));
44198
+ const baseName = basename13(absPath, extname12(absPath));
44175
44199
  const outputPath = resolve41(outputDir, `${baseName}-short-${i + 1}.mp4`);
44176
44200
  const { stdout: probeOut } = await execSafe("ffprobe", [
44177
44201
  "-v",
@@ -44245,7 +44269,7 @@ function registerHighlightsCommands(aiCommand) {
44245
44269
  validateOutputPath(options.output);
44246
44270
  }
44247
44271
  const absPath = resolve41(process.cwd(), mediaPath);
44248
- if (!existsSync37(absPath)) {
44272
+ if (!existsSync38(absPath)) {
44249
44273
  exitWithError(notFoundError(absPath));
44250
44274
  }
44251
44275
  if (options.dryRun) {
@@ -44428,7 +44452,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
44428
44452
  const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
44429
44453
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
44430
44454
  transcribeSpinner.fail("Transcription failed");
44431
- if (tempAudioPath && existsSync37(tempAudioPath)) {
44455
+ if (tempAudioPath && existsSync38(tempAudioPath)) {
44432
44456
  const { unlink: unlinkFile } = await import("node:fs/promises");
44433
44457
  await unlinkFile(tempAudioPath).catch(() => {
44434
44458
  });
@@ -44436,7 +44460,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
44436
44460
  exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
44437
44461
  }
44438
44462
  transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
44439
- if (tempAudioPath && existsSync37(tempAudioPath)) {
44463
+ if (tempAudioPath && existsSync38(tempAudioPath)) {
44440
44464
  const { unlink: unlinkFile } = await import("node:fs/promises");
44441
44465
  await unlinkFile(tempAudioPath).catch(() => {
44442
44466
  });
@@ -44502,7 +44526,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
44502
44526
  const projectSpinner = ora("\u{1F4E6} Creating project...").start();
44503
44527
  const project = new Project("Highlight Reel");
44504
44528
  const source = project.addSource({
44505
- name: basename12(absPath),
44529
+ name: basename13(absPath),
44506
44530
  url: absPath,
44507
44531
  type: isVideo ? "video" : "audio",
44508
44532
  duration: sourceDuration
@@ -44542,7 +44566,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
44542
44566
  validateOutputPath(options.output);
44543
44567
  }
44544
44568
  const absPath = resolve41(process.cwd(), videoPath);
44545
- if (!existsSync37(absPath)) {
44569
+ if (!existsSync38(absPath)) {
44546
44570
  exitWithError(notFoundError(absPath));
44547
44571
  }
44548
44572
  if (options.dryRun) {
@@ -44731,8 +44755,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
44731
44755
  return;
44732
44756
  }
44733
44757
  const outputDir = options.outputDir ? resolve41(process.cwd(), options.outputDir) : dirname13(absPath);
44734
- if (options.outputDir && !existsSync37(outputDir)) {
44735
- await mkdir20(outputDir, { recursive: true });
44758
+ if (options.outputDir && !existsSync38(outputDir)) {
44759
+ await mkdir21(outputDir, { recursive: true });
44736
44760
  }
44737
44761
  for (let i = 0; i < selectedHighlights.length; i++) {
44738
44762
  const h = selectedHighlights[i];
@@ -44744,12 +44768,12 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
44744
44768
  outputPath += ".mp4";
44745
44769
  }
44746
44770
  } else {
44747
- const baseName = basename12(absPath, extname12(absPath));
44771
+ const baseName = basename13(absPath, extname12(absPath));
44748
44772
  outputPath = resolve41(outputDir, `${baseName}-short-${i + 1}.mp4`);
44749
44773
  }
44750
44774
  const parentDir = dirname13(outputPath);
44751
- if (!existsSync37(parentDir)) {
44752
- await mkdir20(parentDir, { recursive: true });
44775
+ if (!existsSync38(parentDir)) {
44776
+ await mkdir21(parentDir, { recursive: true });
44753
44777
  }
44754
44778
  const { stdout: probeOut } = await execSafe("ffprobe", [
44755
44779
  "-v",
@@ -44822,9 +44846,9 @@ var init_ai_highlights = __esm({
44822
44846
  });
44823
44847
 
44824
44848
  // src/commands/ai-animated-caption.ts
44825
- import { resolve as resolve42, dirname as dirname14, basename as basename13 } from "node:path";
44826
- import { writeFile as writeFile33, mkdir as mkdir21, rm as rm7 } from "node:fs/promises";
44827
- import { existsSync as existsSync38 } from "node:fs";
44849
+ import { resolve as resolve42, dirname as dirname14, basename as basename14 } from "node:path";
44850
+ import { writeFile as writeFile33, mkdir as mkdir22, rm as rm7 } from "node:fs/promises";
44851
+ import { existsSync as existsSync39 } from "node:fs";
44828
44852
  import { tmpdir as tmpdir4 } from "node:os";
44829
44853
  function groupWords(words, options = {}) {
44830
44854
  if (words.length === 0) return [];
@@ -44971,7 +44995,7 @@ async function executeAnimatedCaption(options) {
44971
44995
  }
44972
44996
  const effectiveFontSize = fontSize ?? Math.round(height * 0.04);
44973
44997
  const tmpAudioDir = resolve42(tmpdir4(), `vf-ac-${Date.now()}`);
44974
- await mkdir21(tmpAudioDir, { recursive: true });
44998
+ await mkdir22(tmpAudioDir, { recursive: true });
44975
44999
  const audioPath = resolve42(tmpAudioDir, "audio.wav");
44976
45000
  await execSafe("ffmpeg", [
44977
45001
  "-y",
@@ -45001,8 +45025,8 @@ async function executeAnimatedCaption(options) {
45001
45025
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
45002
45026
  const absOutputPath = resolve42(process.cwd(), outputPath);
45003
45027
  const outDir = dirname14(absOutputPath);
45004
- if (!existsSync38(outDir)) {
45005
- await mkdir21(outDir, { recursive: true });
45028
+ if (!existsSync39(outDir)) {
45029
+ await mkdir22(outDir, { recursive: true });
45006
45030
  }
45007
45031
  if (tier === "ass") {
45008
45032
  const assContent = generateASS(
@@ -45033,7 +45057,7 @@ async function executeAnimatedCaption(options) {
45033
45057
  width,
45034
45058
  height,
45035
45059
  fps: videoFps,
45036
- videoFileName: basename13(videoPath)
45060
+ videoFileName: basename14(videoPath)
45037
45061
  });
45038
45062
  const durationInFrames = Math.ceil(duration * videoFps);
45039
45063
  const renderResult = await renderWithEmbeddedVideo({
@@ -45044,7 +45068,7 @@ async function executeAnimatedCaption(options) {
45044
45068
  fps: videoFps,
45045
45069
  durationInFrames,
45046
45070
  videoPath,
45047
- videoFileName: basename13(videoPath),
45071
+ videoFileName: basename14(videoPath),
45048
45072
  outputPath: absOutputPath
45049
45073
  });
45050
45074
  if (!renderResult.success) {
@@ -54649,7 +54673,7 @@ __export(detect_exports, {
54649
54673
  executeDetectSilence: () => executeDetectSilence
54650
54674
  });
54651
54675
  import { readFile as readFile34, writeFile as writeFile35 } from "node:fs/promises";
54652
- import { resolve as resolve46, basename as basename17 } from "node:path";
54676
+ import { resolve as resolve46, basename as basename18 } from "node:path";
54653
54677
  async function executeDetectScenes(options) {
54654
54678
  try {
54655
54679
  if (!commandExists("ffmpeg")) {
@@ -54899,7 +54923,7 @@ var init_detect = __esm({
54899
54923
  let source = project.getSources().find((s) => s.url === absPath);
54900
54924
  if (!source) {
54901
54925
  source = project.addSource({
54902
- name: basename17(absPath),
54926
+ name: basename18(absPath),
54903
54927
  type: "video",
54904
54928
  url: absPath,
54905
54929
  duration: totalDuration
@@ -56707,14 +56731,14 @@ var init_composer_resolve = __esm({
56707
56731
  });
56708
56732
 
56709
56733
  // src/commands/_shared/storyboard-revise.ts
56710
- import { existsSync as existsSync43 } from "node:fs";
56734
+ import { existsSync as existsSync44 } from "node:fs";
56711
56735
  import { readFile as readFile38, writeFile as writeFile38 } from "node:fs/promises";
56712
- import { join as join23, resolve as resolve50 } from "node:path";
56736
+ import { join as join24, resolve as resolve50 } from "node:path";
56713
56737
  async function executeStoryboardRevision(opts) {
56714
56738
  const projectDir = resolve50(opts.projectDir);
56715
- const storyboardPath2 = join23(projectDir, "STORYBOARD.md");
56739
+ const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
56716
56740
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
56717
- if (!existsSync43(storyboardPath2)) {
56741
+ if (!existsSync44(storyboardPath2)) {
56718
56742
  return failureResult({
56719
56743
  projectDir,
56720
56744
  storyboardPath: storyboardPath2,
@@ -56955,16 +56979,16 @@ function diffChangedBeats(before, after) {
56955
56979
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
56956
56980
  }
56957
56981
  async function readRevisionContext(projectDir) {
56958
- const designMd = await readOptionalText(join23(projectDir, "DESIGN.md"));
56959
- const buildReportJson = await readOptionalJsonText(join23(projectDir, "build-report.json"));
56982
+ const designMd = await readOptionalText(join24(projectDir, "DESIGN.md"));
56983
+ const buildReportJson = await readOptionalJsonText(join24(projectDir, "build-report.json"));
56960
56984
  return { designMd, buildReportJson };
56961
56985
  }
56962
56986
  async function readOptionalText(path) {
56963
- if (!existsSync43(path)) return null;
56987
+ if (!existsSync44(path)) return null;
56964
56988
  return readFile38(path, "utf-8");
56965
56989
  }
56966
56990
  async function readOptionalJsonText(path) {
56967
- if (!existsSync43(path)) return null;
56991
+ if (!existsSync44(path)) return null;
56968
56992
  try {
56969
56993
  const parsed = JSON.parse(await readFile38(path, "utf-8"));
56970
56994
  return JSON.stringify(parsed, null, 2);
@@ -57118,7 +57142,7 @@ var init_design_parse = __esm({
57118
57142
  });
57119
57143
 
57120
57144
  // src/commands/_shared/build-asset-reference.ts
57121
- import { existsSync as existsSync46 } from "node:fs";
57145
+ import { existsSync as existsSync47 } from "node:fs";
57122
57146
  import { extname as extname15, isAbsolute as isAbsolute4, relative as relative8, resolve as resolve53, sep as sep2 } from "node:path";
57123
57147
  function resolveTypedAssetReference(projectDir, kind, value) {
57124
57148
  const raw = stringOrUndefined2(value);
@@ -57149,7 +57173,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
57149
57173
  const absPath = isAbsolute4(raw) ? resolve53(raw) : resolve53(projectDir, raw);
57150
57174
  const rel = relative8(resolve53(projectDir), absPath);
57151
57175
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute4(rel);
57152
- const exists = withinProject && existsSync46(absPath);
57176
+ const exists = withinProject && existsSync47(absPath);
57153
57177
  let error = forcedError;
57154
57178
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
57155
57179
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -57370,8 +57394,8 @@ var init_build_video_providers = __esm({
57370
57394
  });
57371
57395
 
57372
57396
  // src/commands/_shared/build-plan.ts
57373
- import { existsSync as existsSync47 } from "node:fs";
57374
- import { dirname as dirname17, join as join25, resolve as resolve54 } from "node:path";
57397
+ import { existsSync as existsSync48 } from "node:fs";
57398
+ import { dirname as dirname17, join as join26, resolve as resolve54 } from "node:path";
57375
57399
  function backdropCostUsd(quality) {
57376
57400
  return quality === "hd" ? 0.2 : 0.05;
57377
57401
  }
@@ -57390,11 +57414,11 @@ async function createBuildPlan(opts) {
57390
57414
  const skipBackdrop = opts.skipBackdrop ?? (composerIsFootage ? true : kindPolicy.skipBackdrop ?? false);
57391
57415
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
57392
57416
  const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
57393
- const storyboardPath2 = join25(projectDir, "STORYBOARD.md");
57417
+ const storyboardPath2 = join26(projectDir, "STORYBOARD.md");
57394
57418
  const warnings = [];
57395
57419
  const retryWith = [];
57396
57420
  const providerResolution = [];
57397
- if (!existsSync47(storyboardPath2)) {
57421
+ if (!existsSync48(storyboardPath2)) {
57398
57422
  const validation2 = {
57399
57423
  ok: false,
57400
57424
  issues: [
@@ -57454,7 +57478,7 @@ async function createBuildPlan(opts) {
57454
57478
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
57455
57479
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
57456
57480
  const imageRatio = imageRatioForSize(imageSize2);
57457
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync47(join25(projectDir, `compositions/scene-${beat.id}.html`)));
57481
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync48(join26(projectDir, `compositions/scene-${beat.id}.html`)));
57458
57482
  const resolved = await resolvePlanProviders({
57459
57483
  projectDir,
57460
57484
  config: config2,
@@ -57604,7 +57628,7 @@ async function createBuildPlan(opts) {
57604
57628
  active: includeAssets
57605
57629
  }) : null;
57606
57630
  const compositionPath = `compositions/scene-${beat.id}.html`;
57607
- const compositionExists = existsSync47(join25(projectDir, compositionPath));
57631
+ const compositionExists = existsSync48(join26(projectDir, compositionPath));
57608
57632
  for (const asset of [narration, backdrop, keyframe, video, music]) {
57609
57633
  if (!asset) continue;
57610
57634
  if (asset.referenceError) {
@@ -57677,7 +57701,7 @@ async function createBuildPlan(opts) {
57677
57701
  }
57678
57702
  }
57679
57703
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
57680
- if (!existsSync47(join25(projectDir, config2.config.composition.entry))) {
57704
+ if (!existsSync48(join26(projectDir, config2.config.composition.entry))) {
57681
57705
  missing.add("root-composition");
57682
57706
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
57683
57707
  }
@@ -57711,7 +57735,7 @@ async function createBuildPlan(opts) {
57711
57735
  }
57712
57736
  if (referenced.size > 0) {
57713
57737
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
57714
- (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync47(join25(projectDir, `assets/character-${c.name}.png`)))
57738
+ (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync48(join26(projectDir, `assets/character-${c.name}.png`)))
57715
57739
  );
57716
57740
  if (toGenerate.length > 0) {
57717
57741
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -57824,8 +57848,8 @@ function assetPlan(opts) {
57824
57848
  reason: ready ? "referenced-asset" : "invalid-reference"
57825
57849
  };
57826
57850
  }
57827
- const canonicalExists = existsSync47(join25(opts.projectDir, opts.path));
57828
- const cacheHit = opts.cache ? existsSync47(join25(opts.projectDir, opts.cache.path)) : false;
57851
+ const canonicalExists = existsSync48(join26(opts.projectDir, opts.path));
57852
+ const cacheHit = opts.cache ? existsSync48(join26(opts.projectDir, opts.cache.path)) : false;
57829
57853
  const freshness = assetFreshnessFromMetadata({
57830
57854
  projectDir: opts.projectDir,
57831
57855
  kind: opts.kind,
@@ -58034,7 +58058,7 @@ async function firstConfiguredComposer(projectDir) {
58034
58058
  }
58035
58059
  function firstExisting2(projectDir, paths) {
58036
58060
  for (const path of paths) {
58037
- if (existsSync47(join25(projectDir, path))) return path;
58061
+ if (existsSync48(join26(projectDir, path))) return path;
58038
58062
  }
58039
58063
  return null;
58040
58064
  }
@@ -58070,12 +58094,12 @@ function unique3(items) {
58070
58094
  return [...new Set(items.filter((item) => item.length > 0))];
58071
58095
  }
58072
58096
  function loadPlanEnv(projectDir) {
58073
- (0, import_dotenv3.config)({ path: join25(projectDir, ".env"), quiet: true });
58097
+ (0, import_dotenv3.config)({ path: join26(projectDir, ".env"), quiet: true });
58074
58098
  (0, import_dotenv3.config)({ path: resolve54(process.cwd(), ".env"), quiet: true });
58075
58099
  let dir = process.cwd();
58076
58100
  while (dir !== dirname17(dir)) {
58077
- if (existsSync47(join25(dir, "pnpm-workspace.yaml"))) {
58078
- (0, import_dotenv3.config)({ path: join25(dir, ".env"), quiet: true });
58101
+ if (existsSync48(join26(dir, "pnpm-workspace.yaml"))) {
58102
+ (0, import_dotenv3.config)({ path: join26(dir, ".env"), quiet: true });
58079
58103
  return;
58080
58104
  }
58081
58105
  dir = dirname17(dir);
@@ -58160,9 +58184,9 @@ var init_concurrency = __esm({
58160
58184
  });
58161
58185
 
58162
58186
  // src/commands/_shared/compose-aivideo.ts
58163
- import { existsSync as existsSync48 } from "node:fs";
58164
- import { mkdir as mkdir24, readFile as readFile41, writeFile as writeFile40 } from "node:fs/promises";
58165
- import { join as join26, resolve as resolve56 } from "node:path";
58187
+ import { existsSync as existsSync49 } from "node:fs";
58188
+ import { mkdir as mkdir25, readFile as readFile41, writeFile as writeFile40 } from "node:fs/promises";
58189
+ import { join as join27, resolve as resolve56 } from "node:path";
58166
58190
  function headingDescriptor(heading) {
58167
58191
  const stripped = heading.replace(/^\s*beat\s+/i, "");
58168
58192
  const m = stripped.match(/^[A-Za-z0-9][\w-]*(?:\s*[—:]\s*|\s+-\s+)(.+)$/);
@@ -58319,20 +58343,20 @@ async function composeAiVideo(opts) {
58319
58343
  const clipRels = [];
58320
58344
  for (const beat of beats) {
58321
58345
  const rel = clipRelFor(beat);
58322
- if (!existsSync48(join26(projectDir, rel))) {
58346
+ if (!existsSync49(join27(projectDir, rel))) {
58323
58347
  throw new Error(`Missing clip for beat "${beat.id}" at ${rel} \u2014 run the assets stage first.`);
58324
58348
  }
58325
58349
  clipRels.push(rel);
58326
58350
  }
58327
- await mkdir24(join26(projectDir, "assets"), { recursive: true });
58351
+ await mkdir25(join27(projectDir, "assets"), { recursive: true });
58328
58352
  const bgRel = "assets/bg-full.mp4";
58329
58353
  const concatArgs = buildBgConcatArgs(
58330
- clipRels.map((r) => join26(projectDir, r)),
58331
- join26(projectDir, bgRel)
58354
+ clipRels.map((r) => join27(projectDir, r)),
58355
+ join27(projectDir, bgRel)
58332
58356
  );
58333
58357
  await execSafe("ffmpeg", concatArgs, { timeout: 6e5 });
58334
- const totalDurationSec = await probeDurationSec(join26(projectDir, bgRel));
58335
- await mkdir24(join26(projectDir, "compositions"), { recursive: true });
58358
+ const totalDurationSec = await probeDurationSec(join27(projectDir, bgRel));
58359
+ await mkdir25(join27(projectDir, "compositions"), { recursive: true });
58336
58360
  const sceneFiles = [];
58337
58361
  for (let i = 0; i < beats.length; i++) {
58338
58362
  const beat = beats[i];
@@ -58349,11 +58373,11 @@ async function composeAiVideo(opts) {
58349
58373
  isLast: i === beats.length - 1
58350
58374
  });
58351
58375
  const rel = `compositions/scene-${beat.id}.html`;
58352
- await writeFile40(join26(projectDir, rel), html, "utf-8");
58376
+ await writeFile40(join27(projectDir, rel), html, "utf-8");
58353
58377
  sceneFiles.push(rel);
58354
58378
  }
58355
- const indexPath = join26(projectDir, "index.html");
58356
- const next = existsSync48(indexPath) ? injectBgVideo(await readFile41(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
58379
+ const indexPath = join27(projectDir, "index.html");
58380
+ const next = existsSync49(indexPath) ? injectBgVideo(await readFile41(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
58357
58381
  await writeFile40(indexPath, next, "utf-8");
58358
58382
  return { bgVideoRel: bgRel, sceneFiles, totalDurationSec, beats: beats.length };
58359
58383
  }
@@ -58402,8 +58426,8 @@ async function probeDurationSec(absPath) {
58402
58426
  }
58403
58427
  }
58404
58428
  async function loadProjectTokens(projectDir) {
58405
- const designPath = join26(projectDir, "DESIGN.md");
58406
- if (!existsSync48(designPath)) return DEFAULT_AIVIDEO_TOKENS;
58429
+ const designPath = join27(projectDir, "DESIGN.md");
58430
+ if (!existsSync49(designPath)) return DEFAULT_AIVIDEO_TOKENS;
58407
58431
  try {
58408
58432
  return extractDesignTokens(await readFile41(designPath, "utf-8"));
58409
58433
  } catch {
@@ -58480,9 +58504,9 @@ var init_compose_aivideo = __esm({
58480
58504
  });
58481
58505
 
58482
58506
  // src/commands/_shared/footage-assemble.ts
58483
- import { existsSync as existsSync49 } from "node:fs";
58484
- import { mkdir as mkdir25, writeFile as writeFile41 } from "node:fs/promises";
58485
- import { dirname as dirname18, isAbsolute as isAbsolute5, join as join27, resolve as resolve57 } from "node:path";
58507
+ import { existsSync as existsSync50 } from "node:fs";
58508
+ import { mkdir as mkdir26, writeFile as writeFile41 } from "node:fs/promises";
58509
+ import { dirname as dirname18, isAbsolute as isAbsolute5, join as join28, resolve as resolve57 } from "node:path";
58486
58510
  function planFootageTimeline(inputs, opts = {}) {
58487
58511
  if (inputs.length === 0) {
58488
58512
  throw new Error("No clips to assemble.");
@@ -58708,12 +58732,12 @@ async function executeFootageAssemble(opts) {
58708
58732
  const kenburnsBeatIds = [];
58709
58733
  for (const beat of beats) {
58710
58734
  const clipRel = `assets/video-${beat.id}.mp4`;
58711
- if (existsSync49(join27(projectDir, clipRel))) {
58735
+ if (existsSync50(join28(projectDir, clipRel))) {
58712
58736
  sourceRels.push({ rel: clipRel, kind: "video" });
58713
58737
  continue;
58714
58738
  }
58715
58739
  const keyframeRel = `assets/keyframe-${beat.id}.png`;
58716
- if (existsSync49(join27(projectDir, keyframeRel))) {
58740
+ if (existsSync50(join28(projectDir, keyframeRel))) {
58717
58741
  sourceRels.push({ rel: keyframeRel, kind: "image" });
58718
58742
  kenburnsBeatIds.push(beat.id);
58719
58743
  continue;
@@ -58732,7 +58756,7 @@ async function executeFootageAssemble(opts) {
58732
58756
  }
58733
58757
  const narrationRels = /* @__PURE__ */ new Map();
58734
58758
  for (let i = 0; i < beats.length; i++) {
58735
- const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync49(join27(projectDir, r)));
58759
+ const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync50(join28(projectDir, r)));
58736
58760
  if (rel) narrationRels.set(i, rel);
58737
58761
  }
58738
58762
  const music = resolveMusicBed(projectDir, beats[0]?.id, opts.music);
@@ -58746,9 +58770,9 @@ async function executeFootageAssemble(opts) {
58746
58770
  }
58747
58771
  const inputs = [];
58748
58772
  for (let i = 0; i < beats.length; i++) {
58749
- const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join27(projectDir, sourceRels[i].rel)));
58773
+ const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join28(projectDir, sourceRels[i].rel)));
58750
58774
  const narrationRel = narrationRels.get(i);
58751
- const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join27(projectDir, narrationRel))) : void 0;
58775
+ const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join28(projectDir, narrationRel))) : void 0;
58752
58776
  inputs.push({ id: beats[i].id, clipDurationSec, narrationDurationSec });
58753
58777
  }
58754
58778
  const plan = planFootageTimeline(inputs, {
@@ -58811,14 +58835,14 @@ async function executeFootageAssemble(opts) {
58811
58835
  if (opts.dryRun) {
58812
58836
  return { success: true, dryRun: true, outputPath, report };
58813
58837
  }
58814
- await mkdir25(dirname18(outputPath), { recursive: true });
58838
+ await mkdir26(dirname18(outputPath), { recursive: true });
58815
58839
  const args = buildFootageAssembleArgs({
58816
- sources: sourceRels.map((s) => ({ path: join27(projectDir, s.rel), kind: s.kind })),
58840
+ sources: sourceRels.map((s) => ({ path: join28(projectDir, s.rel), kind: s.kind })),
58817
58841
  plan,
58818
58842
  narrationPaths: new Map(
58819
- [...narrationRels.entries()].map(([i, rel]) => [i, join27(projectDir, rel)])
58843
+ [...narrationRels.entries()].map(([i, rel]) => [i, join28(projectDir, rel)])
58820
58844
  ),
58821
- musicPath: music.rel ? isAbsolute5(music.rel) ? music.rel : join27(projectDir, music.rel) : void 0,
58845
+ musicPath: music.rel ? isAbsolute5(music.rel) ? music.rel : join28(projectDir, music.rel) : void 0,
58822
58846
  outputPath,
58823
58847
  musicVolume,
58824
58848
  finish: opts.finish
@@ -58835,7 +58859,7 @@ async function executeFootageAssemble(opts) {
58835
58859
  suggestion: "Re-run with --dry-run --json to inspect the planned timeline."
58836
58860
  };
58837
58861
  }
58838
- const reportPath = join27(projectDir, "assemble-report.json");
58862
+ const reportPath = join28(projectDir, "assemble-report.json");
58839
58863
  await writeFile41(reportPath, `${JSON.stringify(report, null, 2)}
58840
58864
  `, "utf-8");
58841
58865
  return { success: true, dryRun: false, outputPath, reportPath, report };
@@ -58843,15 +58867,15 @@ async function executeFootageAssemble(opts) {
58843
58867
  function resolveMusicBed(projectDir, firstBeatId, flag) {
58844
58868
  if (flag === "none") return { ok: true, source: "flag" };
58845
58869
  if (flag) {
58846
- const abs = isAbsolute5(flag) ? flag : join27(projectDir, flag);
58847
- if (!existsSync49(abs)) return { ok: false, error: `Music file not found: ${abs}` };
58870
+ const abs = isAbsolute5(flag) ? flag : join28(projectDir, flag);
58871
+ if (!existsSync50(abs)) return { ok: false, error: `Music file not found: ${abs}` };
58848
58872
  return { ok: true, rel: flag, source: "flag" };
58849
58873
  }
58850
58874
  const candidates = ["assets/music.mp3", "assets/music.wav"];
58851
58875
  if (firstBeatId) {
58852
58876
  candidates.push(`assets/music-${firstBeatId}.mp3`, `assets/music-${firstBeatId}.wav`);
58853
58877
  }
58854
- const rel = candidates.find((r) => existsSync49(join27(projectDir, r)));
58878
+ const rel = candidates.find((r) => existsSync50(join28(projectDir, r)));
58855
58879
  return { ok: true, rel, source: "auto" };
58856
58880
  }
58857
58881
  var FOOTAGE_DEFAULTS, round3, round6, DEFAULT_FOOTAGE_OUTPUT;
@@ -59001,7 +59025,7 @@ var init_scene_audio_scan = __esm({
59001
59025
 
59002
59026
  // src/commands/_shared/scene-audio-mux.ts
59003
59027
  import { rename as rename4, unlink } from "node:fs/promises";
59004
- import { resolve as resolve59, dirname as dirname19, extname as extname16, basename as basename19 } from "node:path";
59028
+ import { resolve as resolve59, dirname as dirname19, extname as extname16, basename as basename20 } from "node:path";
59005
59029
  function buildAudioMuxFilter(audios) {
59006
59030
  if (audios.length === 0) return null;
59007
59031
  const labels = [];
@@ -59063,7 +59087,7 @@ async function muxAudioIntoVideo(opts) {
59063
59087
  const ext = extname16(opts.videoPath) || `.${opts.format}`;
59064
59088
  const tmpPath = resolve59(
59065
59089
  dirname19(opts.videoPath),
59066
- `.${basename19(opts.videoPath, ext)}.muxing${ext}`
59090
+ `.${basename20(opts.videoPath, ext)}.muxing${ext}`
59067
59091
  );
59068
59092
  const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
59069
59093
  for (const a of opts.audios) {
@@ -59124,7 +59148,7 @@ var init_scene_audio_mux = __esm({
59124
59148
  });
59125
59149
 
59126
59150
  // src/commands/_shared/scene-assemble.ts
59127
- import { existsSync as existsSync50 } from "node:fs";
59151
+ import { existsSync as existsSync51 } from "node:fs";
59128
59152
  import { readFile as readFile43 } from "node:fs/promises";
59129
59153
  import { resolve as resolve60, isAbsolute as isAbsolute6 } from "node:path";
59130
59154
  async function executeSceneAssemble(opts) {
@@ -59132,7 +59156,7 @@ async function executeSceneAssemble(opts) {
59132
59156
  const root = opts.root ?? "index.html";
59133
59157
  const format = opts.format ?? "mp4";
59134
59158
  const videoPath = isAbsolute6(opts.videoPath) ? opts.videoPath : resolve60(projectDir, opts.videoPath);
59135
- if (!existsSync50(videoPath)) {
59159
+ if (!existsSync51(videoPath)) {
59136
59160
  return {
59137
59161
  success: false,
59138
59162
  audioCount: 0,
@@ -59142,7 +59166,7 @@ async function executeSceneAssemble(opts) {
59142
59166
  };
59143
59167
  }
59144
59168
  const rootPath = resolve60(projectDir, root);
59145
- if (!existsSync50(rootPath)) {
59169
+ if (!existsSync51(rootPath)) {
59146
59170
  return {
59147
59171
  success: false,
59148
59172
  audioCount: 0,
@@ -59209,9 +59233,9 @@ __export(scene_render_exports, {
59209
59233
  qualityToCrf: () => qualityToCrf
59210
59234
  });
59211
59235
  import { execFile as execFile2 } from "node:child_process";
59212
- import { existsSync as existsSync51 } from "node:fs";
59213
- import { mkdir as mkdir26, readFile as readFile44, stat as stat8, writeFile as writeFile42 } from "node:fs/promises";
59214
- import { resolve as resolve61, relative as relative9, dirname as dirname20, basename as basename20, join as join28, isAbsolute as isAbsolute7 } from "node:path";
59236
+ import { existsSync as existsSync52 } from "node:fs";
59237
+ import { mkdir as mkdir27, readFile as readFile44, stat as stat8, writeFile as writeFile42 } from "node:fs/promises";
59238
+ import { resolve as resolve61, relative as relative9, dirname as dirname20, basename as basename21, join as join29, isAbsolute as isAbsolute7 } from "node:path";
59215
59239
  import { promisify as promisify2 } from "node:util";
59216
59240
  import {
59217
59241
  createRenderJob,
@@ -59224,7 +59248,7 @@ function defaultOutputPath(opts) {
59224
59248
  const fmt = opts.format ?? "mp4";
59225
59249
  const now = opts.now ?? /* @__PURE__ */ new Date();
59226
59250
  const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
59227
- const name = (opts.projectName ?? basename20(resolve61(opts.projectDir))) || "scene";
59251
+ const name = (opts.projectName ?? basename21(resolve61(opts.projectDir))) || "scene";
59228
59252
  const beat = opts.beatId ? `-${sanitizeFileSegment2(opts.beatId)}` : "";
59229
59253
  return resolve61(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
59230
59254
  }
@@ -59303,7 +59327,7 @@ async function executeSceneRender(opts = {}) {
59303
59327
  }
59304
59328
  let autoSyncApplied = false;
59305
59329
  let autoSyncWarning;
59306
- if (!opts.beatId && existsSync51(join28(projectDir, "STORYBOARD.md"))) {
59330
+ if (!opts.beatId && existsSync52(join29(projectDir, "STORYBOARD.md"))) {
59307
59331
  try {
59308
59332
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
59309
59333
  const subPlans = await createSubCompDurationSyncPlans({
@@ -59360,7 +59384,7 @@ async function executeSceneRender(opts = {}) {
59360
59384
  }
59361
59385
  const projectName = projectConfig.config.name;
59362
59386
  const outputPath = opts.output ? resolve61(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
59363
- await mkdir26(dirname20(outputPath), { recursive: true });
59387
+ await mkdir27(dirname20(outputPath), { recursive: true });
59364
59388
  const config2 = buildRenderConfig({
59365
59389
  fps: opts.fps,
59366
59390
  quality: opts.quality,
@@ -59472,11 +59496,11 @@ async function safeStat(p) {
59472
59496
  }
59473
59497
  async function prepareBeatRenderRoot(opts) {
59474
59498
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
59475
- const reportPath = join28(opts.projectDir, "build-report.json");
59499
+ const reportPath = join29(opts.projectDir, "build-report.json");
59476
59500
  const retryWith = [
59477
59501
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
59478
59502
  ];
59479
- if (!existsSync51(reportPath)) {
59503
+ if (!existsSync52(reportPath)) {
59480
59504
  return {
59481
59505
  success: false,
59482
59506
  root,
@@ -59556,8 +59580,8 @@ async function prepareBeatRenderRoot(opts) {
59556
59580
  durationSec,
59557
59581
  narrationPath
59558
59582
  });
59559
- await mkdir26(dirname20(join28(opts.projectDir, root)), { recursive: true });
59560
- await writeFile42(join28(opts.projectDir, root), html, "utf-8");
59583
+ await mkdir27(dirname20(join29(opts.projectDir, root)), { recursive: true });
59584
+ await writeFile42(join29(opts.projectDir, root), html, "utf-8");
59561
59585
  return {
59562
59586
  success: true,
59563
59587
  root,
@@ -59621,7 +59645,7 @@ function reportedBeatAssetPaths(beat) {
59621
59645
  ]);
59622
59646
  }
59623
59647
  async function writeRenderReport(projectDir, result) {
59624
- const reportPath = join28(projectDir, "render-report.json");
59648
+ const reportPath = join29(projectDir, "render-report.json");
59625
59649
  try {
59626
59650
  await writeFile42(
59627
59651
  reportPath,
@@ -59663,7 +59687,7 @@ async function writeRenderReport(projectDir, result) {
59663
59687
  }
59664
59688
  function pathExists3(projectDir, value) {
59665
59689
  const abs = isAbsolute7(value) ? value : resolve61(projectDir, value);
59666
- return existsSync51(abs);
59690
+ return existsSync52(abs);
59667
59691
  }
59668
59692
  function stringValue(value) {
59669
59693
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -59719,12 +59743,12 @@ __export(scene_build_exports, {
59719
59743
  isLikenessRejection: () => isLikenessRejection,
59720
59744
  resolveSceneBuildMode: () => resolveSceneBuildMode
59721
59745
  });
59722
- import { existsSync as existsSync52, readFileSync as readFileSync2 } from "node:fs";
59723
- import { copyFile as copyFile5, mkdir as mkdir27, readFile as readFile45, rm as rm8, writeFile as writeFile43 } from "node:fs/promises";
59724
- import { dirname as dirname21, join as join29, resolve as resolve62 } from "node:path";
59746
+ import { existsSync as existsSync53, readFileSync as readFileSync2 } from "node:fs";
59747
+ import { copyFile as copyFile6, mkdir as mkdir28, readFile as readFile45, rm as rm8, writeFile as writeFile43 } from "node:fs/promises";
59748
+ import { dirname as dirname21, join as join30, resolve as resolve62 } from "node:path";
59725
59749
  function readProjectKindSync(projectDir) {
59726
59750
  try {
59727
- const raw = readFileSync2(join29(projectDir, "vibe.config.json"), "utf-8");
59751
+ const raw = readFileSync2(join30(projectDir, "vibe.config.json"), "utf-8");
59728
59752
  const k = JSON.parse(raw).kind;
59729
59753
  return typeof k === "string" && isSceneKind(k) ? k : "cinema";
59730
59754
  } catch {
@@ -59756,7 +59780,7 @@ async function executeSceneBuild(opts) {
59756
59780
  );
59757
59781
  }
59758
59782
  let sceneRepair = skippedSceneRepairSummary();
59759
- const storyboardPath2 = join29(projectDir, "STORYBOARD.md");
59783
+ const storyboardPath2 = join30(projectDir, "STORYBOARD.md");
59760
59784
  const buildPlan = await createBuildPlan({
59761
59785
  projectDir,
59762
59786
  stage: selectedStage,
@@ -59828,7 +59852,7 @@ async function executeSceneBuild(opts) {
59828
59852
  }
59829
59853
  if (!buildPlan.validation.ok) {
59830
59854
  let invalidBeats = [];
59831
- if (existsSync52(storyboardPath2)) {
59855
+ if (existsSync53(storyboardPath2)) {
59832
59856
  invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
59833
59857
  }
59834
59858
  return finishBuildResult({
@@ -59853,7 +59877,7 @@ async function executeSceneBuild(opts) {
59853
59877
  totalLatencyMs: Date.now() - startedAt
59854
59878
  });
59855
59879
  }
59856
- if (!existsSync52(storyboardPath2)) {
59880
+ if (!existsSync53(storyboardPath2)) {
59857
59881
  return finishBuildResult({
59858
59882
  success: false,
59859
59883
  phase: "failed",
@@ -60089,9 +60113,9 @@ async function executeSceneBuild(opts) {
60089
60113
  }
60090
60114
  if (shouldRunStage(selectedStage, "compose")) {
60091
60115
  if (mode === "agent") {
60092
- const compositionsDir = join29(projectDir, "compositions");
60116
+ const compositionsDir = join30(projectDir, "compositions");
60093
60117
  const missingBeats = activeBeats.filter(
60094
- (b) => !existsSync52(join29(compositionsDir, `scene-${b.id}.html`))
60118
+ (b) => !existsSync53(join30(compositionsDir, `scene-${b.id}.html`))
60095
60119
  );
60096
60120
  if (missingBeats.length > 0) {
60097
60121
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -60166,8 +60190,8 @@ async function executeSceneBuild(opts) {
60166
60190
  if (stageReports.compose.status === "done") {
60167
60191
  for (const beat of composer === "template" ? [] : activeBeats) {
60168
60192
  const videoRel = `assets/video-${beat.id}.mp4`;
60169
- const compositionAbs = join29(projectDir, "compositions", `scene-${beat.id}.html`);
60170
- if (!existsSync52(join29(projectDir, videoRel)) || !existsSync52(compositionAbs)) continue;
60193
+ const compositionAbs = join30(projectDir, "compositions", `scene-${beat.id}.html`);
60194
+ if (!existsSync53(join30(projectDir, videoRel)) || !existsSync53(compositionAbs)) continue;
60171
60195
  const html = readFileSync2(compositionAbs, "utf-8");
60172
60196
  if (!html.includes(videoRel)) {
60173
60197
  warnings.push(
@@ -60223,7 +60247,7 @@ async function executeSceneBuild(opts) {
60223
60247
  });
60224
60248
  }
60225
60249
  if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
60226
- if (!existsSync52(join29(projectDir, "index.html"))) {
60250
+ if (!existsSync53(join30(projectDir, "index.html"))) {
60227
60251
  await scaffoldSceneProject({
60228
60252
  dir: projectDir,
60229
60253
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -60525,7 +60549,7 @@ async function dispatchNarration(beat, ctx) {
60525
60549
  return {
60526
60550
  status: "cached",
60527
60551
  path: existingRel,
60528
- durationSec: await safeAudioDuration(join29(ctx.projectDir, existingRel)),
60552
+ durationSec: await safeAudioDuration(join30(ctx.projectDir, existingRel)),
60529
60553
  provider: metadata?.provider,
60530
60554
  cachePath: metadata?.cachePath,
60531
60555
  cacheKey: metadata?.cacheKey,
@@ -60549,9 +60573,9 @@ async function dispatchNarration(beat, ctx) {
60549
60573
  voice,
60550
60574
  ext: resolution.audioExtension
60551
60575
  });
60552
- const cacheAbs = join29(ctx.projectDir, cache.path);
60576
+ const cacheAbs = join30(ctx.projectDir, cache.path);
60553
60577
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
60554
- const abs = join29(ctx.projectDir, rel);
60578
+ const abs = join30(ctx.projectDir, rel);
60555
60579
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
60556
60580
  projectDir: ctx.projectDir,
60557
60581
  kind: "narration",
@@ -60565,7 +60589,7 @@ async function dispatchNarration(beat, ctx) {
60565
60589
  return {
60566
60590
  status: "cached",
60567
60591
  path: existingRel,
60568
- durationSec: await safeAudioDuration(join29(ctx.projectDir, existingRel)),
60592
+ durationSec: await safeAudioDuration(join30(ctx.projectDir, existingRel)),
60569
60593
  provider: resolution.provider,
60570
60594
  cachePath: cache.path,
60571
60595
  cacheKey: cache.key,
@@ -60573,9 +60597,9 @@ async function dispatchNarration(beat, ctx) {
60573
60597
  freshness: "fresh"
60574
60598
  };
60575
60599
  }
60576
- if (existsSync52(cacheAbs) && !ctx.force) {
60577
- await mkdir27(dirname21(abs), { recursive: true });
60578
- await copyFile5(cacheAbs, abs);
60600
+ if (existsSync53(cacheAbs) && !ctx.force) {
60601
+ await mkdir28(dirname21(abs), { recursive: true });
60602
+ await copyFile6(cacheAbs, abs);
60579
60603
  await writeAssetMetadata({
60580
60604
  projectDir: ctx.projectDir,
60581
60605
  kind: "narration",
@@ -60605,13 +60629,13 @@ async function dispatchNarration(beat, ctx) {
60605
60629
  ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
60606
60630
  return { status: "failed", error };
60607
60631
  }
60608
- await mkdir27(dirname21(abs), { recursive: true });
60632
+ await mkdir28(dirname21(abs), { recursive: true });
60609
60633
  await writeFile43(abs, result.audioBuffer);
60610
60634
  for (const otherExt of ["mp3", "wav"]) {
60611
- const sibling = join29(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
60612
- if (sibling !== abs && existsSync52(sibling)) await rm8(sibling, { force: true });
60635
+ const sibling = join30(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
60636
+ if (sibling !== abs && existsSync53(sibling)) await rm8(sibling, { force: true });
60613
60637
  }
60614
- await mkdir27(dirname21(cacheAbs), { recursive: true });
60638
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
60615
60639
  await writeFile43(cacheAbs, result.audioBuffer);
60616
60640
  await writeAssetMetadata({
60617
60641
  projectDir: ctx.projectDir,
@@ -60644,8 +60668,8 @@ async function dispatchNarration(beat, ctx) {
60644
60668
  async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
60645
60669
  if (!narrationPath) return "skipped";
60646
60670
  const relPath = beatTranscriptRelPath(beat.id);
60647
- const abs = join29(ctx.projectDir, relPath);
60648
- if (existsSync52(abs) && !ctx.force && !narrationFresh) {
60671
+ const abs = join30(ctx.projectDir, relPath);
60672
+ if (existsSync53(abs) && !ctx.force && !narrationFresh) {
60649
60673
  ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
60650
60674
  return "cached";
60651
60675
  }
@@ -60658,7 +60682,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
60658
60682
  });
60659
60683
  return "skipped";
60660
60684
  }
60661
- const words = await transcribeNarrationWords(join29(ctx.projectDir, narrationPath), { apiKey });
60685
+ const words = await transcribeNarrationWords(join30(ctx.projectDir, narrationPath), { apiKey });
60662
60686
  if (words.length === 0) {
60663
60687
  ctx.onProgress({
60664
60688
  type: "transcript-skipped",
@@ -60668,7 +60692,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
60668
60692
  return "skipped";
60669
60693
  }
60670
60694
  try {
60671
- await mkdir27(dirname21(abs), { recursive: true });
60695
+ await mkdir28(dirname21(abs), { recursive: true });
60672
60696
  await writeFile43(abs, JSON.stringify(words, null, 2), "utf-8");
60673
60697
  } catch (err) {
60674
60698
  ctx.onProgress({
@@ -60716,7 +60740,7 @@ async function buildCharacters(characters, ctx) {
60716
60740
  const size = ctx.imageSize ?? "1536x1024";
60717
60741
  for (const character of characters) {
60718
60742
  if (character.imagePath) {
60719
- if (existsSync52(join29(ctx.projectDir, character.imagePath))) {
60743
+ if (existsSync53(join30(ctx.projectDir, character.imagePath))) {
60720
60744
  paths.set(character.name, character.imagePath);
60721
60745
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
60722
60746
  } else {
@@ -60729,7 +60753,7 @@ async function buildCharacters(characters, ctx) {
60729
60753
  if (!character.prompt) continue;
60730
60754
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
60731
60755
  const rel = `assets/character-${character.name}.png`;
60732
- const abs = join29(ctx.projectDir, rel);
60756
+ const abs = join30(ctx.projectDir, rel);
60733
60757
  const metadataOptions = { quality: ctx.imageQuality, size };
60734
60758
  const cache = characterCacheDescriptor({
60735
60759
  name: character.name,
@@ -60739,7 +60763,7 @@ async function buildCharacters(characters, ctx) {
60739
60763
  size,
60740
60764
  model: ctx.imageModel
60741
60765
  });
60742
- if (existsSync52(abs) && !ctx.force) {
60766
+ if (existsSync53(abs) && !ctx.force) {
60743
60767
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
60744
60768
  if (!metadata || isFreshCanonicalAsset({
60745
60769
  projectDir: ctx.projectDir,
@@ -60755,10 +60779,10 @@ async function buildCharacters(characters, ctx) {
60755
60779
  continue;
60756
60780
  }
60757
60781
  }
60758
- const cacheAbs = join29(ctx.projectDir, cache.path);
60759
- if (existsSync52(cacheAbs) && !ctx.force) {
60760
- await mkdir27(dirname21(abs), { recursive: true });
60761
- await copyFile5(cacheAbs, abs);
60782
+ const cacheAbs = join30(ctx.projectDir, cache.path);
60783
+ if (existsSync53(cacheAbs) && !ctx.force) {
60784
+ await mkdir28(dirname21(abs), { recursive: true });
60785
+ await copyFile6(cacheAbs, abs);
60762
60786
  await writeAssetMetadata({
60763
60787
  projectDir: ctx.projectDir,
60764
60788
  kind: "character",
@@ -60790,9 +60814,9 @@ async function buildCharacters(characters, ctx) {
60790
60814
  ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
60791
60815
  continue;
60792
60816
  }
60793
- await mkdir27(dirname21(abs), { recursive: true });
60817
+ await mkdir28(dirname21(abs), { recursive: true });
60794
60818
  await writeImageFile(abs, generated.buffer);
60795
- await mkdir27(dirname21(cacheAbs), { recursive: true });
60819
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
60796
60820
  await writeImageFile(cacheAbs, generated.buffer);
60797
60821
  await writeAssetMetadata({
60798
60822
  projectDir: ctx.projectDir,
@@ -60823,7 +60847,7 @@ async function dispatchBackdrop(beat, ctx) {
60823
60847
  if (!cue) return { status: "no-cue" };
60824
60848
  const prompt3 = augmentBackdropPrompt(cue);
60825
60849
  const rel = `assets/backdrop-${beat.id}.png`;
60826
- const abs = join29(ctx.projectDir, rel);
60850
+ const abs = join30(ctx.projectDir, rel);
60827
60851
  const size = ctx.imageSize ?? "1536x1024";
60828
60852
  const ratio = imageRatioForSize(size);
60829
60853
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -60837,7 +60861,7 @@ async function dispatchBackdrop(beat, ctx) {
60837
60861
  model: ctx.imageModel
60838
60862
  });
60839
60863
  const metadataPath = assetMetadataPath("backdrop", beat.id);
60840
- if (existsSync52(abs) && !ctx.force) {
60864
+ if (existsSync53(abs) && !ctx.force) {
60841
60865
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
60842
60866
  if (!metadata || isFreshCanonicalAsset({
60843
60867
  projectDir: ctx.projectDir,
@@ -60860,10 +60884,10 @@ async function dispatchBackdrop(beat, ctx) {
60860
60884
  };
60861
60885
  }
60862
60886
  }
60863
- const cacheAbs = join29(ctx.projectDir, cache.path);
60864
- if (existsSync52(cacheAbs) && !ctx.force) {
60865
- await mkdir27(dirname21(abs), { recursive: true });
60866
- await copyFile5(cacheAbs, abs);
60887
+ const cacheAbs = join30(ctx.projectDir, cache.path);
60888
+ if (existsSync53(cacheAbs) && !ctx.force) {
60889
+ await mkdir28(dirname21(abs), { recursive: true });
60890
+ await copyFile6(cacheAbs, abs);
60867
60891
  await writeAssetMetadata({
60868
60892
  projectDir: ctx.projectDir,
60869
60893
  kind: "backdrop",
@@ -60892,10 +60916,10 @@ async function dispatchBackdrop(beat, ctx) {
60892
60916
  ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
60893
60917
  return { status: "failed", error };
60894
60918
  }
60895
- await mkdir27(dirname21(abs), { recursive: true });
60919
+ await mkdir28(dirname21(abs), { recursive: true });
60896
60920
  const buffer = generated.buffer;
60897
60921
  await writeImageFile(abs, buffer);
60898
- await mkdir27(dirname21(cacheAbs), { recursive: true });
60922
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
60899
60923
  await writeImageFile(cacheAbs, buffer);
60900
60924
  await writeAssetMetadata({
60901
60925
  projectDir: ctx.projectDir,
@@ -60958,7 +60982,7 @@ async function buildImage(prompt3, ctx, ratio, sheetBuffers) {
60958
60982
  }
60959
60983
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
60960
60984
  const rel = `assets/keyframe-${beat.id}.png`;
60961
- const abs = join29(ctx.projectDir, rel);
60985
+ const abs = join30(ctx.projectDir, rel);
60962
60986
  const size = ctx.imageSize ?? "1536x1024";
60963
60987
  const ratio = imageRatioForSize(size);
60964
60988
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -60972,7 +60996,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
60972
60996
  characters: characterRefsRel,
60973
60997
  model: ctx.imageModel
60974
60998
  });
60975
- if (existsSync52(abs) && !ctx.force) {
60999
+ if (existsSync53(abs) && !ctx.force) {
60976
61000
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
60977
61001
  if (!metadata || isFreshCanonicalAsset({
60978
61002
  projectDir: ctx.projectDir,
@@ -60986,10 +61010,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
60986
61010
  return { status: "cached", rel };
60987
61011
  }
60988
61012
  }
60989
- const cacheAbs = join29(ctx.projectDir, cache.path);
60990
- if (existsSync52(cacheAbs) && !ctx.force) {
60991
- await mkdir27(dirname21(abs), { recursive: true });
60992
- await copyFile5(cacheAbs, abs);
61013
+ const cacheAbs = join30(ctx.projectDir, cache.path);
61014
+ if (existsSync53(cacheAbs) && !ctx.force) {
61015
+ await mkdir28(dirname21(abs), { recursive: true });
61016
+ await copyFile6(cacheAbs, abs);
60993
61017
  await writeAssetMetadata({
60994
61018
  projectDir: ctx.projectDir,
60995
61019
  kind: "keyframe",
@@ -61005,8 +61029,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61005
61029
  }
61006
61030
  const sheetBuffers = [];
61007
61031
  for (const relPath of characterRefsRel) {
61008
- const sheetAbs = join29(ctx.projectDir, relPath);
61009
- if (existsSync52(sheetAbs)) sheetBuffers.push(await readFile45(sheetAbs));
61032
+ const sheetAbs = join30(ctx.projectDir, relPath);
61033
+ if (existsSync53(sheetAbs)) sheetBuffers.push(await readFile45(sheetAbs));
61010
61034
  }
61011
61035
  const imgCtx = {
61012
61036
  projectDir: ctx.projectDir,
@@ -61018,9 +61042,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61018
61042
  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. ";
61019
61043
  const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
61020
61044
  if (!generated.success) return { status: "failed", error: generated.error };
61021
- await mkdir27(dirname21(abs), { recursive: true });
61045
+ await mkdir28(dirname21(abs), { recursive: true });
61022
61046
  await writeImageFile(abs, generated.buffer);
61023
- await mkdir27(dirname21(cacheAbs), { recursive: true });
61047
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
61024
61048
  await writeImageFile(cacheAbs, generated.buffer);
61025
61049
  await writeAssetMetadata({
61026
61050
  projectDir: ctx.projectDir,
@@ -61063,9 +61087,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
61063
61087
  const providerCue = stringOrUndefined4(beat.cues?.provider);
61064
61088
  const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
61065
61089
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
61066
- const characterRefsAbs = characterRefsRel.map((p) => join29(ctx.projectDir, p));
61090
+ const characterRefsAbs = characterRefsRel.map((p) => join30(ctx.projectDir, p));
61067
61091
  const rel = `assets/video-${beat.id}.mp4`;
61068
- const abs = join29(ctx.projectDir, rel);
61092
+ const abs = join30(ctx.projectDir, rel);
61069
61093
  const cache = videoCacheDescriptor({
61070
61094
  beatId: beat.id,
61071
61095
  cue: prompt3,
@@ -61077,7 +61101,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61077
61101
  keyframe: keyframeCue
61078
61102
  });
61079
61103
  const metadataPath = assetMetadataPath("video", beat.id);
61080
- if (existsSync52(abs) && !ctx.force) {
61104
+ if (existsSync53(abs) && !ctx.force) {
61081
61105
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
61082
61106
  if (!metadata || isFreshCanonicalAsset({
61083
61107
  projectDir: ctx.projectDir,
@@ -61100,10 +61124,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
61100
61124
  };
61101
61125
  }
61102
61126
  }
61103
- const cacheAbs = join29(ctx.projectDir, cache.path);
61104
- if (existsSync52(cacheAbs) && !ctx.force) {
61105
- await mkdir27(dirname21(abs), { recursive: true });
61106
- await copyFile5(cacheAbs, abs);
61127
+ const cacheAbs = join30(ctx.projectDir, cache.path);
61128
+ if (existsSync53(cacheAbs) && !ctx.force) {
61129
+ await mkdir28(dirname21(abs), { recursive: true });
61130
+ await copyFile6(cacheAbs, abs);
61107
61131
  await writeAssetMetadata({
61108
61132
  projectDir: ctx.projectDir,
61109
61133
  kind: "video",
@@ -61132,7 +61156,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61132
61156
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
61133
61157
  return { status: "skipped" };
61134
61158
  }
61135
- const keyframeImageAbs = keyframeImageRel ? join29(ctx.projectDir, keyframeImageRel) : void 0;
61159
+ const keyframeImageAbs = keyframeImageRel ? join30(ctx.projectDir, keyframeImageRel) : void 0;
61136
61160
  loadSceneBuildEnv(ctx.projectDir);
61137
61161
  let job;
61138
61162
  const recordJob = async (provider, submitted) => {
@@ -61210,10 +61234,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
61210
61234
  return { status: "failed", error: fallbackError };
61211
61235
  }
61212
61236
  }
61213
- if (result.status === "completed" && existsSync52(abs)) {
61237
+ if (result.status === "completed" && existsSync53(abs)) {
61214
61238
  await settleJob("completed");
61215
- await mkdir27(dirname21(cacheAbs), { recursive: true });
61216
- await copyFile5(abs, cacheAbs);
61239
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
61240
+ await copyFile6(abs, cacheAbs);
61217
61241
  await writeAssetMetadata({
61218
61242
  projectDir: ctx.projectDir,
61219
61243
  kind: "video",
@@ -61262,7 +61286,7 @@ async function dispatchMusic(beat, ctx) {
61262
61286
  const prompt3 = stringOrUndefined4(beat.cues?.music);
61263
61287
  if (!prompt3) return { status: "no-cue" };
61264
61288
  const rel = `assets/music-${beat.id}.mp3`;
61265
- const abs = join29(ctx.projectDir, rel);
61289
+ const abs = join30(ctx.projectDir, rel);
61266
61290
  const duration = normalizeMusicDuration(beat.duration);
61267
61291
  const cache = musicCacheDescriptor({
61268
61292
  beatId: beat.id,
@@ -61271,7 +61295,7 @@ async function dispatchMusic(beat, ctx) {
61271
61295
  duration
61272
61296
  });
61273
61297
  const metadataPath = assetMetadataPath("music", beat.id);
61274
- if (existsSync52(abs) && !ctx.force) {
61298
+ if (existsSync53(abs) && !ctx.force) {
61275
61299
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
61276
61300
  if (!metadata || isFreshCanonicalAsset({
61277
61301
  projectDir: ctx.projectDir,
@@ -61295,10 +61319,10 @@ async function dispatchMusic(beat, ctx) {
61295
61319
  };
61296
61320
  }
61297
61321
  }
61298
- const cacheAbs = join29(ctx.projectDir, cache.path);
61299
- if (existsSync52(cacheAbs) && !ctx.force) {
61300
- await mkdir27(dirname21(abs), { recursive: true });
61301
- await copyFile5(cacheAbs, abs);
61322
+ const cacheAbs = join30(ctx.projectDir, cache.path);
61323
+ if (existsSync53(cacheAbs) && !ctx.force) {
61324
+ await mkdir28(dirname21(abs), { recursive: true });
61325
+ await copyFile6(cacheAbs, abs);
61302
61326
  await writeAssetMetadata({
61303
61327
  projectDir: ctx.projectDir,
61304
61328
  kind: "music",
@@ -61370,13 +61394,13 @@ async function dispatchMusic(beat, ctx) {
61370
61394
  metadataPath
61371
61395
  };
61372
61396
  }
61373
- if (!existsSync52(abs)) {
61397
+ if (!existsSync53(abs)) {
61374
61398
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
61375
61399
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
61376
61400
  return { status: "failed", error };
61377
61401
  }
61378
- await mkdir27(dirname21(cacheAbs), { recursive: true });
61379
- await copyFile5(abs, cacheAbs);
61402
+ await mkdir28(dirname21(cacheAbs), { recursive: true });
61403
+ await copyFile6(abs, cacheAbs);
61380
61404
  await writeAssetMetadata({
61381
61405
  projectDir: ctx.projectDir,
61382
61406
  kind: "music",
@@ -61406,12 +61430,12 @@ async function dispatchMusic(beat, ctx) {
61406
61430
  };
61407
61431
  }
61408
61432
  function loadSceneBuildEnv(projectDir) {
61409
- (0, import_dotenv4.config)({ path: join29(projectDir, ".env"), quiet: true });
61433
+ (0, import_dotenv4.config)({ path: join30(projectDir, ".env"), quiet: true });
61410
61434
  (0, import_dotenv4.config)({ path: resolve62(process.cwd(), ".env"), quiet: true });
61411
61435
  let dir = process.cwd();
61412
61436
  while (dir !== dirname21(dir)) {
61413
- if (existsSync52(join29(dir, "pnpm-workspace.yaml"))) {
61414
- (0, import_dotenv4.config)({ path: join29(dir, ".env"), quiet: true });
61437
+ if (existsSync53(join30(dir, "pnpm-workspace.yaml"))) {
61438
+ (0, import_dotenv4.config)({ path: join30(dir, ".env"), quiet: true });
61415
61439
  return;
61416
61440
  }
61417
61441
  dir = dirname21(dir);
@@ -61628,7 +61652,7 @@ function mergeBeatOutcomes(base, updates) {
61628
61652
  }
61629
61653
  function firstExisting3(projectDir, relPaths) {
61630
61654
  for (const rel of relPaths) {
61631
- if (existsSync52(join29(projectDir, rel))) return rel;
61655
+ if (existsSync53(join30(projectDir, rel))) return rel;
61632
61656
  }
61633
61657
  return null;
61634
61658
  }
@@ -61653,7 +61677,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
61653
61677
  return getApiKeyFromConfig(videoProviderConfigKey(provider), { cwd: projectDir });
61654
61678
  }
61655
61679
  async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
61656
- const reportPath = join29(projectDir, "build-report.json");
61680
+ const reportPath = join30(projectDir, "build-report.json");
61657
61681
  for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
61658
61682
  if (source.stageReports) {
61659
61683
  for (const report of Object.values(source.stageReports)) {
@@ -61816,7 +61840,7 @@ function normalizeReportDuration(value) {
61816
61840
  }
61817
61841
  function buildReportComposition(projectDir, result, beatId) {
61818
61842
  const path = `compositions/scene-${beatId}.html`;
61819
- const exists = existsSync52(join29(projectDir, path));
61843
+ const exists = existsSync53(join30(projectDir, path));
61820
61844
  if (result.stageReports?.compose.status === "failed") return { path, exists, status: "failed" };
61821
61845
  if (result.phase === "needs-author" && !exists) return { path, exists, status: "needs-author" };
61822
61846
  if (exists) return { path, exists, status: "exists" };
@@ -61863,8 +61887,8 @@ function summarizeBuildBeats(projectDir, beats) {
61863
61887
  let compositionsReady = 0;
61864
61888
  for (const beat of beats) {
61865
61889
  if (beatAssetsReady(beat)) assetsReady += 1;
61866
- const compositionPath = join29(projectDir, "compositions", `scene-${beat.beatId}.html`);
61867
- if (existsSync52(compositionPath)) {
61890
+ const compositionPath = join30(projectDir, "compositions", `scene-${beat.beatId}.html`);
61891
+ if (existsSync53(compositionPath)) {
61868
61892
  compositionsReady += 1;
61869
61893
  } else {
61870
61894
  needsAuthor.push(beat.beatId);
@@ -61996,8 +62020,8 @@ var init_resolver = __esm({
61996
62020
 
61997
62021
  // src/pipeline/executor.ts
61998
62022
  import { resolve as resolve72 } from "node:path";
61999
- import { readFile as readFile47, writeFile as writeFile44, mkdir as mkdir29 } from "node:fs/promises";
62000
- import { existsSync as existsSync57 } from "node:fs";
62023
+ import { readFile as readFile47, writeFile as writeFile44, mkdir as mkdir30 } from "node:fs/promises";
62024
+ import { existsSync as existsSync58 } from "node:fs";
62001
62025
  function maxCostFor(action) {
62002
62026
  const cmd = ACTION_METADATA[action]?.command;
62003
62027
  if (!cmd) return 0;
@@ -62297,7 +62321,7 @@ async function ensureActionsRegistered() {
62297
62321
  }
62298
62322
  async function loadPipeline(filePath) {
62299
62323
  const absPath = resolve72(process.cwd(), filePath);
62300
- if (!existsSync57(absPath)) {
62324
+ if (!existsSync58(absPath)) {
62301
62325
  throw new Error(`Pipeline file not found: ${absPath}`);
62302
62326
  }
62303
62327
  const content = await readFile47(absPath, "utf-8");
@@ -62316,13 +62340,13 @@ async function loadPipeline(filePath) {
62316
62340
  async function executePipeline(manifest2, options = {}) {
62317
62341
  await ensureActionsRegistered();
62318
62342
  const outputDir = resolve72(process.cwd(), options.outputDir || `${manifest2.name}-output`);
62319
- await mkdir29(outputDir, { recursive: true });
62343
+ await mkdir30(outputDir, { recursive: true });
62320
62344
  const completedSteps = /* @__PURE__ */ new Map();
62321
62345
  const results = [];
62322
62346
  const startTime = Date.now();
62323
62347
  if (options.resume) {
62324
62348
  const checkpointPath = resolve72(outputDir, CHECKPOINT_FILE);
62325
- if (existsSync57(checkpointPath)) {
62349
+ if (existsSync58(checkpointPath)) {
62326
62350
  const checkpointContent = await readFile47(checkpointPath, "utf-8");
62327
62351
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
62328
62352
  for (const cs of checkpoint.completedSteps) {
@@ -66713,8 +66737,9 @@ async function runWithMcpPromotion(work, opts) {
66713
66737
  let lastRecordWriteAt = 0;
66714
66738
  let heartbeat = null;
66715
66739
  let writes = Promise.resolve();
66740
+ let finished = false;
66716
66741
  const writeRecord = (patch) => {
66717
- if (!record) return writes;
66742
+ if (!record || finished) return writes;
66718
66743
  record = {
66719
66744
  ...record,
66720
66745
  ...patch,
@@ -66777,8 +66802,14 @@ async function runWithMcpPromotion(work, opts) {
66777
66802
  void writeRecord({ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() });
66778
66803
  }, opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS);
66779
66804
  heartbeat.unref?.();
66805
+ const finish = (patch) => {
66806
+ if (heartbeat) clearInterval(heartbeat);
66807
+ const terminal2 = writeRecord(patch);
66808
+ finished = true;
66809
+ return terminal2;
66810
+ };
66780
66811
  void workPromise.then(
66781
- (result) => writeRecord(
66812
+ (result) => finish(
66782
66813
  result.success ? {
66783
66814
  status: "completed",
66784
66815
  progress: 100,
@@ -66790,13 +66821,11 @@ async function runWithMcpPromotion(work, opts) {
66790
66821
  resultPayload: result.data
66791
66822
  }
66792
66823
  ),
66793
- (error) => writeRecord({
66824
+ (error) => finish({
66794
66825
  status: "failed",
66795
66826
  error: error instanceof Error ? error.message : String(error)
66796
66827
  })
66797
- ).finally(() => {
66798
- if (heartbeat) clearInterval(heartbeat);
66799
- });
66828
+ );
66800
66829
  return {
66801
66830
  success: true,
66802
66831
  data: {
@@ -68833,12 +68862,12 @@ var init_generate2 = __esm({
68833
68862
  });
68834
68863
 
68835
68864
  // src/tools/manifest/storyboard.ts
68836
- import { existsSync as existsSync58 } from "node:fs";
68865
+ import { existsSync as existsSync59 } from "node:fs";
68837
68866
  import { readFile as readFile49, writeFile as writeFile46 } from "node:fs/promises";
68838
68867
  import { resolve as resolve76 } from "node:path";
68839
68868
  async function readStoryboard2(projectDir) {
68840
68869
  const path = resolve76(projectDir, "STORYBOARD.md");
68841
- if (!existsSync58(path)) return null;
68870
+ if (!existsSync59(path)) return null;
68842
68871
  return { path, markdown: await readFile49(path, "utf-8") };
68843
68872
  }
68844
68873
  var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
@@ -69113,9 +69142,9 @@ var init_storyboard2 = __esm({
69113
69142
  });
69114
69143
 
69115
69144
  // src/tools/manifest/design.ts
69116
- import { existsSync as existsSync59 } from "node:fs";
69145
+ import { existsSync as existsSync60 } from "node:fs";
69117
69146
  import { readFile as readFile50 } from "node:fs/promises";
69118
- import { resolve as resolve77, join as join31 } from "node:path";
69147
+ import { resolve as resolve77, join as join32 } from "node:path";
69119
69148
  var PROJECT_DIR_DESCRIPTION4, projectDirSchema2, designValidateTool, designTools;
69120
69149
  var init_design = __esm({
69121
69150
  "src/tools/manifest/design.ts"() {
@@ -69137,8 +69166,8 @@ var init_design = __esm({
69137
69166
  schema: projectDirSchema2,
69138
69167
  async execute(args, ctx) {
69139
69168
  const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69140
- const path = join31(projectDir, "DESIGN.md");
69141
- if (!existsSync59(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
69169
+ const path = join32(projectDir, "DESIGN.md");
69170
+ if (!existsSync60(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
69142
69171
  const md = await readFile50(path, "utf-8");
69143
69172
  const issues = validateDesignMarkdown(md);
69144
69173
  const design = parseDesign(md);
@@ -69164,7 +69193,7 @@ var init_design = __esm({
69164
69193
  // src/tools/manifest/remix.ts
69165
69194
  import { writeFile as writeFile47 } from "node:fs/promises";
69166
69195
  import { tmpdir as tmpdir5 } from "node:os";
69167
- import { join as join32 } from "node:path";
69196
+ import { join as join33 } from "node:path";
69168
69197
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, remixTools;
69169
69198
  var init_remix = __esm({
69170
69199
  "src/tools/manifest/remix.ts"() {
@@ -69277,7 +69306,7 @@ var init_remix = __esm({
69277
69306
  let resolvedPath = args.pipelinePath;
69278
69307
  let tempPath;
69279
69308
  if (args.pipelineYaml) {
69280
- tempPath = join32(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
69309
+ tempPath = join33(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
69281
69310
  await writeFile47(tempPath, args.pipelineYaml, "utf-8");
69282
69311
  resolvedPath = tempPath;
69283
69312
  }
@@ -69442,8 +69471,8 @@ var init_project_io = __esm({
69442
69471
  });
69443
69472
 
69444
69473
  // src/tools/manifest/timeline.ts
69445
- import { mkdir as mkdir30, writeFile as writeFile49 } from "node:fs/promises";
69446
- import { basename as basename21, resolve as resolve78 } from "node:path";
69474
+ import { mkdir as mkdir31, writeFile as writeFile49 } from "node:fs/promises";
69475
+ import { basename as basename22, resolve as resolve78 } from "node:path";
69447
69476
  var MEDIA_TYPES, timelineCreateTool, timelineInfoTool, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
69448
69477
  var init_timeline = __esm({
69449
69478
  "src/tools/manifest/timeline.ts"() {
@@ -69481,7 +69510,7 @@ var init_timeline = __esm({
69481
69510
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
69482
69511
  }),
69483
69512
  async execute(args, ctx) {
69484
- const projectName = args.name === "." ? basename21(ctx.workingDirectory) : basename21(resolve78(ctx.workingDirectory, args.name));
69513
+ const projectName = args.name === "." ? basename22(ctx.workingDirectory) : basename22(resolve78(ctx.workingDirectory, args.name));
69485
69514
  const project = new Project(projectName);
69486
69515
  if (args.fps) project.setFrameRate(args.fps);
69487
69516
  let outputPath;
@@ -69489,7 +69518,7 @@ var init_timeline = __esm({
69489
69518
  outputPath = resolve78(ctx.workingDirectory, args.outputPath);
69490
69519
  } else {
69491
69520
  const dirPath = resolve78(ctx.workingDirectory, args.name);
69492
- await mkdir30(dirPath, { recursive: true });
69521
+ await mkdir31(dirPath, { recursive: true });
69493
69522
  outputPath = resolve78(dirPath, TIMELINE_FILENAME);
69494
69523
  }
69495
69524
  await writeFile49(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
@@ -69791,11 +69820,11 @@ var init_timeline = __esm({
69791
69820
 
69792
69821
  // src/commands/_shared/project-list.ts
69793
69822
  import { readdir as readdir7, readFile as readFile52, stat as stat9 } from "node:fs/promises";
69794
- import { existsSync as existsSync60 } from "node:fs";
69795
- import { join as join33, resolve as resolve79 } from "node:path";
69823
+ import { existsSync as existsSync61 } from "node:fs";
69824
+ import { join as join34, resolve as resolve79 } from "node:path";
69796
69825
  async function executeProjectList(opts = {}) {
69797
69826
  const workspaceDir = resolve79(opts.workspaceDir ?? ".");
69798
- if (!existsSync60(workspaceDir)) {
69827
+ if (!existsSync61(workspaceDir)) {
69799
69828
  return {
69800
69829
  success: false,
69801
69830
  workspaceDir,
@@ -69812,11 +69841,11 @@ async function executeProjectList(opts = {}) {
69812
69841
  if (entry.name === "node_modules") continue;
69813
69842
  if (entry.name.startsWith(".")) continue;
69814
69843
  if (entry.name.startsWith("_")) {
69815
- archivedCount += await countProjects(join33(workspaceDir, entry.name));
69844
+ archivedCount += await countProjects(join34(workspaceDir, entry.name));
69816
69845
  continue;
69817
69846
  }
69818
- const projectDir = join33(workspaceDir, entry.name);
69819
- if (!existsSync60(join33(projectDir, "STORYBOARD.md"))) continue;
69847
+ const projectDir = join34(workspaceDir, entry.name);
69848
+ if (!existsSync61(join34(projectDir, "STORYBOARD.md"))) continue;
69820
69849
  projects.push(await describeProject(entry.name, projectDir));
69821
69850
  }
69822
69851
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -69826,7 +69855,7 @@ async function countProjects(dir) {
69826
69855
  try {
69827
69856
  const entries = await readdir7(dir, { withFileTypes: true });
69828
69857
  return entries.filter(
69829
- (e) => e.isDirectory() && existsSync60(join33(dir, e.name, "STORYBOARD.md"))
69858
+ (e) => e.isDirectory() && existsSync61(join34(dir, e.name, "STORYBOARD.md"))
69830
69859
  ).length;
69831
69860
  } catch {
69832
69861
  return 0;
@@ -69837,7 +69866,7 @@ async function describeProject(name, projectDir) {
69837
69866
  let storyboardDurationSec = 0;
69838
69867
  const timestamps = [];
69839
69868
  try {
69840
- const storyboardPath2 = join33(projectDir, "STORYBOARD.md");
69869
+ const storyboardPath2 = join34(projectDir, "STORYBOARD.md");
69841
69870
  const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
69842
69871
  beats = parsed.beats.length;
69843
69872
  storyboardDurationSec = Number(
@@ -69849,7 +69878,7 @@ async function describeProject(name, projectDir) {
69849
69878
  let buildStatus;
69850
69879
  let buildUpdatedAt;
69851
69880
  try {
69852
- const reportPath = join33(projectDir, "build-report.json");
69881
+ const reportPath = join34(projectDir, "build-report.json");
69853
69882
  const report = JSON.parse(await readFile52(reportPath, "utf-8"));
69854
69883
  buildStatus = report.status ?? report.phase;
69855
69884
  buildUpdatedAt = report.updatedAt;
@@ -69858,12 +69887,12 @@ async function describeProject(name, projectDir) {
69858
69887
  }
69859
69888
  let latestRender;
69860
69889
  try {
69861
- const rendersDir = join33(projectDir, "renders");
69890
+ const rendersDir = join34(projectDir, "renders");
69862
69891
  const files = await readdir7(rendersDir);
69863
69892
  let newest;
69864
69893
  for (const file of files) {
69865
69894
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
69866
- const { mtimeMs } = await stat9(join33(rendersDir, file));
69895
+ const { mtimeMs } = await stat9(join34(rendersDir, file));
69867
69896
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
69868
69897
  }
69869
69898
  if (newest) {
@@ -70696,7 +70725,7 @@ var init_export2 = __esm({
70696
70725
 
70697
70726
  // src/tools/manifest/agent-only.ts
70698
70727
  import { readFile as readFile54, writeFile as writeFile51, readdir as readdir8, stat as stat10, access as access8, unlink as unlink2 } from "node:fs/promises";
70699
- import { resolve as resolve83, join as join34, basename as basename22, extname as extname17 } from "node:path";
70728
+ import { resolve as resolve83, join as join35, basename as basename23, extname as extname17 } from "node:path";
70700
70729
  function matchPattern(filename, pattern) {
70701
70730
  const regex2 = new RegExp(
70702
70731
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -70781,7 +70810,7 @@ var init_agent_only = __esm({
70781
70810
  const results = [];
70782
70811
  for (const entry of entries) {
70783
70812
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
70784
- const fullPath = join34(absPath, entry.name);
70813
+ const fullPath = join35(absPath, entry.name);
70785
70814
  const stats = await stat10(fullPath);
70786
70815
  if (entry.isDirectory()) {
70787
70816
  results.push(`[DIR] ${entry.name}/`);
@@ -70894,7 +70923,7 @@ var init_agent_only = __esm({
70894
70923
  const scanDir = async (dir) => {
70895
70924
  const entries = await readdir8(dir, { withFileTypes: true });
70896
70925
  for (const entry of entries) {
70897
- const entryPath = join34(dir, entry.name);
70926
+ const entryPath = join35(dir, entry.name);
70898
70927
  if (entry.isDirectory() && recursive) {
70899
70928
  await scanDir(entryPath);
70900
70929
  } else if (entry.isFile()) {
@@ -70911,7 +70940,7 @@ var init_agent_only = __esm({
70911
70940
  mediaFiles.sort();
70912
70941
  const addedSources = [];
70913
70942
  for (const mediaFile of mediaFiles) {
70914
- const mediaName = basename22(mediaFile);
70943
+ const mediaName = basename23(mediaFile);
70915
70944
  const mediaType = detectMediaType4(mediaFile);
70916
70945
  let duration = imageDuration;
70917
70946
  if (mediaType !== "image") {
@@ -71987,7 +72016,7 @@ init_source();
71987
72016
  // package.json
71988
72017
  var package_default = {
71989
72018
  name: "@vibeframe/cli",
71990
- version: "0.115.8",
72019
+ version: "0.115.9",
71991
72020
  description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
71992
72021
  private: false,
71993
72022
  license: "MIT",
@@ -72338,9 +72367,9 @@ init_validate();
72338
72367
  init_output();
72339
72368
  init_cost_tier();
72340
72369
  init_project_resolver();
72341
- import { readFile as readFile16, writeFile as writeFile13 } from "node:fs/promises";
72342
- import { existsSync as existsSync12 } from "node:fs";
72343
- import { resolve as resolve19, basename as basename5, extname as extname3 } from "node:path";
72370
+ import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
72371
+ import { existsSync as existsSync13 } from "node:fs";
72372
+ import { resolve as resolve19, basename as basename6, extname as extname3 } from "node:path";
72344
72373
  var timelineCommand = new Command("timeline").description("Low-level timeline JSON commands").addHelpText(
72345
72374
  "after",
72346
72375
  `
@@ -72389,11 +72418,11 @@ timelineCommand.command("add-source").description("Add a media source to the tim
72389
72418
  return;
72390
72419
  }
72391
72420
  const filePath = await resolveTimelineFile(projectPath);
72392
- const content = await readFile16(filePath, "utf-8");
72421
+ const content = await readFile17(filePath, "utf-8");
72393
72422
  const data = JSON.parse(content);
72394
72423
  const project = Project.fromJSON(data);
72395
72424
  const absMediaPath = resolve19(process.cwd(), mediaPath);
72396
- const mediaName = options.name || basename5(mediaPath);
72425
+ const mediaName = options.name || basename6(mediaPath);
72397
72426
  const mediaType = options.type || detectMediaType(mediaPath);
72398
72427
  const duration = parseFloat(options.duration) || 5;
72399
72428
  const source = project.addSource({
@@ -72455,7 +72484,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
72455
72484
  return;
72456
72485
  }
72457
72486
  const filePath = await resolveTimelineFile(projectPath);
72458
- const content = await readFile16(filePath, "utf-8");
72487
+ const content = await readFile17(filePath, "utf-8");
72459
72488
  const data = JSON.parse(content);
72460
72489
  const project = Project.fromJSON(data);
72461
72490
  const source = project.getSource(sourceId);
@@ -72533,7 +72562,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
72533
72562
  return;
72534
72563
  }
72535
72564
  const filePath = await resolveTimelineFile(projectPath);
72536
- const content = await readFile16(filePath, "utf-8");
72565
+ const content = await readFile17(filePath, "utf-8");
72537
72566
  const data = JSON.parse(content);
72538
72567
  const project = Project.fromJSON(data);
72539
72568
  const existingTracks = project.getTracksByType(type);
@@ -72598,7 +72627,7 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
72598
72627
  return;
72599
72628
  }
72600
72629
  const filePath = await resolveTimelineFile(projectPath);
72601
- const content = await readFile16(filePath, "utf-8");
72630
+ const content = await readFile17(filePath, "utf-8");
72602
72631
  const data = JSON.parse(content);
72603
72632
  const project = Project.fromJSON(data);
72604
72633
  const clip = project.getClip(clipId);
@@ -72669,7 +72698,7 @@ timelineCommand.command("trim-clip").description("Trim a clip").argument("<proje
72669
72698
  return;
72670
72699
  }
72671
72700
  const filePath = await resolveTimelineFile(projectPath);
72672
- const content = await readFile16(filePath, "utf-8");
72701
+ const content = await readFile17(filePath, "utf-8");
72673
72702
  const data = JSON.parse(content);
72674
72703
  const project = Project.fromJSON(data);
72675
72704
  const clip = project.getClip(clipId);
@@ -72711,10 +72740,10 @@ timelineCommand.command("list").description("List timeline contents").argument("
72711
72740
  const startedAt = Date.now();
72712
72741
  try {
72713
72742
  const filePath = await resolveTimelineFile(projectPath);
72714
- if (!existsSync12(filePath)) {
72743
+ if (!existsSync13(filePath)) {
72715
72744
  exitWithError(notFoundError(projectPath));
72716
72745
  }
72717
- const content = await readFile16(filePath, "utf-8");
72746
+ const content = await readFile17(filePath, "utf-8");
72718
72747
  const data = JSON.parse(content);
72719
72748
  const project = Project.fromJSON(data);
72720
72749
  const showAll = !options.sources && !options.tracks && !options.clips;
@@ -72836,7 +72865,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
72836
72865
  return;
72837
72866
  }
72838
72867
  const filePath = await resolveTimelineFile(projectPath);
72839
- const content = await readFile16(filePath, "utf-8");
72868
+ const content = await readFile17(filePath, "utf-8");
72840
72869
  const data = JSON.parse(content);
72841
72870
  const project = Project.fromJSON(data);
72842
72871
  const clip = project.getClip(clipId);
@@ -72899,7 +72928,7 @@ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argume
72899
72928
  return;
72900
72929
  }
72901
72930
  const filePath = await resolveTimelineFile(projectPath);
72902
- const content = await readFile16(filePath, "utf-8");
72931
+ const content = await readFile17(filePath, "utf-8");
72903
72932
  const data = JSON.parse(content);
72904
72933
  const project = Project.fromJSON(data);
72905
72934
  const clip = project.getClip(clipId);
@@ -72958,7 +72987,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
72958
72987
  return;
72959
72988
  }
72960
72989
  const filePath = await resolveTimelineFile(projectPath);
72961
- const content = await readFile16(filePath, "utf-8");
72990
+ const content = await readFile17(filePath, "utf-8");
72962
72991
  const data = JSON.parse(content);
72963
72992
  const project = Project.fromJSON(data);
72964
72993
  const clip = project.getClip(clipId);
@@ -73014,7 +73043,7 @@ timelineCommand.command("move-clip").description("Move a clip to a new position"
73014
73043
  return;
73015
73044
  }
73016
73045
  const filePath = await resolveTimelineFile(projectPath);
73017
- const content = await readFile16(filePath, "utf-8");
73046
+ const content = await readFile17(filePath, "utf-8");
73018
73047
  const data = JSON.parse(content);
73019
73048
  const project = Project.fromJSON(data);
73020
73049
  const clip = project.getClip(clipId);
@@ -73440,7 +73469,7 @@ init_output();
73440
73469
  init_validate();
73441
73470
  init_ai_audio();
73442
73471
  import { resolve as resolve40 } from "node:path";
73443
- import { existsSync as existsSync36 } from "node:fs";
73472
+ import { existsSync as existsSync37 } from "node:fs";
73444
73473
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, dub, duck, isolate, voice clone)").addHelpText(
73445
73474
  "after",
73446
73475
  `
@@ -73468,7 +73497,7 @@ Run 'vibe schema audio.<command>' for structured parameter info.
73468
73497
  audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("--format <format>", "Output format: json, srt, vtt (auto-detected from extension)").action(async (audioPath, options) => {
73469
73498
  const startedAt = Date.now();
73470
73499
  if (options.output) validateOutputPath(options.output);
73471
- if (!existsSync36(resolve40(process.cwd(), audioPath))) {
73500
+ if (!existsSync37(resolve40(process.cwd(), audioPath))) {
73472
73501
  exitWithError(notFoundError(resolve40(process.cwd(), audioPath)));
73473
73502
  }
73474
73503
  const apiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI", options.apiKey);
@@ -73560,7 +73589,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
73560
73589
  return;
73561
73590
  }
73562
73591
  if (options.output) validateOutputPath(options.output);
73563
- if (!existsSync36(resolve40(process.cwd(), audioPath))) {
73592
+ if (!existsSync37(resolve40(process.cwd(), audioPath))) {
73564
73593
  exitWithError(notFoundError(resolve40(process.cwd(), audioPath)));
73565
73594
  }
73566
73595
  const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
@@ -73612,7 +73641,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
73612
73641
  rejectControlChars(options.name);
73613
73642
  if (!samples || samples.length === 0) exitWithError(usageError("At least one audio sample is required"));
73614
73643
  for (const samplePath of samples) {
73615
- if (!existsSync36(resolve40(process.cwd(), samplePath))) exitWithError(notFoundError(samplePath));
73644
+ if (!existsSync37(resolve40(process.cwd(), samplePath))) exitWithError(notFoundError(samplePath));
73616
73645
  }
73617
73646
  let labels;
73618
73647
  try {
@@ -73660,7 +73689,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
73660
73689
  }
73661
73690
  if (options.output) validateOutputPath(options.output);
73662
73691
  if (!options.language) exitWithError(usageError("Target language is required. Use -l or --language"));
73663
- if (!existsSync36(resolve40(process.cwd(), mediaPath))) exitWithError(notFoundError(mediaPath));
73692
+ if (!existsSync37(resolve40(process.cwd(), mediaPath))) exitWithError(notFoundError(mediaPath));
73664
73693
  await requireApiKey("OPENAI_API_KEY", "OpenAI");
73665
73694
  const spinner2 = isJsonMode() ? null : ora("Transcribing, translating, and dubbing...").start();
73666
73695
  const result = await executeDub({
@@ -73812,8 +73841,8 @@ init_ai_highlights();
73812
73841
  init_ai_animated_caption();
73813
73842
  init_output();
73814
73843
  init_cost_tier();
73815
- import { resolve as resolve43, basename as basename14 } from "node:path";
73816
- import { existsSync as existsSync39 } from "node:fs";
73844
+ import { resolve as resolve43, basename as basename15 } from "node:path";
73845
+ import { existsSync as existsSync40 } from "node:fs";
73817
73846
  var remixCommand = new Command("remix").description("Repurpose existing media (highlights, auto-shorts, animated captions)").addHelpText(
73818
73847
  "after",
73819
73848
  `
@@ -73876,7 +73905,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
73876
73905
  const startedAt = Date.now();
73877
73906
  try {
73878
73907
  const absVideoPath = resolve43(process.cwd(), videoPath);
73879
- if (!existsSync39(absVideoPath)) {
73908
+ if (!existsSync40(absVideoPath)) {
73880
73909
  exitWithError(notFoundError(absVideoPath));
73881
73910
  }
73882
73911
  if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
@@ -73913,7 +73942,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
73913
73942
  console.log();
73914
73943
  console.log(source_default.bold.cyan("Animated Caption Pipeline"));
73915
73944
  console.log(source_default.dim("\u2500".repeat(60)));
73916
- console.log(` Video: ${source_default.bold(basename14(absVideoPath))}`);
73945
+ console.log(` Video: ${source_default.bold(basename15(absVideoPath))}`);
73917
73946
  console.log(` Style: ${source_default.bold(options.style)}`);
73918
73947
  console.log(` Mode: ${source_default.bold(options.fast ? "ASS (fast)" : "Remotion")}`);
73919
73948
  console.log();
@@ -74240,7 +74269,7 @@ init_esm();
74240
74269
  init_source();
74241
74270
  init_ora();
74242
74271
  import { stat as stat7 } from "node:fs/promises";
74243
- import { resolve as resolve44, extname as extname13, basename as basename15 } from "node:path";
74272
+ import { resolve as resolve44, extname as extname13, basename as basename16 } from "node:path";
74244
74273
 
74245
74274
  // ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
74246
74275
  init_lib();
@@ -78118,7 +78147,7 @@ mediaCommand.command("info").description("Get media file information").argument(
78118
78147
  try {
78119
78148
  const filePath = resolve44(process.cwd(), file);
78120
78149
  const ext = extname13(filePath).toLowerCase();
78121
- const fileName = basename15(filePath);
78150
+ const fileName = basename16(filePath);
78122
78151
  const fileStat = await stat7(filePath);
78123
78152
  const mediaType = detectMediaType2(ext);
78124
78153
  spinner2.stop();
@@ -78259,7 +78288,7 @@ init_engine();
78259
78288
  init_output();
78260
78289
  init_cost_tier();
78261
78290
  import { readFile as readFile33, writeFile as writeFile34, readdir as readdir6 } from "node:fs/promises";
78262
- import { resolve as resolve45, basename as basename16, extname as extname14, join as join20 } from "node:path";
78291
+ import { resolve as resolve45, basename as basename17, extname as extname14, join as join21 } from "node:path";
78263
78292
  var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
78264
78293
  function detectMediaType3(filePath) {
78265
78294
  const ext = extname14(filePath).toLowerCase();
@@ -78327,7 +78356,7 @@ batchCommand.command("import").description("Import multiple media files from a d
78327
78356
  const scanDir = async (dir) => {
78328
78357
  const entries = await readdir6(dir, { withFileTypes: true });
78329
78358
  for (const entry of entries) {
78330
- const entryPath = join20(dir, entry.name);
78359
+ const entryPath = join21(dir, entry.name);
78331
78360
  if (entry.isDirectory() && options.recursive) {
78332
78361
  await scanDir(entryPath);
78333
78362
  } else if (entry.isFile()) {
@@ -78349,7 +78378,7 @@ batchCommand.command("import").description("Import multiple media files from a d
78349
78378
  const addedSources = [];
78350
78379
  const defaultDuration = parseFloat(options.duration);
78351
78380
  for (const mediaFile of mediaFiles) {
78352
- const mediaName = basename16(mediaFile);
78381
+ const mediaName = basename17(mediaFile);
78353
78382
  const mediaType = detectMediaType3(mediaFile);
78354
78383
  const duration = mediaType === "image" ? defaultDuration : 0;
78355
78384
  const source = project.addSource({
@@ -78364,7 +78393,7 @@ batchCommand.command("import").description("Import multiple media files from a d
78364
78393
  spinner2.succeed(source_default.green(`Imported ${addedSources.length} media files`));
78365
78394
  console.log();
78366
78395
  for (const file of mediaFiles) {
78367
- console.log(source_default.dim(" +"), basename16(file));
78396
+ console.log(source_default.dim(" +"), basename17(file));
78368
78397
  }
78369
78398
  console.log();
78370
78399
  } catch (error) {
@@ -78705,9 +78734,9 @@ async function copyToClipboard(text) {
78705
78734
  }
78706
78735
 
78707
78736
  // src/utils/agent-host-detect.ts
78708
- import { existsSync as existsSync40 } from "node:fs";
78737
+ import { existsSync as existsSync41 } from "node:fs";
78709
78738
  import { homedir as homedir5 } from "node:os";
78710
- import { join as join21 } from "node:path";
78739
+ import { join as join22 } from "node:path";
78711
78740
  function detectAgentHosts(env2 = process.env) {
78712
78741
  const home = homedir5();
78713
78742
  return [
@@ -78767,8 +78796,8 @@ function detectAgentHosts(env2 = process.env) {
78767
78796
  }
78768
78797
  const configDirRel = HOST_CONFIG_DIRS[host.id];
78769
78798
  if (configDirRel) {
78770
- const path = join21(home, configDirRel);
78771
- if (existsSync40(path)) {
78799
+ const path = join22(home, configDirRel);
78800
+ if (existsSync41(path)) {
78772
78801
  signals2.push({ kind: "configDir", path });
78773
78802
  }
78774
78803
  }
@@ -78822,8 +78851,8 @@ function isOnPath(binary, env2) {
78822
78851
  const sep3 = process.platform === "win32" ? ";" : ":";
78823
78852
  for (const dir of path.split(sep3)) {
78824
78853
  if (!dir) continue;
78825
- if (existsSync40(join21(dir, binary))) return true;
78826
- if (process.platform === "win32" && existsSync40(join21(dir, `${binary}.exe`))) return true;
78854
+ if (existsSync41(join22(dir, binary))) return true;
78855
+ if (process.platform === "win32" && existsSync41(join22(dir, `${binary}.exe`))) return true;
78827
78856
  }
78828
78857
  return false;
78829
78858
  }
@@ -80064,9 +80093,9 @@ function pickCompletionHint(shellPath) {
80064
80093
  // src/commands/init.ts
80065
80094
  init_esm();
80066
80095
  init_source();
80067
- import { existsSync as existsSync42 } from "node:fs";
80068
- import { mkdir as mkdir23, readFile as readFile37, writeFile as writeFile37 } from "node:fs/promises";
80069
- import { basename as basename18, dirname as dirname16, resolve as resolve49, sep } from "node:path";
80096
+ import { existsSync as existsSync43 } from "node:fs";
80097
+ import { mkdir as mkdir24, readFile as readFile37, writeFile as writeFile37 } from "node:fs/promises";
80098
+ import { basename as basename19, dirname as dirname16, resolve as resolve49, sep } from "node:path";
80070
80099
  init_scene_project();
80071
80100
  init_visual_styles();
80072
80101
 
@@ -80232,10 +80261,10 @@ init_project_config();
80232
80261
  init_install_skill();
80233
80262
 
80234
80263
  // src/commands/_shared/host-integration.ts
80235
- import { existsSync as existsSync41 } from "node:fs";
80236
- import { mkdir as mkdir22, readFile as readFile36, writeFile as writeFile36 } from "node:fs/promises";
80264
+ import { existsSync as existsSync42 } from "node:fs";
80265
+ import { mkdir as mkdir23, readFile as readFile36, writeFile as writeFile36 } from "node:fs/promises";
80237
80266
  import { homedir as homedir7 } from "node:os";
80238
- import { dirname as dirname15, join as join22, resolve as resolve48 } from "node:path";
80267
+ import { dirname as dirname15, join as join23, resolve as resolve48 } from "node:path";
80239
80268
  var HOST_SELECTIONS = [
80240
80269
  "codex",
80241
80270
  "claude",
@@ -80262,7 +80291,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
80262
80291
  label: "OpenAI Codex",
80263
80292
  surfaces: ["shell", "project-guidance", "mcp"],
80264
80293
  guidanceFiles: ["AGENTS.md"],
80265
- configPath: (dir) => join22(resolve48(dir), ".codex", "config.toml"),
80294
+ configPath: (dir) => join23(resolve48(dir), ".codex", "config.toml"),
80266
80295
  configKind: "codex-toml",
80267
80296
  notes: [
80268
80297
  "Codex reads AGENTS.md and can also load project-scoped .codex/config.toml after the project is trusted.",
@@ -80275,7 +80304,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
80275
80304
  label: "Claude Code",
80276
80305
  surfaces: ["shell", "project-guidance", "mcp"],
80277
80306
  guidanceFiles: ["AGENTS.md", "CLAUDE.md", ".claude/skills/hyperframes/"],
80278
- configPath: (dir) => join22(resolve48(dir), ".mcp.json"),
80307
+ configPath: (dir) => join23(resolve48(dir), ".mcp.json"),
80279
80308
  configKind: "mcp-json",
80280
80309
  notes: [
80281
80310
  "Claude Code can drive vibe directly through shell plus AGENTS.md/CLAUDE.md.",
@@ -80300,7 +80329,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
80300
80329
  label: "Cursor",
80301
80330
  surfaces: ["shell", "project-guidance", "mcp"],
80302
80331
  guidanceFiles: ["AGENTS.md", ".cursor/rules/hyperframes.mdc"],
80303
- configPath: (dir) => join22(resolve48(dir), ".cursor", "mcp.json"),
80332
+ configPath: (dir) => join23(resolve48(dir), ".cursor", "mcp.json"),
80304
80333
  configKind: "mcp-json",
80305
80334
  notes: [
80306
80335
  "Cursor can use AGENTS.md/rules for shell-driven work and .cursor/mcp.json for typed MCP tools."
@@ -80341,11 +80370,11 @@ async function planHostSetup(host, projectDir, opts) {
80341
80370
  const warnings = [];
80342
80371
  const nextSteps = hostNextSteps(host);
80343
80372
  for (const relPath of host.guidanceFiles.filter((p) => !p.endsWith("/"))) {
80344
- const absPath = join22(resolve48(projectDir), relPath);
80373
+ const absPath = join23(resolve48(projectDir), relPath);
80345
80374
  files.push({
80346
80375
  path: absPath,
80347
- status: existsSync41(absPath) ? "skipped-exists" : "missing",
80348
- reason: existsSync41(absPath) ? "already present" : "run vibe init --agent all first"
80376
+ status: existsSync42(absPath) ? "skipped-exists" : "missing",
80377
+ reason: existsSync42(absPath) ? "already present" : "run vibe init --agent all first"
80349
80378
  });
80350
80379
  }
80351
80380
  if (!opts.write || opts.dryRun) {
@@ -80389,15 +80418,15 @@ async function inspectHost(host, projectDir) {
80389
80418
  const warnings = [];
80390
80419
  const nextSteps = hostNextSteps(host);
80391
80420
  const guidanceFiles = host.guidanceFiles.map((relPath) => {
80392
- const absPath = relPath.endsWith("/") ? join22(resolve48(projectDir), relPath) : join22(resolve48(projectDir), relPath);
80421
+ const absPath = relPath.endsWith("/") ? join23(resolve48(projectDir), relPath) : join23(resolve48(projectDir), relPath);
80393
80422
  return {
80394
80423
  path: absPath,
80395
- status: existsSync41(absPath) ? "skipped-exists" : "missing",
80396
- reason: existsSync41(absPath) ? "present" : "missing"
80424
+ status: existsSync42(absPath) ? "skipped-exists" : "missing",
80425
+ reason: existsSync42(absPath) ? "present" : "missing"
80397
80426
  };
80398
80427
  });
80399
80428
  let configured = false;
80400
- if (existsSync41(configPath)) {
80429
+ if (existsSync42(configPath)) {
80401
80430
  try {
80402
80431
  const content = await readFile36(configPath, "utf-8");
80403
80432
  if (host.configKind === "codex-toml") {
@@ -80420,7 +80449,7 @@ async function inspectHost(host, projectDir) {
80420
80449
  }
80421
80450
  async function mergeMcpJson(configPath, opts) {
80422
80451
  let root = {};
80423
- const exists = existsSync41(configPath);
80452
+ const exists = existsSync42(configPath);
80424
80453
  if (exists) {
80425
80454
  const raw = await readFile36(configPath, "utf-8");
80426
80455
  try {
@@ -80439,7 +80468,7 @@ async function mergeMcpJson(configPath, opts) {
80439
80468
  ...mcpServers,
80440
80469
  vibeframe: opts.server
80441
80470
  };
80442
- await mkdir22(dirname15(configPath), { recursive: true });
80471
+ await mkdir23(dirname15(configPath), { recursive: true });
80443
80472
  if (exists && opts.backup) {
80444
80473
  await writeFile36(
80445
80474
  `${configPath}.bak-${timestamp()}`,
@@ -80461,7 +80490,7 @@ async function mergeMcpJson(configPath, opts) {
80461
80490
  };
80462
80491
  }
80463
80492
  root.mcpServers = { ...mcpServers, vibeframe: opts.server };
80464
- await mkdir22(dirname15(configPath), { recursive: true });
80493
+ await mkdir23(dirname15(configPath), { recursive: true });
80465
80494
  if (exists && opts.backup) {
80466
80495
  await writeFile36(
80467
80496
  `${configPath}.bak-${timestamp()}`,
@@ -80473,11 +80502,11 @@ async function mergeMcpJson(configPath, opts) {
80473
80502
  return { path: configPath, status: exists ? "merged" : "wrote" };
80474
80503
  }
80475
80504
  async function mergeCodexToml(configPath, opts) {
80476
- const exists = existsSync41(configPath);
80505
+ const exists = existsSync42(configPath);
80477
80506
  const block = `${CODEX_MANAGED_START}
80478
80507
  ${CODEX_MCP_BLOCK}${CODEX_MANAGED_END}`;
80479
80508
  if (!exists) {
80480
- await mkdir22(dirname15(configPath), { recursive: true });
80509
+ await mkdir23(dirname15(configPath), { recursive: true });
80481
80510
  await writeFile36(
80482
80511
  configPath,
80483
80512
  `#:schema https://developers.openai.com/codex/config-schema.json
@@ -80577,16 +80606,16 @@ function hostNextSteps(host) {
80577
80606
  function claudeDesktopConfigPath() {
80578
80607
  const home = homedir7();
80579
80608
  if (process.platform === "darwin") {
80580
- return join22(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
80609
+ return join23(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
80581
80610
  }
80582
80611
  if (process.platform === "win32") {
80583
- return join22(
80584
- process.env.APPDATA ?? join22(home, "AppData", "Roaming"),
80612
+ return join23(
80613
+ process.env.APPDATA ?? join23(home, "AppData", "Roaming"),
80585
80614
  "Claude",
80586
80615
  "claude_desktop_config.json"
80587
80616
  );
80588
80617
  }
80589
- return join22(home, ".config", "Claude", "claude_desktop_config.json");
80618
+ return join23(home, ".config", "Claude", "claude_desktop_config.json");
80590
80619
  }
80591
80620
  function timestamp() {
80592
80621
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -81023,13 +81052,13 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
81023
81052
  );
81024
81053
  }
81025
81054
  const projectDir = resolve49(projectDirArg);
81026
- const projectName = basename18(projectDir);
81055
+ const projectName = basename19(projectDir);
81027
81056
  const targetHosts = resolveTargets(agent);
81028
81057
  const wantsClaude = targetHosts.includes("claude-code");
81029
81058
  const wantsGemini = targetHosts.includes("gemini-cli");
81030
81059
  const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
81031
81060
  const actions = [];
81032
- if (!existsSync42(projectDir)) {
81061
+ if (!existsSync43(projectDir)) {
81033
81062
  if (options.dryRun) {
81034
81063
  actions.push({
81035
81064
  path: projectDir,
@@ -81037,7 +81066,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
81037
81066
  reason: "(would create directory)"
81038
81067
  });
81039
81068
  } else {
81040
- await mkdir23(projectDir, { recursive: true });
81069
+ await mkdir24(projectDir, { recursive: true });
81041
81070
  }
81042
81071
  }
81043
81072
  if (wantsCrossTool || !wantsClaude) {
@@ -81189,7 +81218,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
81189
81218
  );
81190
81219
  }
81191
81220
  const projectDir = resolve49(projectDirArg);
81192
- const projectName = basename18(projectDir);
81221
+ const projectName = basename19(projectDir);
81193
81222
  const groups = describeSceneScaffold({ dir: projectDir, profile, kind });
81194
81223
  const fromBrief = typeof options.from === "string" && options.from.trim() ? await readBriefOrLiteral(String(options.from), process.cwd()) : null;
81195
81224
  if (options.dryRun) {
@@ -81245,7 +81274,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
81245
81274
  });
81246
81275
  const storyboardPath2 = resolve49(projectDir, "STORYBOARD.md");
81247
81276
  const designPath = resolve49(projectDir, "DESIGN.md");
81248
- if (options.force || result.created.includes(storyboardPath2) || !existsSync42(storyboardPath2)) {
81277
+ if (options.force || result.created.includes(storyboardPath2) || !existsSync43(storyboardPath2)) {
81249
81278
  const plan = await rewriteBundleFromMarkdown(projectDir, draft.storyboardMd);
81250
81279
  if (!result.created.includes(storyboardPath2)) result.created.push(storyboardPath2);
81251
81280
  const planScenePaths = new Set(plan.scenes.map((file) => resolve49(projectDir, file.path)));
@@ -81257,7 +81286,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
81257
81286
  if (!result.created.includes(scenePath)) result.created.push(scenePath);
81258
81287
  }
81259
81288
  }
81260
- if (options.force || result.created.includes(designPath) || !existsSync42(designPath)) {
81289
+ if (options.force || result.created.includes(designPath) || !existsSync43(designPath)) {
81261
81290
  await writeFile37(designPath, draft.designMd, "utf-8");
81262
81291
  if (!result.created.includes(designPath)) result.created.push(designPath);
81263
81292
  }
@@ -81380,7 +81409,7 @@ function printGroup(label, files, projectDir) {
81380
81409
  }
81381
81410
  async function readBriefOrLiteral(value, cwd) {
81382
81411
  const candidate = resolve49(cwd, value);
81383
- if (existsSync42(candidate)) {
81412
+ if (existsSync43(candidate)) {
81384
81413
  return readFile37(candidate, "utf-8");
81385
81414
  }
81386
81415
  return value;
@@ -81434,20 +81463,20 @@ function normalizeHostAction(status) {
81434
81463
  return "would-write";
81435
81464
  }
81436
81465
  async function writeIfMissing(absPath, content, force, dryRun) {
81437
- const exists = existsSync42(absPath);
81466
+ const exists = existsSync43(absPath);
81438
81467
  if (exists && !force) {
81439
81468
  return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
81440
81469
  }
81441
81470
  if (dryRun) {
81442
81471
  return { path: absPath, status: "would-write" };
81443
81472
  }
81444
- await mkdir23(dirname16(absPath), { recursive: true });
81473
+ await mkdir24(dirname16(absPath), { recursive: true });
81445
81474
  await writeFile37(absPath, content, "utf-8");
81446
81475
  return { path: absPath, status: "wrote" };
81447
81476
  }
81448
81477
  async function mergeGitignore(absPath, additions, dryRun) {
81449
81478
  const marker = "# VibeFrame";
81450
- if (!existsSync42(absPath)) {
81479
+ if (!existsSync43(absPath)) {
81451
81480
  if (dryRun) return { path: absPath, status: "would-write" };
81452
81481
  await writeFile37(absPath, additions.trimStart(), "utf-8");
81453
81482
  return { path: absPath, status: "wrote" };
@@ -81487,7 +81516,7 @@ init_composer_resolve();
81487
81516
  init_cost_tier();
81488
81517
  init_output();
81489
81518
  import { readFile as readFile39, writeFile as writeFile39 } from "node:fs/promises";
81490
- import { existsSync as existsSync44 } from "node:fs";
81519
+ import { existsSync as existsSync45 } from "node:fs";
81491
81520
  import { resolve as resolve51 } from "node:path";
81492
81521
  var storyboardCommand = new Command("storyboard").description("Read, validate, and safely mutate STORYBOARD.md cue blocks").addHelpText("after", `
81493
81522
  Examples:
@@ -81754,7 +81783,7 @@ function parseComposer(value) {
81754
81783
  }
81755
81784
  async function readBrief(value, cwd) {
81756
81785
  const candidate = resolve51(cwd, value);
81757
- if (existsSync44(candidate)) {
81786
+ if (existsSync45(candidate)) {
81758
81787
  return readFile39(candidate, "utf-8");
81759
81788
  }
81760
81789
  return value;
@@ -81791,8 +81820,8 @@ init_design_parse();
81791
81820
  init_cost_tier();
81792
81821
  init_output();
81793
81822
  import { readFile as readFile40 } from "node:fs/promises";
81794
- import { existsSync as existsSync45 } from "node:fs";
81795
- import { resolve as resolve52, join as join24 } from "node:path";
81823
+ import { existsSync as existsSync46 } from "node:fs";
81824
+ import { resolve as resolve52, join as join25 } from "node:path";
81796
81825
  var designCommand = new Command("design").description("Read and validate DESIGN.md (google-labs design.md format)").addHelpText(
81797
81826
  "after",
81798
81827
  `
@@ -81804,8 +81833,8 @@ Examples:
81804
81833
  designCommand.command("validate").description("Validate DESIGN.md front-matter tokens and sections").argument("[project-dir]", "Project directory", ".").action(async (projectDirArg) => {
81805
81834
  const startedAt = Date.now();
81806
81835
  const projectDir = resolve52(projectDirArg);
81807
- const path = join24(projectDir, "DESIGN.md");
81808
- if (!existsSync45(path)) {
81836
+ const path = join25(projectDir, "DESIGN.md");
81837
+ if (!existsSync46(path)) {
81809
81838
  exitWithError(generalError(`DESIGN.md not found in ${projectDir}`));
81810
81839
  return;
81811
81840
  }
@@ -82027,8 +82056,8 @@ init_build_plan();
82027
82056
  init_storyboard_parse();
82028
82057
  init_output();
82029
82058
  init_build_video_providers();
82030
- import { resolve as resolve63, join as join30 } from "node:path";
82031
- import { readFileSync as readFileSync3, existsSync as existsSync53 } from "node:fs";
82059
+ import { resolve as resolve63, join as join31 } from "node:path";
82060
+ import { readFileSync as readFileSync3, existsSync as existsSync54 } from "node:fs";
82032
82061
  var VALID_MODES2 = ["agent", "batch", "auto"];
82033
82062
  var VALID_STAGES2 = ["assets", "transcript", "compose", "sync", "render", "all"];
82034
82063
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
@@ -82392,8 +82421,8 @@ function buildPlanValidationFailureData(plan) {
82392
82421
  };
82393
82422
  }
82394
82423
  function estimateBuildCost(params) {
82395
- const storyboardPath2 = join30(params.projectDir, "STORYBOARD.md");
82396
- if (!existsSync53(storyboardPath2)) return null;
82424
+ const storyboardPath2 = join31(params.projectDir, "STORYBOARD.md");
82425
+ if (!existsSync54(storyboardPath2)) return null;
82397
82426
  let beats = 0;
82398
82427
  try {
82399
82428
  const md = readFileSync3(storyboardPath2, "utf-8");
@@ -83148,7 +83177,7 @@ init_dist2();
83148
83177
  init_exec_safe();
83149
83178
  init_exec_safe();
83150
83179
  init_api_key();
83151
- import { existsSync as existsSync54 } from "node:fs";
83180
+ import { existsSync as existsSync55 } from "node:fs";
83152
83181
  import { resolve as resolve68 } from "node:path";
83153
83182
  init_composer_resolve();
83154
83183
  init_cost_tier();
@@ -83280,13 +83309,13 @@ async function runDiagnostics() {
83280
83309
  installed = false;
83281
83310
  }
83282
83311
  } else if (toolName === "chrome") {
83283
- const { existsSync: existsSync61 } = await import("node:fs");
83312
+ const { existsSync: existsSync62 } = await import("node:fs");
83284
83313
  const { homedir: homedir8 } = await import("node:os");
83285
- const { join: join35 } = await import("node:path");
83314
+ const { join: join36 } = await import("node:path");
83286
83315
  const chromePaths = [
83287
83316
  process.env.HYPERFRAMES_CHROME_PATH,
83288
83317
  process.env.CHROME_PATH,
83289
- join35(
83318
+ join36(
83290
83319
  homedir8(),
83291
83320
  ".cache",
83292
83321
  "puppeteer",
@@ -83301,7 +83330,7 @@ async function runDiagnostics() {
83301
83330
  "/usr/bin/chromium",
83302
83331
  "/usr/bin/chromium-browser"
83303
83332
  ];
83304
- installed = chromePaths.some((p) => p && existsSync61(p));
83333
+ installed = chromePaths.some((p) => p && existsSync62(p));
83305
83334
  } else {
83306
83335
  installed = commandExists(toolName);
83307
83336
  }
@@ -83341,7 +83370,7 @@ async function runDiagnostics() {
83341
83370
  }
83342
83371
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
83343
83372
  path: rel,
83344
- exists: existsSync54(resolve68(cwd, rel))
83373
+ exists: existsSync55(resolve68(cwd, rel))
83345
83374
  }));
83346
83375
  const projectInitialized = projectFiles.some((f) => f.exists);
83347
83376
  const nearestProjectConfigPath = await findProjectConfigPath(cwd);
@@ -83359,7 +83388,7 @@ async function runDiagnostics() {
83359
83388
  } catch (err) {
83360
83389
  if (!(err instanceof ComposerResolveError)) throw err;
83361
83390
  }
83362
- const sceneProjectInCwd = existsSync54(resolve68(cwd, "STORYBOARD.md"));
83391
+ const sceneProjectInCwd = existsSync55(resolve68(cwd, "STORYBOARD.md"));
83363
83392
  const skillInstalled = hyperframesSkillAvailable(cwd);
83364
83393
  const skillPath = resolveSkillReference(cwd);
83365
83394
  return {
@@ -83908,8 +83937,8 @@ init_detect();
83908
83937
  init_ai_edit();
83909
83938
  init_output();
83910
83939
  import { resolve as resolve70 } from "node:path";
83911
- import { existsSync as existsSync55 } from "node:fs";
83912
- import { mkdir as mkdir28, rm as rm9 } from "node:fs/promises";
83940
+ import { existsSync as existsSync56 } from "node:fs";
83941
+ import { mkdir as mkdir29, rm as rm9 } from "node:fs/promises";
83913
83942
  var DEMO_DIR = resolve70(process.cwd(), ".vibeframe-demo");
83914
83943
  async function generateTestVideo(outputPath) {
83915
83944
  const filter = [
@@ -83939,7 +83968,7 @@ async function generateTestVideo(outputPath) {
83939
83968
  "-y",
83940
83969
  outputPath
83941
83970
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
83942
- return existsSync55(outputPath);
83971
+ return existsSync56(outputPath);
83943
83972
  } catch {
83944
83973
  try {
83945
83974
  await execSafe("ffmpeg", [
@@ -83965,7 +83994,7 @@ async function generateTestVideo(outputPath) {
83965
83994
  "-y",
83966
83995
  outputPath
83967
83996
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
83968
- return existsSync55(outputPath);
83997
+ return existsSync56(outputPath);
83969
83998
  } catch {
83970
83999
  return false;
83971
84000
  }
@@ -83989,7 +84018,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
83989
84018
  console.log(source_default.dim(" " + "\u2500".repeat(50)));
83990
84019
  console.log();
83991
84020
  }
83992
- await mkdir28(DEMO_DIR, { recursive: true });
84021
+ await mkdir29(DEMO_DIR, { recursive: true });
83993
84022
  const testVideo = resolve70(DEMO_DIR, "test-input.mp4");
83994
84023
  const genSpinner = !isJson ? ora("Generating test video...").start() : null;
83995
84024
  const generated = await generateTestVideo(testVideo);
@@ -84107,11 +84136,11 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84107
84136
 
84108
84137
  // src/commands/context.ts
84109
84138
  init_esm();
84110
- import { existsSync as existsSync56 } from "node:fs";
84139
+ import { existsSync as existsSync57 } from "node:fs";
84111
84140
  import { readFile as readFile46 } from "node:fs/promises";
84112
84141
  import { resolve as resolve71, dirname as dirname22 } from "node:path";
84113
- import { fileURLToPath } from "node:url";
84114
- var __filename = fileURLToPath(import.meta.url);
84142
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
84143
+ var __filename = fileURLToPath2(import.meta.url);
84115
84144
  var __dirname = dirname22(__filename);
84116
84145
  function resolveContextPath(baseDir = __dirname) {
84117
84146
  const candidates = [
@@ -84120,7 +84149,7 @@ function resolveContextPath(baseDir = __dirname) {
84120
84149
  // Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
84121
84150
  resolve71(baseDir, "../../CONTEXT.md")
84122
84151
  ];
84123
- return candidates.find((candidate) => existsSync56(candidate)) ?? null;
84152
+ return candidates.find((candidate) => existsSync57(candidate)) ?? null;
84124
84153
  }
84125
84154
  var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").option("--format <format>", "Output format: markdown | json", "markdown").action(async (_options, cmd) => {
84126
84155
  const options = {
@@ -85559,7 +85588,7 @@ init_source();
85559
85588
  init_config();
85560
85589
  init_schema();
85561
85590
  init_api_key();
85562
- import { access as access9, writeFile as writeFile52, mkdir as mkdir31 } from "node:fs/promises";
85591
+ import { access as access9, writeFile as writeFile52, mkdir as mkdir32 } from "node:fs/promises";
85563
85592
  import { dirname as dirname23 } from "node:path";
85564
85593
  var BANNER_SHOWN_PATH = `${USER_CONFIG_DIR}/.banner-shown`;
85565
85594
  async function isFirstRun() {
@@ -85579,7 +85608,7 @@ async function isFirstRun() {
85579
85608
  }
85580
85609
  async function markBannerShown() {
85581
85610
  try {
85582
- await mkdir31(dirname23(BANNER_SHOWN_PATH), { recursive: true });
85611
+ await mkdir32(dirname23(BANNER_SHOWN_PATH), { recursive: true });
85583
85612
  await writeFile52(BANNER_SHOWN_PATH, (/* @__PURE__ */ new Date()).toISOString());
85584
85613
  } catch {
85585
85614
  }